diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 5a2752944..3c1dd7da0 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -546,6 +546,18 @@ export const { cut } = registerControl('cut'); */ export const { cutoff, ctf, lpf, lp } = registerControl(['cutoff', 'resonance', 'lpenv'], 'ctf', 'lpf', 'lp'); +/** + * Sets the lowpass filter lfo modulation depth. + * @name lplfo + * @param {number | Pattern} modulation depth of the lowpass filter lfo between 0 and _n_ + * @example + * note("c2 e2 f2 g2") + * .sound('sawtooth') + * .lpf(300) + * .lplfo(2) + */ +export const { lplfo } = registerControl('lplfo'); + /** * Sets the lowpass filter envelope modulation depth. * @name lpenv diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index e51156fec..9f294a68b 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -112,11 +112,27 @@ export const getADSRValues = (params, curve = 'linear', defaultValues) => { return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)]; }; -export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor, model, drive) { +export function createFilter( + context, + type, + frequency, + Q, + att, + dec, + sus, + rel, + fenv, + start, + end, + fanchor, + model, + drive, + flfo, +) { const curve = 'exponential'; const [attack, decay, sustain, release] = getADSRValues([att, dec, sus, rel], curve, [0.005, 0.14, 0, 0.1]); let filter; - let frequencyParam; + let frequencyParam, detuneParam; if (model === 'ladder') { filter = getWorklet(context, 'ladder-processor', { frequency, q: Q, drive }); frequencyParam = filter.parameters.get('frequency'); @@ -126,6 +142,7 @@ export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fe filter.Q.value = Q; filter.frequency.value = frequency; frequencyParam = filter.frequency; + detuneParam = filter.detune; } // envelope is active when any of these values is set @@ -140,7 +157,10 @@ export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fe let max = clamp(2 ** (fenvAbs - offset) * frequency, 0, 20000); if (fenv < 0) [min, max] = [max, min]; getParamADSR(frequencyParam, attack, decay, sustain, release, min, max, start, end, curve); - return filter; + } + const hasLFO = detuneParam && flfo !== undefined; + if (hasLFO !== undefined) { + let lfo = getVibratoOscillator(detuneParam, { vib: flfo, vibmod: 4 }, start); } return filter; } diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index b61f42637..3a9cc8f6b 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -357,6 +357,7 @@ export const superdough = async (value, t, hapDuration) => { lpdecay, lpsustain, lprelease, + lplfo, resonance = getDefaultValue('resonance'), // high pass hpenv, @@ -475,6 +476,7 @@ export const superdough = async (value, t, hapDuration) => { fanchor, ftype, drive, + lplfo, ); chain.push(lp()); if (ftype === '24db') { diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index c071b5365..cf6f73f7b 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -4496,6 +4496,27 @@ exports[`runs examples > example "lpf" example index 1 1`] = ` ] `; +exports[`runs examples > example "lplfo" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:c2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 1/4 → 1/2 | note:e2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 1/2 → 3/4 | note:f2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 3/4 → 1/1 | note:g2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 1/1 → 5/4 | note:c2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 5/4 → 3/2 | note:e2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 3/2 → 7/4 | note:f2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 7/4 → 2/1 | note:g2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 2/1 → 9/4 | note:c2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 9/4 → 5/2 | note:e2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 5/2 → 11/4 | note:f2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 11/4 → 3/1 | note:g2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 3/1 → 13/4 | note:c2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 13/4 → 7/2 | note:e2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 7/2 → 15/4 | note:f2 s:sawtooth cutoff:300 lplfo:2 ]", + "[ 15/4 → 4/1 | note:g2 s:sawtooth cutoff:300 lplfo:2 ]", +] +`; + exports[`runs examples > example "lpq" example index 0 1`] = ` [ "[ 0/1 → 1/8 | s:hh cutoff:2000 resonance:0 ]",