summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-18 11:48:53 +0100
committerskal <pascal.massimino@gmail.com>2026-02-18 11:48:53 +0100
commit722b545e79178ae67b11af6a3567e68c69fd1983 (patch)
treea2a99df56019675dde507ce050225ba16d9a78f7
parent91d546c3ff52ac30daf3e3e0fe90bbeab4a366ac (diff)
feat(mq_editor): post-synthesis LP/HP filter pair in synthesizeMQ
Add first-order IIR low-pass (options.k1) and high-pass (options.k2) filters applied globally after the synthesis loop and before normalization. Both are bypassed when the key is absent; LP is applied before HP (bandpass configuration when both active). handoff(Claude): LP/HP filters added to synthesizeMQ, no UI yet. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--tools/mq_editor/mq_synth.js24
1 files changed, 24 insertions, 0 deletions
diff --git a/tools/mq_editor/mq_synth.js b/tools/mq_editor/mq_synth.js
index 2d3111b..f298392 100644
--- a/tools/mq_editor/mq_synth.js
+++ b/tools/mq_editor/mq_synth.js
@@ -26,6 +26,8 @@ function randFloat(seed, min, max) {
// resonator: {enabled, r, gainComp} — two-pole resonator mode per partial
// integratePhase: true = accumulate 2π*f/SR per sample (correct for varying freq)
// false = 2π*f*t (simpler, only correct for constant freq)
+// options.k1: LP coefficient in (0,1] — omit to bypass
+// options.k2: HP coefficient in (0,1] — omit to bypass
function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, options = {}) {
const numSamples = Math.floor(sampleRate * duration);
const pcm = new Float32Array(numSamples);
@@ -137,6 +139,28 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt
pcm[i] = sample;
}
+ // Post-synthesis filters (applied before normalization)
+ // LP: y[n] = k1*x[n] + (1-k1)*y[n-1] — options.k1 in (0,1], omit to bypass
+ // HP: y[n] = k2*(y[n-1] + x[n] - x[n-1]) — options.k2 in (0,1], omit to bypass
+ if (options.k1 != null) {
+ const k1 = Math.max(0, Math.min(1, options.k1));
+ let y = 0.0;
+ for (let i = 0; i < numSamples; ++i) {
+ y = k1 * pcm[i] + (1.0 - k1) * y;
+ pcm[i] = y;
+ }
+ }
+ if (options.k2 != null) {
+ const k2 = Math.max(0, Math.min(1, options.k2));
+ let y = 0.0, xp = 0.0;
+ for (let i = 0; i < numSamples; ++i) {
+ const x = pcm[i];
+ y = k2 * (y + x - xp);
+ xp = x;
+ pcm[i] = y;
+ }
+ }
+
// Normalize
let maxAbs = 0;
for (let i = 0; i < numSamples; ++i) maxAbs = Math.max(maxAbs, Math.abs(pcm[i]));