add an new adaptive example with 6 in patches WORKING

rapp/detached-control-value-iframe
Robert Rapp 2025-07-02 18:39:24 +02:00
parent c5f605395b
commit 43a11ab86b
10 changed files with 1492 additions and 62 deletions

View File

@ -7,7 +7,23 @@
<title>channel pass through rnbo patches</title>
</head>
<body>
<div id="app"></div>
<div id="app">
<div id="gains">
<label>Gain Noise: <span id="noiseGain-value">0.5</span></label><br>
<input type="range" id="noiseGain" min="0.001" max="1" step="0.005" value="0.5"><br>
<label>Gain Soundscape: <span id="scapeGain-value">0.5</span></label><br>
<input type="range" id="scapeGain" min="0.001" max="1" step="0.005" value="0.5"><br>
</div>
<div id="param-controls">
<label>LAF: <input type="number" id="param-laf" value="2.5" step="0.1" /></label><br>
<label>Attack (ms): <input type="number" id="param-attack" value="5000" step="100" /></label><br>
<label>Release (ms): <input type="number" id="param-release" value="5000" step="100" /></label><br>
<label>Attenuation Factor: <input type="number" id="param-attenuation" value="0.0562" step="0.01" /></label><br>
<label>Dynamic Range: <input type="number" id="param-range" value="-3" step="1" /></label><br>
<button id="update-rnbo">Update RNBO</button>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -0,0 +1,55 @@
Copyright (c) 2023 Cycling '74
The code that Max generates automatically and that end users are capable of
exporting and using, and any associated documentation files (the “Software”)
is a work of authorship for which Cycling '74 is the author and owner for
copyright purposes.
This Software is dual-licensed either under the terms of the Cycling '74
License for Max-Generated Code for Export, or alternatively under the terms
of the General Public License (GPL) Version 3. You may use the Software
according to either of these licenses as it is most appropriate for your
project on a case-by-case basis (proprietary or not).
A) Cycling '74 License for Max-Generated Code for Export
A license is hereby granted, free of charge, to any person obtaining a copy
of the Software (“Licensee”) to use, copy, modify, merge, publish, and
distribute copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
The Software is licensed to Licensee for all uses that do not include the sale,
sublicensing, or commercial distribution of software that incorporates this
source code. This means that the Licensee is free to use this software for
educational, research, and prototyping purposes, to create musical or other
creative works with software that incorporates this source code, or any other
use that does not constitute selling software that makes use of this source
code. Commercial distribution also includes the packaging of free software with
other paid software, hardware, or software-provided commercial services.
For entities with UNDER $200k in annual revenue or funding, a license is hereby
granted, free of charge, for the sale, sublicensing, or commercial distribution
of software that incorporates this source code, for as long as the entity's
annual revenue remains below $200k annual revenue or funding.
For entities with OVER $200k in annual revenue or funding interested in the
sale, sublicensing, or commercial distribution of software that incorporates
this source code, please send inquiries to licensing@cycling74.com.
The above copyright notice and this license shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Please see
https://support.cycling74.com/hc/en-us/articles/10730637742483-RNBO-Export-Licensing-FAQ
for additional information
B) General Public License Version 3 (GPLv3)
Details of the GPLv3 license can be found at: https://www.gnu.org/licenses/gpl-3.0.html

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,57 @@
// composables/GainStore.ts
const gainValues = new Map<string, number>()
export const useRnboParamStore = () => ({
get: (id: string) => gainValues.get(id),
set: (id: string, val: number) => {
gainValues.set(id, val)
},
all: () => gainValues
})
export function connectSliderToGain(sliderId: string, gainNode: GainNode, labelId: string) {
const slider = document.getElementById(sliderId) as HTMLInputElement
const label = document.getElementById(labelId)
console.log({
slider: slider,
label: label
})
if (!slider || !gainNode) return
// Initialen Wert setzen
const initialValue = parseFloat(slider.value)
gainNode.gain.setValueAtTime(initialValue, gainNode.context.currentTime)
if (label) label.textContent = initialValue.toFixed(2)
slider.addEventListener("input", (e) => {
const value = parseFloat((e.target as HTMLInputElement).value)
gainNode.gain.setValueAtTime(value, gainNode.context.currentTime)
if (label) label.textContent = value.toFixed(2)
})
}
// Nutzung mit Slidern
document.addEventListener("DOMContentLoaded", () => {
const store = useRnboParamStore();
// Beispiel-Slider-IDs
const sliders = document.querySelectorAll("input[type='range']");
sliders.forEach(slider => {
const id = slider.id;
// Initialen Wert setzen
store.set(id, parseFloat(slider.value))
// Wenn sich der Wert ändert, speichere ihn
slider.addEventListener("input", (event) => {
const newVal = parseFloat(event.target.value)
// Hier sollte jetzt ein update au
store.set(id, newVal)
// Zeige neuen Wert an
document.getElementById(`${id}-value`).textContent = newVal.toFixed(2);
});
});
});

