From cd771a49d1d59b1403ef7f358398fa2f0f646cc4 Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 18 Feb 2026 22:59:15 +0100 Subject: feat(mq_editor): replace replicas with harmonics model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fundamental f0 always synthesized; harmonics added at n*freq_mult - decay^n amplitude rolloff per harmonic (capped at 0.90) - Resonator mode also expanded across harmonics (per-harmonic y1/y2 state) - UI: h.decay, h.freq (default 2.0), jitter, spread↑/↓ params - Viewer: faint dotted harmonic bands with spread visualization - Default freq_mult=2.0 (natural harmonic series) handoff(Gemini): harmonics model complete, ready for next task Co-Authored-By: Claude Sonnet 4.6 --- tools/mq_editor/viewer.js | 60 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 4 deletions(-) (limited to 'tools/mq_editor/viewer.js') diff --git a/tools/mq_editor/viewer.js b/tools/mq_editor/viewer.js index 923edcc..677e5b5 100644 --- a/tools/mq_editor/viewer.js +++ b/tools/mq_editor/viewer.js @@ -293,10 +293,12 @@ class SpectrogramViewer { _renderSpreadBand(partial, color) { const {ctx} = this; - const curve = partial.freqCurve; - const rep = partial.replicas || {}; - const sa = rep.spread_above != null ? rep.spread_above : 0.02; - const sb = rep.spread_below != null ? rep.spread_below : 0.02; + const curve = partial.freqCurve; + const harm = partial.harmonics || {}; + const sa = harm.spread_above != null ? harm.spread_above : 0.02; + const sb = harm.spread_below != null ? harm.spread_below : 0.02; + const decay = harm.decay != null ? harm.decay : 0.0; + const freqMult = harm.freq_mult != null ? harm.freq_mult : 2.0; const {upper, lower} = buildBandPoints(this, curve, sa, sb); if (upper.length < 2) return; @@ -346,6 +348,56 @@ class SpectrogramViewer { ctx.setLineDash([]); } + // Harmonic bands (faint, fading with decay^n) + if (decay > 0) { + for (let n = 1; ; ++n) { + const ampMult = Math.pow(decay, n); + if (ampMult < 0.001) break; + const hRatio = n * freqMult; + + // Center line + const cpts = buildCenterPoints(this, curve, hRatio); + if (cpts.length >= 2) { + ctx.globalAlpha = ampMult * 0.85; + ctx.strokeStyle = color; + ctx.lineWidth = 1.5; + ctx.setLineDash([3, 4]); + ctx.beginPath(); + ctx.moveTo(cpts[0][0], cpts[0][1]); + for (let i = 1; i < cpts.length; ++i) ctx.lineTo(cpts[i][0], cpts[i][1]); + ctx.stroke(); + ctx.setLineDash([]); + } + + // Spread band fill + boundary dashes + const {upper: hu, lower: hl} = buildBandPoints(this, curve, sa, sb, hRatio); + if (hu.length >= 2) { + ctx.beginPath(); + ctx.moveTo(hu[0][0], hu[0][1]); + for (let i = 1; i < hu.length; ++i) ctx.lineTo(hu[i][0], hu[i][1]); + for (let i = hl.length - 1; i >= 0; --i) ctx.lineTo(hl[i][0], hl[i][1]); + ctx.closePath(); + ctx.fillStyle = color; + ctx.globalAlpha = ampMult * 0.12; + ctx.fill(); + + ctx.globalAlpha = ampMult * 0.55; + ctx.strokeStyle = color; + ctx.lineWidth = 1; + ctx.setLineDash([3, 5]); + ctx.beginPath(); + ctx.moveTo(hu[0][0], hu[0][1]); + for (let i = 1; i < hu.length; ++i) ctx.lineTo(hu[i][0], hu[i][1]); + ctx.stroke(); + ctx.beginPath(); + ctx.moveTo(hl[0][0], hl[0][1]); + for (let i = 1; i < hl.length; ++i) ctx.lineTo(hl[i][0], hl[i][1]); + ctx.stroke(); + ctx.setLineDash([]); + } + } + } + ctx.globalAlpha = savedAlpha; } -- cgit v1.2.3