From 2ce09cfd321750e383e3407fb5d946a801807106 Mon Sep 17 00:00:00 2001 From: Aria Date: Sun, 23 Nov 2025 15:25:08 -0600 Subject: [PATCH] Move fns to util file; optimized clamp --- packages/core/util.mjs | 7 ++++- packages/superdough/util.mjs | 40 +++++++++++++++++++++++++-- packages/superdough/worklets.mjs | 39 ++++++++------------------ packages/supradough/dough-worklet.mjs | 6 +++- packages/supradough/dough.mjs | 6 +++- 5 files changed, 66 insertions(+), 32 deletions(-) diff --git a/packages/core/util.mjs b/packages/core/util.mjs index 915a2cb77..a88adbadc 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -253,7 +253,12 @@ export const pairs = function (xs) { return result; }; -export const clamp = (num, min, max) => Math.min(Math.max(num, min), max); +// Optimized clamp (~1.7x faster than Math.max/min approach) +export const clamp = (num, min, max) => { + if (num < min) return min; + if (num > max) return max; + return num; +}; /* solmization, not used yet */ const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/ diff --git a/packages/superdough/util.mjs b/packages/superdough/util.mjs index 44ec79f77..01b47f158 100644 --- a/packages/superdough/util.mjs +++ b/packages/superdough/util.mjs @@ -3,6 +3,8 @@ import { logger } from './logger.mjs'; // currently duplicate with core util.mjs to skip dependency // TODO: add separate util module? +export const LN2 = Math.LN2; + export const tokenizeNote = (note) => { if (typeof note !== 'string') { return []; @@ -32,10 +34,9 @@ export const noteToMidi = (note, defaultOctave = 3) => { export const midiToFreq = (n) => { return Math.pow(2, (n - 69) / 12) * 440; }; -export const clamp = (num, min, max) => Math.min(Math.max(num, min), max); export const freqToMidi = (freq) => { - return (12 * Math.log(freq / 440)) / Math.LN2 + 69; + return (12 * Math.log(freq / 440)) / LN2 + 69; }; export const valueToMidi = (value, fallbackValue) => { @@ -114,3 +115,38 @@ export function getCommonSampleInfo(hapValue, bank) { export const pickAndRename = (source, map) => { return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]])); }; + +// Optimized / numerical functions (ideal for hot dsp loops) + +// Optimized clamp (~1.7x faster than Math.max/min approach) +export const clamp = (num, min, max) => { + if (num < min) return min; + if (num > max) return max; + return num; +}; +export const mod = (n, m) => ((n % m) + m) % m; +export const lerp = (a, b, n) => n * (b - a) + a; +export const frac = (x) => x - Math.floor(x); + +// Fast integer ops for non-negative values +export const ffloor = (x) => x | 0; +export const fround = (x) => ffloor(x + 0.5); +export const fceil = (x) => ffloor(x + 1); +export const ffrac = (x) => x - ffloor(x); + +export const fastexpm1 = (x) => x * (1 + x + 0.5 * x * x); // Taylor approximation + +export const fastpow2 = (x) => { + // Taylor approximation of 2 ^ x + const a = x * LN2; + return 1 + a * (1 + a * (0.5 + a / 6)); +}; + +export const applySemitoneDetuneToFrequency = (frequency, detune) => { + return frequency * Math.pow(2, detune / 12); +}; + +export const applyFastDetune = (frequency, detune) => { + if (detune < 4) return frequency * fastpow2(detune / 12); + return applySemitoneDetuneToFrequency(frequency, detune); +}; diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index e8b808be6..f1d67870e 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -5,6 +5,18 @@ import OLAProcessor from './ola-processor'; import FFT from './fft.js'; import { getDistortionAlgorithm } from './helpers.mjs'; +import { + applyFastDetune, + applySemitoneDetuneToFrequency, + clamp, + fastexpm1, + fceil, + ffloor, + ffrac, + frac, + fround, + lerp, +} from './util.mjs'; const blockSize = 128; const PI = Math.PI; @@ -12,23 +24,11 @@ const TWO_PI = 2 * PI; const INVSR = 1 / sampleRate; const MAX_VOICES = 64; -const clamp = (num, min, max) => Math.min(Math.max(num, min), max); -const mod = (n, m) => ((n % m) + m) % m; -const lerp = (a, b, n) => n * (b - a) + a; const pv = (arr, n) => arr[n] ?? arr[0]; -const frac = (x) => x - Math.floor(x); - -// Fast integer ops for non-negative values -const ffloor = (x) => x | 0; -const fround = (x) => ffloor(x + 0.5); -const fceil = (x) => ffloor(x + 1); -const ffrac = (x) => x - ffloor(x); - const fast_tanh = (x) => { const x2 = x * x; return (x * (27 + x2)) / (27 + 9 * x2); }; -const fastexpm1 = (x) => x * (1 + x + 0.5 * x * x); // Taylor approximation // Optimized per-voice detuner which precomputes constants const getDetuner = (unison, detune) => { @@ -40,21 +40,6 @@ 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.5 + a / 6)); -} - -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) { diff --git a/packages/supradough/dough-worklet.mjs b/packages/supradough/dough-worklet.mjs index 18c316b2d..e51d84e16 100644 --- a/packages/supradough/dough-worklet.mjs +++ b/packages/supradough/dough-worklet.mjs @@ -1,6 +1,10 @@ import { Dough } from './dough.mjs'; -const clamp = (num, min, max) => Math.min(Math.max(num, min), max); +const clamp = (num, min, max) => { + if (num < min) return min; + if (num > max) return max; + return num; +}; class DoughProcessor extends AudioWorkletProcessor { constructor() { diff --git a/packages/supradough/dough.mjs b/packages/supradough/dough.mjs index 7231792c9..d90420aac 100644 --- a/packages/supradough/dough.mjs +++ b/packages/supradough/dough.mjs @@ -6,7 +6,11 @@ const PI_DIV_SR = Math.PI / SAMPLE_RATE; const ISR = 1 / SAMPLE_RATE; let gainCurveFunc = (val) => Math.pow(val, 2); -const clamp = (num, min, max) => Math.min(Math.max(num, min), max); +const clamp = (num, min, max) => { + if (num < min) return min; + if (num > max) return max; + return num; +}; function applyGainCurve(val) { return gainCurveFunc(val);