import { createDevice, type Device } from '@rnbo/js' import { attachDBValueListener } from './dbValueListener' import { fetchOrLoadBuffer, createBufferSource } from './composables/BufferStore' import { setupPlayButton } from './composables/Player'; import { getPreferredDevice } from './lib/microphone'; import patcherUrl from '../public/patches/controlvalue/LAF-Controll-Values_Simple_Band1000.rnbopat.export.json?url' interface DeviceInfo { id: number; name: string; device: Device; audioNode: AudioNode; } // RNBO // RNBO let patcherPromise: Promise // create context & master gain let WAContext = window.AudioContext || window.webkitAudioContext const ctx = new WAContext() // match your assets const mobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent) console.log("AudioContext created") // // Gains to control the audio // const masterGain = ctx.createGain() const bands = mobile ? [150, 1500, 8000] : [63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000] const devices = new Map() const sources = new Map() const gains = new Map() // Audio Files const urlsWebM = mobile ? [ { name: bands[0], url: 'audio/masking/3bands/low_band_256kbps.webm' }, { name: bands[1], url: 'audio/masking/3bands/mid_band_256kbps.webm' }, { name: bands[2], url: 'audio/masking/3bands/high_band_256kbps.webm' }, ] : [ { name: bands[0], url: 'audio/masking/9bands/Quellrauschen63.webm' }, { name: bands[1], url: 'audio/masking/9bands/Quellrauschen125.webm' }, { name: bands[2], url: 'audio/masking/9bands/Quellrauschen250.webm' }, { name: bands[3], url: 'audio/masking/9bands/Quellrauschen500.webm' }, { name: bands[4], url: 'audio/masking/9bands/Quellrauschen1000.webm' }, { name: bands[5], url: 'audio/masking/9bands/Quellrauschen2000.webm' }, { name: bands[6], url: 'audio/masking/9bands/Quellrauschen4000.webm' }, { name: bands[7], url: 'audio/masking/9bands/Quellrauschen8000.webm' }, { name: bands[8], url: 'audio/masking/9bands/Quellrauschen16000.webm' }, ] async function getPatcher() { if (!patcherPromise) patcherPromise = fetch(patcherUrl).then(r => r.json()) return patcherPromise } async function init() { await getPatcher() const newDevices = await Promise.all( bands.map(async (freq, index) => { const device = await createBandDevice(freq, ctx) as DeviceInfo await fetchOrLoadBuffer(freq.toString(), urlsWebM[index].url, ctx ) const sourceNode = await createBufferSource(freq.toString(),ctx) const bandGainNode = ctx.createGain() // patch them together sourceNode.node?.connect(bandGainNode) bandGainNode.connect(masterGain) //update the source list if(sourceNode.node instanceof AudioBufferSourceNode && bandGainNode instanceof GainNode ) { sources.set(freq, sourceNode.node) gains.set(freq, bandGainNode) } return {freq, device} } )) newDevices.forEach(({freq, device}) => { devices.set(freq, device) }) console.log("RNBO Devices created") await initMicrophone() devices.forEach((devices, freq)=> { try{ if(devices && devices.device) { attachDBValueListener(devices.device,freq) } }catch { console.warn("couldnt attach rnbo listener") } }) console.log("Everything is setup, display a play button") setupPlayButton(sources, gains,ctx) masterGain.connect(ctx.destination) } /** * Enables the Microphone to use it. */ async function initMicrophone() { try { const preferredDevice = await getPreferredDevice() console.log("INIT MIC", { preferredDevice}) if(preferredDevice){ console.log("prefered device !!! ") const constraints: MediaStreamConstraints = { audio: { deviceId: { exact: preferredDevice.deviceId }, echoCancellation: false, noiseSuppression: false, autoGainControl: false }, video: false } const stream = await navigator.mediaDevices.getUserMedia( constraints ) const source = ctx.createMediaStreamSource(stream) console.log("SOURCE MICROFON", {source}) const splitter = ctx.createChannelSplitter(source.channelCount) try { source.connect(splitter) devices.forEach((device, freq) => { splitter.connect(device.audioNode, 0, 0) console.log('🎤 Mikrofon mono-signal wurde an RNBO-Device mit Freq: '+freq+' geroutet.') }) } catch (error) { console.error("Error!: connecting the microphone to deivces... connected") } }else { console.log("Das Gerät wurde nicht gefunden. Lieber nochmal den Mikrofon-Zugriff checken") } } catch (err) { console.error('❌ Fehler beim Zugriff auf Mikrofon:', err) } } /** * This function creates a RNBODevice with a given frequency. If the microphone is connected * this devices sends out control Values as messages. * @param freq * @param ctx * @returns RNBO Device */ async function createBandDevice(freq: number, ctx: AudioContext): Promise { const patcher = await getPatcher() const rnboDevice = await createDevice({ context: ctx, patcher }) const param = rnboDevice.parametersById.get('centerFrequency') param.value = freq console.log("Device created for "+ freq) return { id: freq, name: ""+freq, device: rnboDevice, audioNode: rnboDevice.node } } init() // hey ho lets go // resume only after interaction inside the iframe (mobile safe) window.addEventListener('click', () => ctx.resume(), { once: true }) console.log("Await unlock context")