From 722b545e79178ae67b11af6a3567e68c69fd1983 Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 18 Feb 2026 11:48:53 +0100 Subject: 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 --- tools/mq_editor/mq_synth.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'tools') 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])); -- cgit v1.2.3