View File

@ -6,3 +6,28 @@ export const useRnboParamStore = () => ({
set: (id: string, val: number) => parameterValues.set(id, val),
all: () => parameterValues
})
export function attachValueUpdate(events: { send: any; subscribe?: () => void }) {
// Button-Handler
document.getElementById('update-rnbo')!.addEventListener('click', () => {
const laf = parseFloat((document.getElementById('param-laf') as HTMLInputElement).value)
const attack = parseFloat((document.getElementById('param-attack') as HTMLInputElement).value)
const release = parseFloat((document.getElementById('param-release') as HTMLInputElement).value)
const attenuation = parseFloat((document.getElementById('param-attenuation') as HTMLInputElement).value)
const range = parseFloat((document.getElementById('param-range') as HTMLInputElement).value)
const updateEvents = [
{ tag: "in7", value: 5000 }, // observation period
{ tag: "in8", value: 125 }, // integrationtime 125
{ tag: "in9", value: laf },
{ tag: "in10", value: attack },
{ tag: "in11", value: release },
{ tag: "in12", value: attenuation },
{ tag: "in13", value: range }
]
updateEvents.forEach(ev => events.send(ev.tag, ev.value))
console.log("RNBO params updated:", updateEvents)
})
}

View File

@ -1,19 +1,48 @@
import { useRnboParamStore } from './composables/RNBOParameterStore'
import type { Device } from '@rnbo/js'
export function attachDBValueListener(noiseDevice: Device, freq: number) {
export function attachDBValueListener(noiseDevice: Device, freq?: number) {
const paramStore = useRnboParamStore()
const deviceId = freq.toString() || 'unknown'
createDisplayElement(deviceId)
const deviceId = freq ? freq.toString() : 'fullband'
console.log("DEVICE :" , { noiseDevice })
createDisplayElement("out3")
createDisplayElement("out4")
createDisplayElement("out5")
createDisplayElement("out6")
createDisplayElement("out7")
createDisplayElement("out8")
createDisplayElement("out9")
createDisplayElement("out10")
createDisplayElement("out11")
createDisplayElement("out12")
console.log(`🔊 Attaching message listener for freq ${freq} on device ${deviceId}`)
noiseDevice.messageEvent.subscribe((ev: any) => {
if (ev.tag === 'out1') {
const payload = Array.isArray(ev.payload) ? ev.payload[0] : ev.payload
paramStore.set(deviceId, payload)
updateDisplay(deviceId, payload)
}
if (ev.tag === "out3") paramStore.set("control value after timeramp 63 Hz", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out4") paramStore.set("control value after timeramp 125 Hz", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out5") paramStore.set("control value after timeramp 250 Hz", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out6") paramStore.set("Control value after timeramp 500 Hz", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out7") paramStore.set("Control value after timeramp 1000 Hz", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out8") paramStore.set("Control value after timeramp 2000 Hz", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out9") paramStore.set("Control value after timeramp 4000 Hz", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out10") paramStore.set("Controll value after timeramp 8000 Hz", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out11") paramStore.set("Controll value after timeramp 16000 Hz", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out12") paramStore.set("Controll value music", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out3") updateDisplay("out3", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out4") updateDisplay("out4", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out5") updateDisplay("out5", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out6") updateDisplay("out6", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out7") updateDisplay("out7", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out8") updateDisplay("out8", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out9") updateDisplay("out9", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out10") updateDisplay("out10", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out11") updateDisplay("out11", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
if (ev.tag === "out12") updateDisplay("out12", Array.isArray(ev.payload) ? ev.payload[0] : ev.payload as number)
})
}

16
src/lib/device.ts Normal file
View File

@ -0,0 +1,16 @@
import { getAudioContext } from "../audio/context";
export function updateRNBOParameters(events:any,init:any) {
if (Array.isArray(init.events)) {
init.events.forEach((e: { tag: any; value: any; time: any; }) => {
if (e) {
events.send(
e.tag,
e.value,
e.time ?? getAudioContext().currentTime
);
}
});
}
console.info("parameters updated...")
}

View File

@ -2,13 +2,15 @@ import RNBO, { createDevice, TimeNow } from '@rnbo/js'
import { fetchOrLoadBuffer } from './composables/BufferStore'
import { setupPlayButton } from './composables/Player';
import { getAudioContext } from './audio/context'
import patcherUrl from '../public/patches/passthrough_6Kanal_Test.rnbopat.export.json?url'
import { updateRNBOParameters } from './lib/device';
import patcherUrl from '../public/patches/6Kanal_adaptive_soundscape/All_In_One_ohneRegelschleife_Mindboost_Algo_clean_02_6in_2out.rnbopat.export.json?url'
import { attachDBValueListener } from './dbValueListener';
import { connectSliderToGain } from "./composables/GainStore";
import { attachValueUpdate } from './composables/RNBOParameterStore';
// RNBO
// RNBO
let patcherPromise: Promise<any>
let microphone: MediaStreamAudioSourceNode
async function getPatcher() {
if (!patcherPromise) patcherPromise = fetch(patcherUrl).then(r => r.json())
@ -45,39 +47,36 @@ async function init() {
console.log(`Input Event ${name} schedueled.`)
},
};
attachValueUpdate(events)
updateRNBOParameters(events, [
{ tag: 'in7', value: 5000 },
{ tag: 'in8', value: 125 },
{ tag: 'in9', value: 2.5 },
{ tag: 'in10', value: 2500 },
{ tag: 'in11', value: 3000 },
{ tag: 'in12', value: 0.5623 },
{ tag: 'in13', value: -12. },
])
// PARAMETER
// PARAMETER
// PARAMETER
let in7 = 0 // Hier soll das aus dem jeweiligen Textfeld
let in8 = 0
let in9 = 0
let in10 = 0
let in11 = 0
let in12 = 0
events.send("in7", in7) // channel 1 auf mute
events.send("in8", in8) // channel 2 auf mute
events.send("in9", in9) // channel 3 auf mute
events.send("in10", in10) // channel 4 auf mute
events.send("in11", in11) // channel 5 auf mute
events.send("in12", in12) // channel 6 auf mute
console.log("Alle Ins gesetzt")
// Schritt 4 Fetch alle AudioBuffer
const buffer1 = await fetchOrLoadBuffer("100000",
'audio/LMusik_RSprache.mp3',
const buffer1 = await fetchOrLoadBuffer("100003",
'audio/MindMusik_15min_48KHz_Forest.mp3',
ctx
)
const buffer2 = await fetchOrLoadBuffer("100001",
'audio/stereo-test1.mp3',
const buffer2 = await fetchOrLoadBuffer("100004",
'audio/masking/3bands/mid_band_256kbps.webm',
ctx
)
const buffer3 = await fetchOrLoadBuffer("100002",
'audio/stereo-test2.wav',
const buffer3 = await fetchOrLoadBuffer("100005",
'audio/MindMusik_15min_48KHz_Forest.mp3',
ctx
)
const sourceNode1 = ctx.createBufferSource() // stereo
@ -92,49 +91,46 @@ console.log("Alle Ins gesetzt")
sourceNode2.loop = true
sourceNode3.loop = true
const channelMergerL = ctx.createChannelMerger()
const channelMergerR = ctx.createChannelMerger()
const noiseGain = ctx.createGain();
const scapeGain = ctx.createGain();
noiseGain.gain.linearRampToValueAtTime(1, getAudioContext().currentTime + 1);
scapeGain.gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 1);
// CHANNEL INTERPRETATION AND COUNT MODE
channelMergerL.channelInterpretation = 'speakers'
channelMergerR.channelInterpretation = 'speakers'
rnboDevice.node.channelCountMode = 'max'
rnboDevice.node.channelInterpretation = 'speakers'
const inputGainNode = ctx.createGain()
connectSliderToGain("noiseGain", noiseGain, "noise")
connectSliderToGain("scapeGain", scapeGain, "scape")
// Connecte Audios auf ein Merger
sourceNode2.connect(noiseGain)
sourceNode3.connect(scapeGain)
let { left:micL, right:micR } = splitStereoIntoMonoChannels(microphone)
const { left: sourceNode1L, right: sourceNode1R } = splitStereoIntoMonoChannels(sourceNode1)
const {left: sourceNode2L, right: sourceNode2R } = splitStereoIntoMonoChannels(sourceNode2)
const {left: sourceNode3L, right: sourceNode3R } = splitStereoIntoMonoChannels(sourceNode3)
const {left: sourceNode2L, right: sourceNode2R } = splitStereoIntoMonoChannels(noiseGain)
const {left: sourceNode3L, right: sourceNode3R } = splitStereoIntoMonoChannels(scapeGain)
console.log({sourceNode1L, sourceNode1R }, {sourceNode2L, sourceNode2R },{sourceNode3L, sourceNode3R })
console.log({ channelMergerL, channelMergerR })
// sourceNode1L.connect(ctx.destination, 0, 0)
sourceNode1L.connect(rnboDevice.node, 0, 0)
sourceNode1R.connect(rnboDevice.node, 0, 1)
micL.connect(rnboDevice.node, 0, 0)
micR.connect(rnboDevice.node, 0, 1)
sourceNode2L.connect(rnboDevice.node, 0, 2)
sourceNode2R.connect(rnboDevice.node, 0 ,3)
sourceNode3L.connect(rnboDevice.node, 0 ,4)
sourceNode3R.connect(rnboDevice.node, 0, 5)
const channelSplitter = ctx.createChannelSplitter(6)
channelMergerL.connect(channelSplitter)
// Schritt 5 Verbinde AudioNodes
// channelMerger.connect(ctx.destination) // 6
rnboDevice.node.connect(ctx.destination)
console.log("RNBO Device created connected with input source")
console.log("Everything is setup, display a play button")
setupPlayButton([sourceNode1,sourceNode2,sourceNode3], inputGainNode , ctx)
setupPlayButton([sourceNode2,sourceNode3], inputGainNode , ctx)
attachDBValueListener(rnboDevice)
// UI
@ -144,12 +140,10 @@ console.log("Alle Ins gesetzt")
// Report-Infos anhängen
ui.appendChild(createNodeInspector('RNBO Device', rnboDevice.node))
ui.appendChild(createNodeInspectorAndToggle('Scape Source LEFT 1', sourceNode1L, 0, events))
ui.appendChild(createNodeInspectorAndToggle('Mic L', micL, 0, events))
ui.appendChild(createNodeInspectorAndToggle('Mic R', micR, 0, events))
ui.appendChild(createNodeInspectorAndToggle('Scape Source 2', sourceNode2, 1, events))
ui.appendChild(createNodeInspectorAndToggle('Scape Source 3', sourceNode3, 2, events))
ui.appendChild(createNodeInspector('ChannelMerger', channelMergerL))
ui.appendChild(createNodeInspector('ChannelMerger', channelMergerR))
ui.appendChild(createNodeInspector('channelSplitter', channelSplitter))
console.log("rnboDevice.numInputChannels= "+ rnboDevice.numInputChannels)
console.log("rnboDevice.numOutputChannels = "+ rnboDevice.numOutputChannels)
@ -157,12 +151,20 @@ console.log("Alle Ins gesetzt")
console.log("rnboDevice.node.numberOfOutputs = "+ rnboDevice.node.numberOfOutputs)
document.body.appendChild(ui)
updateRNBOParameters(events, [
{ tag: 'in7', value: 5000 },
{ tag: 'in8', value: 125 },
{ tag: 'in9', value: 2.5 },
{ tag: 'in10', value: 25 },
{ tag: 'in11', value: 30 },
{ tag: 'in12', value: 0.5623 },
{ tag: 'in13', value: -12. },
])
}
init() // hey ho lets go
// resume only after interaction inside the iframe (mobile safe)
window.addEventListener('click', () => getAudioContext().resume(), { once: true })
console.log("Await unlock context")
@ -301,14 +303,13 @@ async function initMicrophone(ctx:AudioContext) {
}
const availableDevices = await updateAvailableDevices()
console.log("MIKROFONE ", {availableDevices})
const preferredDevice = availableDevices[3]
const deviceJabra = "fdab2256f4654ac7c3fb7c7f334d88a6e468cf155fb00bb4c33d0f2f2479f653"
const externesMic = "276fc63a34034d08b7d5957cda539d268d20598e1708932112de199d85cfed1f"
try {
console.log("prefered device !!! ")
const constraints: MediaStreamConstraints = {
audio: { deviceId: { exact: "276fc63a34034d08b7d5957cda539d268d20598e1708932112de199d85cfed1f" }, echoCancellation: false, noiseSuppression: false,
autoGainControl: false },
audio: { echoCancellation: false,
noiseSuppression: false,
autoGainControl: true },
video: false
}
const stream = await navigator.mediaDevices.getUserMedia(
@ -316,6 +317,7 @@ async function initMicrophone(ctx:AudioContext) {
)
const source = ctx.createMediaStreamSource(stream)
createNodeInspector("MIKROFON", source)
microphone = source
console.log("SOURCE MICROFON", {source})
} catch (error) {