From bfa8fa3c757b2b457ee6aa5ef1a55f8bf14902e2 Mon Sep 17 00:00:00 2001 From: jeromew Date: Sun, 16 Nov 2025 09:23:11 +0000 Subject: [PATCH 1/8] [perf] fix `connect leak` when .noise() is in the mix --- packages/superdough/helpers.mjs | 14 +++++++++++++- packages/superdough/noise.mjs | 3 ++- packages/superdough/synth.mjs | 23 +++++++++++++---------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 47161ed61..de64df0e8 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -262,7 +262,15 @@ export function drywet(dry, wet, wetAmount = 0) { let mix = ac.createGain(); dry_gain.connect(mix); wet_gain.connect(mix); - return mix; + return { + node: mix, + onended: () => { + dry_gain.disconnect(mix); + wet_gain.disconnect(mix); + dry.disconnect(dry_gain); + wet.disconnect(wet_gain); + }, + }; } let curves = ['linear', 'exponential']; @@ -297,6 +305,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); + }; vibratoOscillator.start(t); return vibratoOscillator; } diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index 816dd252b..3e41515df 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -65,8 +65,9 @@ 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(); return { - node: noiseMix, + node: noiseMix.node, stop: (time) => noiseOscillator?.stop(time), }; } diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index e35b98806..a9ec7975c 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -47,19 +47,17 @@ export function registerSynthSounds() { [0.001, 0.05, 0.6, 0.01], ); - let sound = getOscillator(s, t, value); - let { node: o, stop, triggerRelease } = sound; - // turn down const g = gainNode(0.3); - const { duration } = value; - - o.onended = () => { - o.disconnect(); + let sound = getOscillator(s, t, value, () => { g.disconnect(); onended(); - }; + }); + + let { node: o, stop, triggerRelease } = sound; + + const { duration } = value; const envGain = gainNode(1); let node = o.connect(g).connect(envGain); @@ -446,7 +444,7 @@ export function waveformN(partials, type) { } // expects one of waveforms as s -export function getOscillator(s, t, value) { +export function getOscillator(s, t, value, onended) { let { n: partials, duration, noise = 0 } = value; let o; // If no partials are given, use stock waveforms @@ -460,7 +458,6 @@ export function getOscillator(s, t, value) { } // set frequency o.frequency.value = getFrequencyFromValue(value); - o.start(t); let vibratoOscillator = getVibratoOscillator(o.detune, value, t); @@ -473,6 +470,12 @@ export function getOscillator(s, t, value) { noiseMix = getNoiseMix(o, noise, t); } + o.onended = () => { + noiseMix?.node.disconnect(); + onended(); + }; + o.start(t); + return { node: noiseMix?.node || o, stop: (time) => { From 30cc46ea66a4cd3d9b5b7dca3ab6446fc76d6845 Mon Sep 17 00:00:00 2001 From: scrappy_fiddler Date: Mon, 17 Nov 2025 20:04:40 +0100 Subject: [PATCH 2/8] wchooseCycles has now notes in an example --- packages/core/signal.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs index 604d1c805..62fc26ad8 100644 --- a/packages/core/signal.mjs +++ b/packages/core/signal.mjs @@ -524,7 +524,7 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs); * @example * wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8) * @example - * wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s() + * wchooseCycles(["c c c",5], ["a a a",3], ["f f f",1]).fast(4).note() * @example * // The probability can itself be a pattern * wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s() From 99beb4454deb9099a40f355ba4dc3898f341adbe Mon Sep 17 00:00:00 2001 From: scrappy_fiddler Date: Mon, 17 Nov 2025 20:49:26 +0100 Subject: [PATCH 3/8] update snapshot with note example --- test/__snapshots__/examples.test.mjs.snap | 96 +++++++++++------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 32e61c31d..31e4bddd3 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -12484,54 +12484,54 @@ exports[`runs examples > example "wchooseCycles" example index 0 1`] = ` exports[`runs examples > example "wchooseCycles" example index 1 1`] = ` [ - "[ 0/1 → 1/12 | s:bd ]", - "[ 1/12 → 1/6 | s:bd ]", - "[ 1/6 → 1/4 | s:bd ]", - "[ 1/4 → 1/3 | s:bd ]", - "[ 1/3 → 5/12 | s:bd ]", - "[ 5/12 → 1/2 | s:bd ]", - "[ 1/2 → 7/12 | s:sd ]", - "[ 7/12 → 2/3 | s:sd ]", - "[ 2/3 → 3/4 | s:sd ]", - "[ 3/4 → 5/6 | s:bd ]", - "[ 5/6 → 11/12 | s:bd ]", - "[ 11/12 → 1/1 | s:bd ]", - "[ 1/1 → 13/12 | s:bd ]", - "[ 13/12 → 7/6 | s:bd ]", - "[ 7/6 → 5/4 | s:bd ]", - "[ 5/4 → 4/3 | s:bd ]", - "[ 4/3 → 17/12 | s:bd ]", - "[ 17/12 → 3/2 | s:bd ]", - "[ 3/2 → 19/12 | s:hh ]", - "[ 19/12 → 5/3 | s:hh ]", - "[ 5/3 → 7/4 | s:hh ]", - "[ 7/4 → 11/6 | s:bd ]", - "[ 11/6 → 23/12 | s:bd ]", - "[ 23/12 → 2/1 | s:bd ]", - "[ 2/1 → 25/12 | s:hh ]", - "[ 25/12 → 13/6 | s:hh ]", - "[ 13/6 → 9/4 | s:hh ]", - "[ 9/4 → 7/3 | s:hh ]", - "[ 7/3 → 29/12 | s:hh ]", - "[ 29/12 → 5/2 | s:hh ]", - "[ 5/2 → 31/12 | s:bd ]", - "[ 31/12 → 8/3 | s:bd ]", - "[ 8/3 → 11/4 | s:bd ]", - "[ 11/4 → 17/6 | s:bd ]", - "[ 17/6 → 35/12 | s:bd ]", - "[ 35/12 → 3/1 | s:bd ]", - "[ 3/1 → 37/12 | s:bd ]", - "[ 37/12 → 19/6 | s:bd ]", - "[ 19/6 → 13/4 | s:bd ]", - "[ 13/4 → 10/3 | s:sd ]", - "[ 10/3 → 41/12 | s:sd ]", - "[ 41/12 → 7/2 | s:sd ]", - "[ 7/2 → 43/12 | s:hh ]", - "[ 43/12 → 11/3 | s:hh ]", - "[ 11/3 → 15/4 | s:hh ]", - "[ 15/4 → 23/6 | s:bd ]", - "[ 23/6 → 47/12 | s:bd ]", - "[ 47/12 → 4/1 | s:bd ]", + "[ 0/1 → 1/12 | note:c ]", + "[ 1/12 → 1/6 | note:c ]", + "[ 1/6 → 1/4 | note:c ]", + "[ 1/4 → 1/3 | note:c ]", + "[ 1/3 → 5/12 | note:c ]", + "[ 5/12 → 1/2 | note:c ]", + "[ 1/2 → 7/12 | note:f ]", + "[ 7/12 → 2/3 | note:f ]", + "[ 2/3 → 3/4 | note:f ]", + "[ 3/4 → 5/6 | note:c ]", + "[ 5/6 → 11/12 | note:c ]", + "[ 11/12 → 1/1 | note:c ]", + "[ 1/1 → 13/12 | note:c ]", + "[ 13/12 → 7/6 | note:c ]", + "[ 7/6 → 5/4 | note:c ]", + "[ 5/4 → 4/3 | note:c ]", + "[ 4/3 → 17/12 | note:c ]", + "[ 17/12 → 3/2 | note:c ]", + "[ 3/2 → 19/12 | note:a ]", + "[ 19/12 → 5/3 | note:a ]", + "[ 5/3 → 7/4 | note:a ]", + "[ 7/4 → 11/6 | note:c ]", + "[ 11/6 → 23/12 | note:c ]", + "[ 23/12 → 2/1 | note:c ]", + "[ 2/1 → 25/12 | note:a ]", + "[ 25/12 → 13/6 | note:a ]", + "[ 13/6 → 9/4 | note:a ]", + "[ 9/4 → 7/3 | note:a ]", + "[ 7/3 → 29/12 | note:a ]", + "[ 29/12 → 5/2 | note:a ]", + "[ 5/2 → 31/12 | note:c ]", + "[ 31/12 → 8/3 | note:c ]", + "[ 8/3 → 11/4 | note:c ]", + "[ 11/4 → 17/6 | note:c ]", + "[ 17/6 → 35/12 | note:c ]", + "[ 35/12 → 3/1 | note:c ]", + "[ 3/1 → 37/12 | note:c ]", + "[ 37/12 → 19/6 | note:c ]", + "[ 19/6 → 13/4 | note:c ]", + "[ 13/4 → 10/3 | note:f ]", + "[ 10/3 → 41/12 | note:f ]", + "[ 41/12 → 7/2 | note:f ]", + "[ 7/2 → 43/12 | note:a ]", + "[ 43/12 → 11/3 | note:a ]", + "[ 11/3 → 15/4 | note:a ]", + "[ 15/4 → 23/6 | note:c ]", + "[ 23/6 → 47/12 | note:c ]", + "[ 47/12 → 4/1 | note:c ]", ] `; From dd2c808cd71a7f5892f7e57c0f0a6fe7540d332c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 19 Nov 2025 21:55:35 +0100 Subject: [PATCH 4/8] hotfix: reduce sounds-tab click to play latency --- website/src/repl/components/panel/SoundsTab.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/repl/components/panel/SoundsTab.jsx b/website/src/repl/components/panel/SoundsTab.jsx index a9f10bf12..3d363f8a1 100644 --- a/website/src/repl/components/panel/SoundsTab.jsx +++ b/website/src/repl/components/panel/SoundsTab.jsx @@ -126,7 +126,7 @@ export function SoundsTab() { try { // Pre-load the sample by calling onTrigger with a future time // This triggers the loading but schedules playback for later - const time = ctx.currentTime + 0.5; // Give 500ms for loading + const time = ctx.currentTime + 0.05; const ref = await onTrigger(time, params, onended); trigRef.current = ref; if (ref?.node) { From 8ed72b4573503890d04fffafee50e1f28cca9990 Mon Sep 17 00:00:00 2001 From: jeromew Date: Thu, 20 Nov 2025 13:05:41 +0000 Subject: [PATCH 5/8] [perf] fix `connect-leak` in `delay` effect --- packages/superdough/superdough.mjs | 3 ++- packages/superdough/superdoughoutput.mjs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 65c444120..c548e7396 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -692,7 +692,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) // delay if (delay > 0 && delaytime > 0 && delayfeedback > 0) { orbitBus.getDelay(delaytime, delayfeedback, t); - orbitBus.sendDelay(post, delay); + const send = orbitBus.sendDelay(post, delay); + audioNodes.push(send) } // reverb if (room > 0) { diff --git a/packages/superdough/superdoughoutput.mjs b/packages/superdough/superdoughoutput.mjs index a4235efd0..d8ead7d06 100644 --- a/packages/superdough/superdoughoutput.mjs +++ b/packages/superdough/superdoughoutput.mjs @@ -82,7 +82,7 @@ export class Orbit { } sendDelay(node, amount) { - effectSend(node, this.delayNode, amount); + return effectSend(node, this.delayNode, amount); } duck(t, onsettime = 0, attacktime = 0.1, depth = 1) { From d9f63b8dfa28112192ed5b6a2d2cfe7cb41cc41d Mon Sep 17 00:00:00 2001 From: jeromew Date: Sun, 16 Nov 2025 09:23:11 +0000 Subject: [PATCH 6/8] [perf] fix `connect leak` when .noise() is in the mix --- packages/superdough/helpers.mjs | 14 +++++++++++++- packages/superdough/noise.mjs | 3 ++- packages/superdough/synth.mjs | 23 +++++++++++++---------- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 47161ed61..de64df0e8 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -262,7 +262,15 @@ export function drywet(dry, wet, wetAmount = 0) { let mix = ac.createGain(); dry_gain.connect(mix); wet_gain.connect(mix); - return mix; + return { + node: mix, + onended: () => { + dry_gain.disconnect(mix); + wet_gain.disconnect(mix); + dry.disconnect(dry_gain); + wet.disconnect(wet_gain); + }, + }; } let curves = ['linear', 'exponential']; @@ -297,6 +305,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); + }; vibratoOscillator.start(t); return vibratoOscillator; } diff --git a/packages/superdough/noise.mjs b/packages/superdough/noise.mjs index 816dd252b..3e41515df 100644 --- a/packages/superdough/noise.mjs +++ b/packages/superdough/noise.mjs @@ -65,8 +65,9 @@ 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(); return { - node: noiseMix, + node: noiseMix.node, stop: (time) => noiseOscillator?.stop(time), }; } diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 10df1776b..2dc9e2be8 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -48,19 +48,17 @@ export function registerSynthSounds() { [0.001, 0.05, 0.6, 0.01], ); - let sound = getOscillator(s, t, value); - let { node: o, stop, triggerRelease } = sound; - // turn down const g = gainNode(0.3); - const { duration } = value; - - o.onended = () => { - o.disconnect(); + let sound = getOscillator(s, t, value, () => { g.disconnect(); onended(); - }; + }); + + let { node: o, stop, triggerRelease } = sound; + + const { duration } = value; const envGain = gainNode(1); let node = o.connect(g).connect(envGain); @@ -460,7 +458,7 @@ export function waveformN(partials, phases, type) { } // expects one of waveforms as s -export function getOscillator(s, t, value) { +export function getOscillator(s, t, value, onended) { const { duration, noise = 0 } = value; const partials = value.partials ?? value.n; let o; @@ -482,7 +480,6 @@ export function getOscillator(s, t, value) { } // set frequency o.frequency.value = getFrequencyFromValue(value); - o.start(t); let vibratoOscillator = getVibratoOscillator(o.detune, value, t); @@ -495,6 +492,12 @@ export function getOscillator(s, t, value) { noiseMix = getNoiseMix(o, noise, t); } + o.onended = () => { + noiseMix?.node.disconnect(); + onended(); + }; + o.start(t); + return { node: noiseMix?.node || o, stop: (time) => { From 7267990e20ed07c86b95a5c17985461e4f77f8d6 Mon Sep 17 00:00:00 2001 From: jeromew Date: Thu, 20 Nov 2025 13:35:37 +0000 Subject: [PATCH 7/8] Fix codeformat --- packages/superdough/superdough.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index c548e7396..650f9c2ce 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -693,7 +693,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) if (delay > 0 && delaytime > 0 && delayfeedback > 0) { orbitBus.getDelay(delaytime, delayfeedback, t); const send = orbitBus.sendDelay(post, delay); - audioNodes.push(send) + audioNodes.push(send); } // reverb if (room > 0) { From 0435711051ee5d890f15a11b8c2fdd0def231996 Mon Sep 17 00:00:00 2001 From: jeromew Date: Thu, 20 Nov 2025 17:36:00 +0000 Subject: [PATCH 8/8] [perf] fix `connect-leak` added by #1742 when noise() is not used --- packages/superdough/synth.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 2dc9e2be8..768638f04 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -493,6 +493,7 @@ export function getOscillator(s, t, value, onended) { } o.onended = () => { + o.disconnect(); noiseMix?.node.disconnect(); onended(); };