From e3741ae8faad7f1b9c160a6949c71a0ff3b03ee2 Mon Sep 17 00:00:00 2001 From: Aria Date: Thu, 25 Sep 2025 00:05:50 -0700 Subject: [PATCH] Add control over phase randomization and fix a bug with supersaw --- packages/core/controls.mjs | 10 ++++++++++ packages/superdough/superdough.mjs | 1 - packages/superdough/wavetable.mjs | 13 +++++++------ packages/superdough/worklets.mjs | 8 +++++--- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 7bf55457..89583b47 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -120,6 +120,16 @@ export const { wtWarp, wavetableWarp } = registerControl('wtWarp', 'wavetableWar */ export const { wtWarpMode, wavetableWarpMode } = registerControl('wtWarpMode', 'wavetableWarpMode'); +/** + * Amount of randomness of the initial phase of the wavetable oscillator. + * + * @name wtPhaseRand + * @param {number | Pattern} mode Warp mode: an integer + * @synonyms wavetableWarpMode + * + */ +export const { wtPhaseRand, wavetablePhaseRand } = registerControl('wtPhaseRand', 'wavetablePhaseRand'); + /** * Define a custom webaudio node to use as a sound source. * diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index b2b5c8d6..f1f308a7 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -594,7 +594,6 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) tremolophase = 0, tremoloshape, s = getDefaultValue('s'), - wt, bank, source, gain = getDefaultValue('gain'), diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index ccf31920..044b651e 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -186,7 +186,7 @@ async function onTriggerSynth(t, value, onended, bank, frameLen) { const ac = getAudioContext(); let [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]); let sourceDesc, holdEnd, envEnd; - let { unison, spread, detune, wtPos, wtWarp, wtWarpMode } = value; + let { wtWarpMode } = value; if (typeof wtWarpMode === 'string') { wtWarpMode = WarpMode[wtWarpMode.toUpperCase()] ?? WarpMode.NONE; } @@ -202,12 +202,13 @@ async function onTriggerSynth(t, value, onended, bank, frameLen) { begin: t, end: envEnd, frequency, - detune, - position: wtPos, - warp: wtWarp, + detune: value.detune, + position: value.wtPos, + warp: value.wtWarp, warpMode: wtWarpMode, - voices: unison, - spread, + voices: value.unison, + spread: value.spread, + phaserand: value.wtPhaseRand, }, { outputChannelCount: [2] }, ); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index b0c0e8e2..44b82f62 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -474,10 +474,10 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { gainR = gain1; } // Individual voice detuning - freq = applySemitoneDetuneToFrequency(freq, getUnisonDetune(voices, freqspread, n)); + const voiceFreq = applySemitoneDetuneToFrequency(freq, getUnisonDetune(voices, freqspread, n)); // We must wrap this here because it is passed into sawblep below which // has domain [0, 1] - const dt = mod(freq / sampleRate, 1); + const dt = mod(voiceFreq / sampleRate, 1); this.phase[n] = this.phase[n] ?? Math.random(); const v = waveshapes.sawblep(this.phase[n], dt); @@ -988,6 +988,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { { name: 'warpMode', defaultValue: 0 }, { name: 'voices', defaultValue: 1, minValue: 1, maxValue: 32 }, { name: 'spread', defaultValue: 0, minValue: 0, maxValue: 1 }, + { name: 'phaserand', defaultValue: 1, minValue: 0, maxValue: 1 }, ]; } @@ -1188,6 +1189,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const warpAmount = pv(parameters.warp, i); const warpMode = pv(parameters.warpMode, i); const voices = pv(parameters.voices, i); + const phaseRand = pv(parameters.phaserand, i); const gain1 = Math.sqrt(1 - spread); const gain2 = Math.sqrt(spread); let f = pv(parameters.frequency, i); @@ -1207,7 +1209,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const bank = this.tables[level]; // warp phase then sample - this.phase[n] = this.phase[n] ?? Math.random(); + this.phase[n] = this.phase[n] ?? Math.random() * phaseRand; let ph = this._warpPhase(this.phase[n], warpAmount, warpMode); const s0 = this._sampleFrame(bank[fIdx], ph); const s1 = this._sampleFrame(bank[Math.min(this.numFrames - 1, fIdx + 1)], ph);