diff --git a/packages/soundfonts/fontloader.mjs b/packages/soundfonts/fontloader.mjs index ca9e45d51..95e210890 100644 --- a/packages/soundfonts/fontloader.mjs +++ b/packages/soundfonts/fontloader.mjs @@ -7,6 +7,7 @@ import { getPitchEnvelope, getVibratoOscillator, onceEnded, + releaseAudioNode, } from '@strudel/webaudio'; import gm from './gm.mjs'; @@ -172,9 +173,8 @@ export function registerSoundfonts() { bufferSource.stop(envEnd); const stop = (releaseTime) => {}; onceEnded(bufferSource, () => { - bufferSource.disconnect(); - vibratoOscillator?.stop(); - node.disconnect(); + releaseAudioNode(bufferSource); + releaseAudioNode(vibratoOscillator); onended(); }); return { node, stop }; diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 0b696df9b..5abc2bad1 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -189,7 +189,7 @@ export function applyParameterModulators(audioContext, param, start, end, envelo getParamADSR(param, attack, decay, sustain, release, min, max, start, holdEnd, curve); } const lfo = getParamLfo(audioContext, param, start, end, lfoValues); - return { lfo, disconnect: () => lfo?.disconnect() }; + return lfo; } export function createFilter(context, start, end, params, cps, cycle) { let { @@ -283,9 +283,12 @@ export function drywet(dry, wet, wetAmount = 0) { wet_gain.connect(mix); return { node: mix, - onended: () => { - dry_gain.disconnect(mix); - wet_gain.disconnect(mix); + teardown: () => { + releaseAudioNode(dry_gain); + releaseAudioNode(wet_gain); + // it is not the responsability of drywet + // to call `releaseAudioNode` on + // the 2 external args dry and wet dry.disconnect(dry_gain); wet.disconnect(wet_gain); }, @@ -324,10 +327,10 @@ export function getVibratoOscillator(param, value, t) { gain.gain.value = vibmod * 100; vibratoOscillator.connect(gain); gain.connect(param); - vibratoOscillator.onended = () => { - gain.disconnect(param); - vibratoOscillator.disconnect(gain); - }; + onceEnded(vibratoOscillator, () => { + releaseAudioNode(gain); + releaseAudioNode(vibratoOscillator); + }); vibratoOscillator.start(t); return vibratoOscillator; } @@ -613,6 +616,8 @@ export const releaseAudioNode = (node) => { // returns true and either its active source flag is true or // any AudioNode connected to one of its inputs is actively processing. if (node instanceof AudioWorkletNode) { + // while `end` is not native to the web audio API, it is common practice in superdough + // to use that param in the worklets to trigger returning false from the processor node.parameters.get('end')?.setValueAtTime(0, 0); } }; diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index 3e41515df..52d9306a1 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -1,4 +1,4 @@ -import { drywet } from './helpers.mjs'; +import { drywet, onceEnded, releaseAudioNode } from './helpers.mjs'; import { getAudioContext } from './audioContext.mjs'; let noiseCache = {}; @@ -65,9 +65,12 @@ export function getNoiseOscillator(type = 'white', t, density = 0.02) { export function getNoiseMix(inputNode, wet, t) { const noiseOscillator = getNoiseOscillator('pink', t); const noiseMix = drywet(inputNode, noiseOscillator.node, wet); - noiseOscillator.node.onended = () => noiseMix.onended(); + onceEnded(noiseOscillator.node, () => { + releaseAudioNode(noiseOscillator.node); + }); return { node: noiseMix.node, stop: (time) => noiseOscillator?.stop(time), + teardown: noiseMix.teardown, }; } diff --git a/packages/superdough/sampler.mjs b/packages/superdough/sampler.mjs index bdf524d10..2deeb4b74 100644 --- a/packages/superdough/sampler.mjs +++ b/packages/superdough/sampler.mjs @@ -323,10 +323,10 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) { const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox... node.connect(out); onceEnded(bufferSource, function () { - bufferSource.disconnect(); - vibratoOscillator?.stop(); - node.disconnect(); - out.disconnect(); + releaseAudioNode(bufferSource); + releaseAudioNode(vibratoOscillator); + releaseAudioNode(node); + releaseAudioNode(out); onended(); }); let envEnd = holdEnd + release + 0.01; diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index b97865918..6891c9408 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -9,7 +9,16 @@ import './reverb.mjs'; import './vowel.mjs'; import { nanFallback, _mod, cycleToSeconds, pickAndRename } from './util.mjs'; import workletsUrl from './worklets.mjs?audioworklet'; -import { createFilter, gainNode, getCompressor, getDistortion, getLfo, getWorklet, effectSend } from './helpers.mjs'; +import { + createFilter, + gainNode, + getCompressor, + getDistortion, + getLfo, + getWorklet, + effectSend, + releaseAudioNode, +} from './helpers.mjs'; import { map } from 'nanostores'; import { logger } from './logger.mjs'; import { loadBuffer } from './sampler.mjs'; @@ -505,7 +514,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) } else if (getSound(s)) { const { onTrigger } = getSound(s); const onEnded = () => { - audioNodes.forEach((n) => n?.disconnect()); + audioNodes.forEach((n) => releaseAudioNode(n)); activeSoundSources.delete(chainID); }; const soundHandle = await onTrigger(t, value, onEnded, cps); diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index b02354465..d9ee0d936 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -12,6 +12,7 @@ import { getVibratoOscillator, getWorklet, noises, + onceEnded, releaseAudioNode, webAudioTimeout, } from './helpers.mjs'; @@ -52,7 +53,7 @@ export function registerSynthSounds() { const g = gainNode(0.3); let sound = getOscillator(s, t, value, () => { - g.disconnect(); + releaseAudioNode(g); onended(); }); @@ -110,15 +111,15 @@ export function registerSynthSounds() { const mix = gainNode(mixGain); - o.onended = () => { - o.disconnect(); - g.disconnect(); - sat.disconnect(); - noise.node.disconnect(); - noiseGain.disconnect(); - mix.disconnect(); + onceEnded(o, () => { + releaseAudioNode(o); + releaseAudioNode(g); + releaseAudioNode(sat); + releaseAudioNode(noise.node); + releaseAudioNode(noiseGain); + releaseAudioNode(mix); onended(); - }; + }); const node = o.connect(sat).connect(g).connect(mix); noise.node.connect(noiseGain).connect(mix); @@ -384,11 +385,11 @@ export function registerSynthSounds() { const { duration } = value; - o.onended = () => { - o.disconnect(); - g.disconnect(); + onceEnded(o, () => { + releaseAudioNode(o); + releaseAudioNode(g); onended(); - }; + }); const envGain = gainNode(1); let node = o.connect(g).connect(envGain); @@ -489,11 +490,12 @@ export function getOscillator(s, t, value, onended) { noiseMix = getNoiseMix(o, noise, t); } - o.onended = () => { - noiseMix || o.disconnect(); - noiseMix?.node.disconnect(); + onceEnded(o, () => { + noiseMix?.teardown(); + releaseAudioNode(o); + releaseAudioNode(noiseMix?.node); onended(); - }; + }); o.start(t); return { diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index e9d810414..4311c5cab 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -320,10 +320,10 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { ac, () => { releaseAudioNode(source); - vibratoOscillator?.stop(); + releaseAudioNode(vibratoOscillator); fm?.stop(); - wtPosModulators?.disconnect(); - wtWarpModulators?.disconnect(); + releaseAudioNode(wtPosModulators); + releaseAudioNode(wtWarpModulators); onended(); }, t,