diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 6bf0c9327..c7ac893f7 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -48,6 +48,7 @@ export const claimVoice = (key) => { const ref = pool.pop(); node = ref?.deref?.(); } + node?.reset(); return node; }; diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 3fb41ec38..1d2e26c27 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -72,7 +72,9 @@ export function registerSynthSounds() { stop(envEnd); return { node, - stop, + stop: (endTime) => { + stop(endTime); + }, }; }, { type: 'synth', prebake: true }, @@ -139,7 +141,9 @@ export function registerSynthSounds() { return { node, - stop: o.stop, + stop: (endTime) => { + o.stop(endTime); + }, }; }, { type: 'synth', prebake: true }, @@ -210,7 +214,9 @@ export function registerSynthSounds() { return { node: o, - stop: timeoutNode.stop, + stop: (time) => { + timeoutNode.stop(time); + }, }; }, { prebake: true, type: 'synth' }, @@ -284,7 +290,9 @@ export function registerSynthSounds() { return { node: envGain, - stop: timeoutNode.stop, + stop: (time) => { + timeoutNode.stop(time); + }, }; }, { prebake: true, type: 'synth' }, @@ -359,7 +367,9 @@ export function registerSynthSounds() { return { node: envGain, - stop: timeoutNode.stop, + stop: (time) => { + timeoutNode.stop(time); + }, }; }, { prebake: true, type: 'synth' }, @@ -402,7 +412,9 @@ export function registerSynthSounds() { stop(envEnd); return { node, - stop, + stop: (endTime) => { + stop(endTime); + }, }; }, { type: 'synth', prebake: true }, diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index 9ab469379..3bfa4ac4d 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -334,6 +334,8 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { t, envEnd, ); - handle.stop = timeoutNode.stop; + handle.stop = (time) => { + timeoutNode.stop(time); + }; return handle; } diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 7e337ac52..fba52a554 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -26,7 +26,7 @@ const ffrac = (x) => x - ffloor(x); const fast_tanh = (x) => { const x2 = x * x; - return (x * (27.0 + x2)) / (27.0 + 9.0 * x2); + return (x * (27 + x2)) / (27 + 9 * x2); }; const fastexpm1 = (x) => x * (1 + x + 0.5 * x * x); // Taylor approximation @@ -40,10 +40,21 @@ const getDetuner = (unison, detune) => { return (voiceIdx) => voiceIdx * scale - center; }; +function fastPow2(x) { + // Taylor approximation of 2 ^ x + const a = x * 0.6931471805599453; // ln(2) + return 1 + a * (1 + a * (0.8333333333333333 + a)); +} + const applySemitoneDetuneToFrequency = (frequency, detune) => { return frequency * Math.pow(2, detune / 12); }; +const applyFastDetune = (frequency, detune) => { + if (detune < 4) return frequency * fastPow2(detune / 12); + return applySemitoneDetuneToFrequency(frequency, detune); +}; + // Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing // referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517 function polyBlep(phase, dt) { @@ -475,14 +486,12 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { max: Number.POSITIVE_INFINITY, min: 0, }, - { name: 'end', defaultValue: 0, max: Number.POSITIVE_INFINITY, min: 0, }, - { name: 'frequency', defaultValue: 440, @@ -520,7 +529,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { process(_input, outputs, params) { const started = currentTime >= params.begin[0]; const end = params.end[0]; - const ended = currentTime >= end + 0.25; + const ended = currentTime >= end + 1; const inGracePeriod = (currentTime >= end) && !ended; if (started|| inGracePeriod) { return true; @@ -558,7 +567,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { detuner ??= getDetuner(voices, freqspread); for (let n = 0; n < voices; n++) { // Individual voice detuning - const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n)); + const freqVoice = applyFastDetune(freq, detuner(n)); // We must wrap this here because it is passed into sawblep below which // has domain [0, 1] const dt = frac(freqVoice * INVSR); @@ -1350,7 +1359,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { process(_inputs, outputs, params) { const started = currentTime >= params.begin[0]; const end = params.end[0]; - const ended = currentTime >= end + 0.25; + const ended = currentTime >= end + 1; const inGracePeriod = (currentTime >= end) && !ended; if (!this.tables || started || inGracePeriod) { return true; @@ -1395,7 +1404,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { freq ??= applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100); // overall detune detuner ??= getDetuner(voices, freqspread); for (let n = 0; n < voices; n++) { - const fVoice = applySemitoneDetuneToFrequency(f, detuner(n)); // voice detune + const fVoice = applyFastDetune(f, detuner(n)); // voice detune const dPhase = fVoice * INVSR; const level = this._chooseMip(dPhase); const table = this.tables[level];