diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index f5f3ad68..a7886716 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -103,14 +103,22 @@ 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) { +export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor, model) { const curve = 'exponential'; const [attack, decay, sustain, release] = getADSRValues([att, dec, sus, rel], curve, [0.005, 0.14, 0, 0.1]); - const filter = context.createBiquadFilter(); + let filter; + let frequencyParam; + if (model === 'ladder') { + filter = getWorklet(context, 'ladder-processor', { frequency, q: Q, drive: 1 }); + frequencyParam = filter.parameters.get('frequency'); + } else { + filter = context.createBiquadFilter(); + filter.type = type; + filter.Q.value = Q; + filter.frequency.value = frequency; + frequencyParam = filter.frequency; + } - filter.type = type; - filter.Q.value = Q; - filter.frequency.value = frequency; // envelope is active when any of these values is set const hasEnvelope = att ?? dec ?? sus ?? rel ?? fenv; // Apply ADSR to filter frequency @@ -122,7 +130,7 @@ export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fe let min = clamp(2 ** -offset * frequency, 0, 20000); let max = clamp(2 ** (fenvAbs - offset) * frequency, 0, 20000); if (fenv < 0) [min, max] = [max, min]; - getParamADSR(filter.frequency, attack, decay, sustain, release, min, max, start, end, curve); + getParamADSR(frequencyParam, attack, decay, sustain, release, min, max, start, end, curve); return filter; } return filter; diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index cd9fcfeb..e0ea28e3 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -395,6 +395,8 @@ export const superdough = async (value, t, hapDuration) => { chain.push(gainNode(gain)); if (cutoff !== undefined) { + // chain.push(getWorklet(ac, 'ladder-processor', { frequency: cutoff, q: resonance, drive: 1 })); + let lp = () => createFilter( ac, @@ -409,6 +411,7 @@ export const superdough = async (value, t, hapDuration) => { t, t + hapDuration, fanchor, + ftype, ); chain.push(lp()); if (ftype === '24db') { @@ -471,8 +474,6 @@ export const superdough = async (value, t, hapDuration) => { shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol })); distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol })); - chain.push(getWorklet(ac, 'ladder-processor', { cutoff: 500, q: 1 })); - compressorThreshold !== undefined && chain.push( getCompressor(ac, compressorThreshold, compressorRatio, compressorKnee, compressorAttack, compressorRelease), diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 152e51d6..0e0566d7 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -115,8 +115,9 @@ const _PI = 3.14159265359; class LadderProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [ - { name: 'cutoff', defaultValue: 500 }, + { name: 'frequency', defaultValue: 500 }, { name: 'q', defaultValue: 1 }, + { name: 'drive', defaultValue: 1 }, ]; } @@ -143,11 +144,13 @@ class LadderProcessor extends AudioWorkletProcessor { this.started = hasInput; const resonance = parameters.q[0]; - let cutoff = parameters.cutoff[0]; + const drive = parameters.drive[0] * 2; + let cutoff = parameters.frequency[0]; cutoff = (cutoff * 2 * _PI) / sampleRate; cutoff = cutoff > 1 ? 1 : cutoff; const k = resonance * 4; + const makeupgain = 1 / drive; for (let n = 0; n < blockSize; n++) { for (let i = 0; i < input.length; i++) { @@ -157,12 +160,12 @@ class LadderProcessor extends AudioWorkletProcessor { this.p33 = this.p32; this.p32 = this.p3; - this.p0 += (fast_tanh(input[i][n] - k * out) - fast_tanh(this.p0)) * cutoff; + this.p0 += (fast_tanh(input[i][n] * drive - k * out) - fast_tanh(this.p0)) * cutoff; this.p1 += (fast_tanh(this.p0) - fast_tanh(this.p1)) * cutoff; this.p2 += (fast_tanh(this.p1) - fast_tanh(this.p2)) * cutoff; this.p3 += (fast_tanh(this.p2) - fast_tanh(this.p3)) * cutoff; - output[i][n] = out; + output[i][n] = out * makeupgain; } } return true;