From bc07ea00a9f2f418e6b460884c3925b72ff2a358 Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 18 Feb 2026 16:22:54 +0100 Subject: refactor(mq_editor): unify freq+amp into single bezier curve freqCurve now carries a0-a3 (amplitude control values) alongside v0-v3 (frequency). Both components share the same t0-t3 time parameterization. evalBezierAmp() added to utils.js. ampCurve removed from partials and synth pipeline. Amp panel drag now changes only a_i; t is read-only (shared with freq). handoff(Claude): unified freq/amp bezier done --- tools/mq_editor/mq_synth.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'tools/mq_editor/mq_synth.js') diff --git a/tools/mq_editor/mq_synth.js b/tools/mq_editor/mq_synth.js index 4c68056..00867a9 100644 --- a/tools/mq_editor/mq_synth.js +++ b/tools/mq_editor/mq_synth.js @@ -8,7 +8,7 @@ function randFloat(seed, min, max) { } // Synthesize audio from MQ partials -// partials: array of {freqCurve, ampCurve, replicas?, resonator?} +// partials: array of {freqCurve (with a0-a3 for amp), replicas?, resonator?} // replicas: {offsets, decay_alpha, jitter, spread_above, spread_below} // resonator: {enabled, r, gainComp} — two-pole resonator mode per partial // integratePhase: true = accumulate 2π*f/SR per sample (correct for varying freq) @@ -35,7 +35,6 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt for (let p = 0; p < partials.length; ++p) { const partial = partials[p]; const fc = partial.freqCurve; - const ac = partial.ampCurve; if ((partial.resonator && partial.resonator.enabled) || options.forceResonator) { // --- Two-pole resonator mode --- @@ -50,7 +49,7 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt const gainNorm = Math.sqrt(Math.max(0, 1.0 - r * r)); configs.push({ mode: 'resonator', - fc, ac, + fc, r, gainComp, gainNorm, y1: 0.0, y2: 0.0, noiseSeed: ((p * 1664525 + 1013904223) & 0xFFFFFFFF) >>> 0 @@ -70,7 +69,7 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt const initPhase = randFloat(p * 67890 + r, 0.0, 1.0) * (jitter * jitterMult) * 2.0 * Math.PI; replicaData.push({ratio: offsets[r], spread, phase: initPhase}); } - configs.push({ mode: 'sinusoid', fc, ac, decay_alpha, replicaData }); + configs.push({ mode: 'sinusoid', fc, decay_alpha, replicaData }); } } @@ -80,13 +79,13 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt for (let p = 0; p < configs.length; ++p) { const cfg = configs[p]; - const {fc, ac} = cfg; + const {fc} = cfg; if (cfg.mode === 'resonator') { if (t < fc.t0 || t > fc.t3) { cfg.y1 = 0.0; cfg.y2 = 0.0; continue; } const f0 = evalBezier(fc, t); - const A = evalBezier(ac, t); + const A = evalBezierAmp(fc, t); const omega = 2.0 * Math.PI * f0 / sampleRate; const b1 = 2.0 * cfg.r * Math.cos(omega); @@ -104,7 +103,7 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt if (t < fc.t0 || t > fc.t3) continue; const f0 = evalBezier(fc, t); - const A0 = evalBezier(ac, t); + const A0 = evalBezierAmp(fc, t); const {decay_alpha, replicaData} = cfg; for (let r = 0; r < replicaData.length; ++r) { -- cgit v1.2.3