diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 166f0ac2..eb863706 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -430,6 +430,17 @@ export const { crush } = registerControl('crush'); */ export const { coarse } = registerControl('coarse'); +/** + * filter overdrive for supported filter types + * + * @name drive + * @param {number | Pattern} amount + * @example + * note("{f g g c d a a#}%16".sub(17)).s("supersaw").lpenv(8).lpf(150).lpq(.8).ftype('ladder').drive("<.5 4>") + * + */ +export const { drive } = registerControl('drive'); + /** * Allows you to set the output channels on the interface * @@ -750,7 +761,7 @@ export const { bprelease, bpr } = registerControl('bprelease', 'bpr'); * .sound('sawtooth') * .lpf(500) * .bpenv(4) - * .ftype("12db 24db") + * .ftype("") */ export const { ftype } = registerControl('ftype'); export const { fanchor } = registerControl('fanchor'); diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index a7886716..c7883bdb 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -103,13 +103,13 @@ 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) { +export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor, model, drive) { 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; if (model === 'ladder') { - filter = getWorklet(context, 'ladder-processor', { frequency, q: Q, drive: 1 }); + filter = getWorklet(context, 'ladder-processor', { frequency, q: Q, drive }); frequencyParam = filter.parameters.get('frequency'); } else { filter = context.createBiquadFilter(); diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index e0ea28e3..fe536d7a 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -290,6 +290,7 @@ export const superdough = async (value, t, hapDuration) => { // filters ftype = '12db', fanchor = 0.5, + drive = 0.69, // low pass cutoff, lpenv, @@ -297,7 +298,7 @@ export const superdough = async (value, t, hapDuration) => { lpdecay, lpsustain, lprelease, - resonance = 1, + resonance = 0.5, // high pass hpenv, hcutoff, @@ -412,6 +413,7 @@ export const superdough = async (value, t, hapDuration) => { t + hapDuration, fanchor, ftype, + drive, ); chain.push(lp()); if (ftype === '24db') { diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index c3493af3..fe42b90f 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -1,6 +1,6 @@ // coarse, crush, and shape processors adapted from dktr0's webdirt: https://github.com/dktr0/WebDirt/blob/5ce3d698362c54d6e1b68acc47eb2955ac62c793/dist/AudioWorklets.js // LICENSE GNU General Public License v3.0 see https://github.com/dktr0/WebDirt/blob/main/LICENSE - +import { clamp } from './util.mjs'; const blockSize = 128; class CoarseProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { @@ -111,13 +111,13 @@ function fast_tanh(x) { return (x * (27.0 + x2)) / (27.0 + 9.0 * x2); } const _PI = 3.14159265359; - +//adapted from https://github.com/TheBouteillacBear/webaudioworklet-wasm?tab=MIT-1-ov-file class LadderProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { return [ { name: 'frequency', defaultValue: 500 }, { name: 'q', defaultValue: 1 }, - { name: 'drive', defaultValue: 1 }, + { name: 'drive', defaultValue: 0.69 }, ]; } @@ -145,13 +145,14 @@ class LadderProcessor extends AudioWorkletProcessor { this.started = hasInput; const resonance = parameters.q[0]; - const drive = parameters.drive[0] * 2; + const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000); let cutoff = parameters.frequency[0]; cutoff = (cutoff * 2 * _PI) / sampleRate; cutoff = cutoff > 1 ? 1 : cutoff; - const k = resonance * 4; - const makeupgain = 1 / drive; + const k = Math.min(8, resonance * 2); + // drive makeup * resonance volume loss makeup + const makeupgain = (1 / drive) * Math.min(1.75, 1 + k); for (let n = 0; n < blockSize; n++) { for (let i = 0; i < input.length; i++) {