summaryrefslogtreecommitdiff
path: root/tools/mq_editor/viewer.js
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-18 22:59:15 +0100
committerskal <pascal.massimino@gmail.com>2026-02-18 22:59:15 +0100
commitcd771a49d1d59b1403ef7f358398fa2f0f646cc4 (patch)
tree512ca89f54e3a92f65f7d1a7c51193c461f5c23a /tools/mq_editor/viewer.js
parent080f457040ca54256325b922ebd67cde5c0dc030 (diff)
feat(mq_editor): replace replicas with harmonics model
- 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 <noreply@anthropic.com>
Diffstat (limited to 'tools/mq_editor/viewer.js')
-rw-r--r--tools/mq_editor/viewer.js60
1 files changed, 56 insertions, 4 deletions
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;
}