From 6f9bcc53dc43c3166564e0eca0c3ccae20a871ee Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 30 Mar 2025 16:33:47 -0400 Subject: [PATCH] ramp cut audio node --- packages/superdough/helpers.mjs | 1 + packages/superdough/sampler.mjs | 4 ++- packages/superdough/superdough.mjs | 45 ++++++++++++++++-------------- packages/superdough/synth.mjs | 30 +++++++++++++------- packages/superdough/worklets.mjs | 3 ++ 5 files changed, 51 insertions(+), 32 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index e51156fe..dfa8660f 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -212,6 +212,7 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) { constantNode.onended = () => { onComplete(); }; + return constantNode } const mod = (freq, range = 1, type = 'sine') => { const ctx = getAudioContext(); diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index 45f6b33e..f8eddffc 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -345,7 +345,9 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { }; let envEnd = holdEnd + release + 0.01; bufferSource.stop(envEnd); - const stop = (endTime, playWholeBuffer) => {}; + const stop = (endTime) => { + bufferSource.stop(endTime) + }; const handle = { node: out, bufferSource, stop }; // cut groups diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index c306f54e..3cb5349a 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -379,7 +379,7 @@ export function resetGlobalEffects() { analysersData = {}; } -let allAudioNodeChains = new Map(); +let activeSoundSources = new Map(); export const superdough = async (value, t, hapDuration) => { const ac = getAudioContext(); @@ -480,30 +480,24 @@ export const superdough = async (value, t, hapDuration) => { gain = nanFallback(gain, 1); - const chainID = Math.round(Math.random() * 10000); + const chainID = Math.round(Math.random() * 1000000); - // oldest audio nodes will be removed if maximum polyphony is exceeded - for (let i = 0; i <= allAudioNodeChains.size - maxPolyphony; i++) { - const ch = allAudioNodeChains.entries().next(); - const key = ch.value[0]; - const chain = ch.value[1]; - if (key == null) { - continue; - } - chain?.forEach((node) => node?.disconnect()); - allAudioNodeChains.delete(key); + // oldest audio nodes will be destroyed if maximum polyphony is exceeded + for (let i = 0; i <= activeSoundSources.size - maxPolyphony; i++) { + const ch = activeSoundSources.entries().next(); + const source = ch.value[1]; + const chainID = ch.value[0]; + const endTime = t + .25; + source?.node?.gain?.linearRampToValueAtTime(0, endTime); + source?.stop?.(endTime); + activeSoundSources.delete(chainID); } //music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); - gain *= velocity; // velocity currently only multiplies with gain. it might do other things in the future + let audioNodes = []; - const onended = () => { - const chain = allAudioNodeChains.get(chainID); - chain?.forEach((n) => n?.disconnect()); - allAudioNodeChains.delete(chainID); - }; if (bank && s) { s = `${bank}_${s}`; value.s = s; @@ -515,10 +509,15 @@ export const superdough = async (value, t, hapDuration) => { sourceNode = source(t, value, hapDuration); } else if (getSound(s)) { const { onTrigger } = getSound(s); - const soundHandle = await onTrigger(t, value, onended); + const onEnded = () => { + audioNodes.forEach((n) => n?.disconnect()); + activeSoundSources.delete(chainID); + }; + const soundHandle = await onTrigger(t, value, onEnded); + if (soundHandle) { sourceNode = soundHandle.node; - soundHandle.stop(t + hapDuration); + activeSoundSources.set(chainID, soundHandle); } } else { throw new Error(`sound ${s} not found! Is it loaded?`); @@ -648,6 +647,7 @@ export const superdough = async (value, t, hapDuration) => { if (delay > 0 && delaytime > 0 && delayfeedback > 0) { const delyNode = getDelay(orbit, delaytime, delayfeedback, t); delaySend = effectSend(post, delyNode, delay); + audioNodes.push(delaySend); } // reverb let reverbSend; @@ -665,6 +665,7 @@ export const superdough = async (value, t, hapDuration) => { } const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR); reverbSend = effectSend(post, reverbNode, room); + audioNodes.push(reverbSend); } // analyser @@ -672,11 +673,13 @@ export const superdough = async (value, t, hapDuration) => { if (analyze) { const analyserNode = getAnalyserById(analyze, 2 ** (fft + 5)); analyserSend = effectSend(post, analyserNode, 1); + audioNodes.push(analyserSend); } // connect chain elements together chain.slice(1).reduce((last, current) => last.connect(current), chain[0]); - allAudioNodeChains.set(chainID, [...chain, delaySend, reverbSend, analyserSend]); + audioNodes = audioNodes.concat(chain); + // activeSoundSources.set(chainID, [...chain, delaySend, reverbSend, analyserSend].filter(Boolean)); }; export const superdoughTrigger = (t, hap, ct, cps) => { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 86c073d0..f40b40c6 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -63,7 +63,9 @@ export function registerSynthSounds() { stop(envEnd); return { node, - stop: (releaseTime) => {}, + stop: (endTime) => { + stop(endTime); + }, }; }, { type: 'synth', prebake: true }, @@ -110,7 +112,9 @@ export function registerSynthSounds() { let envGain = gainNode(1); envGain = o.connect(envGain); - webAudioTimeout( + getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear'); + + let timeoutNode = webAudioTimeout( ac, () => { o.disconnect(); @@ -123,11 +127,11 @@ export function registerSynthSounds() { end, ); - getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear'); - return { node: envGain, - stop: (time) => {}, + stop: (time) => { + timeoutNode.stop(time); + }, }; }, { prebake: true, type: 'synth' }, @@ -169,7 +173,10 @@ export function registerSynthSounds() { let envGain = gainNode(1); envGain = o.connect(envGain); - webAudioTimeout( + + getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); + + let timeoutNode = webAudioTimeout( ac, () => { o.disconnect(); @@ -182,11 +189,11 @@ export function registerSynthSounds() { end, ); - getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); - return { node: envGain, - stop: (time) => {}, + stop: (time) => { + timeoutNode.stop(time); + }, }; }, { prebake: true, type: 'synth' }, @@ -229,7 +236,9 @@ export function registerSynthSounds() { stop(envEnd); return { node, - stop: (releaseTime) => {}, + stop: (endTime) => { + stop(endTime) + }, }; }, { type: 'synth', prebake: true }, @@ -299,6 +308,7 @@ export function getOscillator(s, t, value) { return { node: noiseMix?.node || o, stop: (time) => { + // console.info(time) fmModulator.stop(time); vibratoOscillator?.stop(time); noiseMix?.stop(time); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 61d5d96e..286d8c03 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -663,6 +663,9 @@ registerProcessor('phase-vocoder-processor', PhaseVocoderProcessor); class PulseOscillatorProcessor extends AudioWorkletProcessor { constructor() { super(); + // this.port.onmessage = (event) => { + // console.info(event) + // }; this.pi = _PI; this.phi = -this.pi; // phase this.Y0 = 0; // feedback memories