From 798eb22d9a5a5be342b2e78a34f845a3c748d184 Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 6 Sep 2025 10:09:44 -0500 Subject: [PATCH] Use currentTime for exact scheduling --- packages/core/controls.mjs | 8 +-- packages/superdough/superdough.mjs | 21 ++++---- test/__snapshots__/examples.test.mjs.snap | 64 +++++++++++------------ 3 files changed, 47 insertions(+), 46 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 589a0634f..af0c4b1ab 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -590,12 +590,12 @@ export const { duckdepth } = registerControl('duckdepth'); * @param {number | Pattern} time The onset time in seconds * @example * // Clicks - * sound: n(run(8)).scale("c:minor").s("sawtooth").lpf(200).delay(.7).orbit(2) - * duckerWithClick: s("bd*4").duckorbit(2).duckonset(0).postgain(0) + * sound: freq("63.2388").s("sine").orbit(2).gain(4) + * duckerWithClick: s("bd*4").duckorbit(2).duckattack(0.3).duckonset(0).postgain(0) * @example * // No clicks - * sound: n(run(8)).scale("c:minor").s("sawtooth").lpf(200).delay(.7).orbit(2) - * duckerWithoutClick: s("bd*4").duckorbit(2).duckonset(0.003).postgain(0) + * sound: freq("63.2388").s("sine").orbit(2).gain(4) + * duckerWithoutClick: s("bd*4").duckorbit(2).duckattack(0.3).duckonset(0.01).postgain(0) * @example * // Rhythmic * noise: s("pink").distort("2:1").orbit(4) // used rhythmically with 0.3 onset below diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 5e5969fed..59590db6d 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -420,7 +420,7 @@ function setOrbit(audioContext, orbit, channels) { } } -function duckOrbit(audioContext, targetOrbit, t, onsettime = 0.003, attacktime = 0.1, duckdepth = 1) { +function duckOrbit(audioContext, targetOrbit, t, onsettime = 0, attacktime = 0.1, duckdepth = 1) { const targetArr = [targetOrbit].flat(); const onsetArr = [onsettime].flat(); const attackArr = [attacktime].flat(); @@ -438,17 +438,18 @@ function duckOrbit(audioContext, targetOrbit, t, onsettime = 0.003, attacktime = webAudioTimeout( audioContext, () => { - gainParam.cancelScheduledValues(t); + const now = audioContext.currentTime; + + // cancelScheduledValues and setValueAtTime together emulate cancelAndHoldAtTime + // on browsers which lack that method const currVal = gainParam.value; + gainParam.cancelScheduledValues(now); + gainParam.setValueAtTime(currVal, now); + + const t0 = Math.max(t, now); // guard against now > t const duckedVal = clamp(1 - Math.sqrt(depth), 0.01, currVal); - - // Guarantees the value is set to currVal at time t. This in conjunction with - // cancelScheduledValues above emulates cancelAndHoldAtTime on browsers which lack - // that method - gainParam.setValueAtTime(currVal, t); - - gainParam.exponentialRampToValueAtTime(duckedVal, t + onset); - gainParam.exponentialRampToValueAtTime(1, t + onset + attack); + gainParam.exponentialRampToValueAtTime(duckedVal, t0 + onset); + gainParam.exponentialRampToValueAtTime(1, t0 + onset + attack); }, 0, t - 0.01, diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index ba54328b7..7579794fd 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -3170,43 +3170,43 @@ exports[`runs examples > example "duckdepth" example index 1 1`] = ` exports[`runs examples > example "duckonset" example index 0 1`] = ` [ - "[ 0/1 → 1/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 1/4 → 1/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 1/2 → 3/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 3/4 → 1/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 1/1 → 5/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 5/4 → 3/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 3/2 → 7/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 7/4 → 2/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 2/1 → 9/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 9/4 → 5/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 5/2 → 11/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 11/4 → 3/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 3/1 → 13/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 13/4 → 7/2 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 7/2 → 15/4 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", - "[ 15/4 → 4/1 | s:bd duckorbit:2 duckonset:0 postgain:0 ]", + "[ 0/1 → 1/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 1/4 → 1/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 1/2 → 3/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 3/4 → 1/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 1/1 → 5/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 5/4 → 3/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 3/2 → 7/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 7/4 → 2/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 2/1 → 9/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 9/4 → 5/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 5/2 → 11/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 11/4 → 3/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 3/1 → 13/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 13/4 → 7/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 7/2 → 15/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", + "[ 15/4 → 4/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]", ] `; exports[`runs examples > example "duckonset" example index 1 1`] = ` [ - "[ 0/1 → 1/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 1/4 → 1/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 1/2 → 3/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 3/4 → 1/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 1/1 → 5/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 5/4 → 3/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 3/2 → 7/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 7/4 → 2/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 2/1 → 9/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 9/4 → 5/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 5/2 → 11/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 11/4 → 3/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 3/1 → 13/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 13/4 → 7/2 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 7/2 → 15/4 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", - "[ 15/4 → 4/1 | s:bd duckorbit:2 duckonset:0.003 postgain:0 ]", + "[ 0/1 → 1/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 1/4 → 1/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 1/2 → 3/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 3/4 → 1/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 1/1 → 5/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 5/4 → 3/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 3/2 → 7/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 7/4 → 2/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 2/1 → 9/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 9/4 → 5/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 5/2 → 11/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 11/4 → 3/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 3/1 → 13/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 13/4 → 7/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 7/2 → 15/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", + "[ 15/4 → 4/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]", ] `;