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/editor.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) (limited to 'tools/mq_editor/editor.js') diff --git a/tools/mq_editor/editor.js b/tools/mq_editor/editor.js index 6ea6d73..b07664e 100644 --- a/tools/mq_editor/editor.js +++ b/tools/mq_editor/editor.js @@ -124,8 +124,8 @@ class PartialEditor { const grid = this._synthGrid; grid.innerHTML = ''; - const repDefaults = { decay_alpha: 0.1, jitter: 0.05, spread_above: 0.02, spread_below: 0.02 }; - const resDefaults = { r: 0.995, gainComp: 1.0 }; + const harmDefaults = { decay: 0.0, freq_mult: 2.0, jitter: 0.05, spread_above: 0.02, spread_below: 0.02 }; + const resDefaults = { r: 0.995, gainComp: 1.0 }; const isResonator = !!(partial.resonator && partial.resonator.enabled); @@ -156,16 +156,17 @@ class PartialEditor { sinSection.style.cssText = 'display:contents;'; sinSection.dataset.section = 'sinusoid'; - const rep = partial.replicas || {}; + const harm = partial.harmonics || {}; const sinParams = [ - { key: 'decay_alpha', label: 'decay', step: '0.001' }, + { key: 'decay', label: 'h.decay', step: '0.01', max: '0.90' }, + { key: 'freq_mult', label: 'h.freq', step: '0.01' }, { key: 'jitter', label: 'jitter', step: '0.001' }, { key: 'spread_above', label: 'spread ↑', step: '0.001' }, { key: 'spread_below', label: 'spread ↓', step: '0.001' }, ]; const sinInputs = {}; for (const p of sinParams) { - const val = rep[p.key] != null ? rep[p.key] : repDefaults[p.key]; + const val = harm[p.key] != null ? harm[p.key] : harmDefaults[p.key]; const lbl = document.createElement('span'); lbl.textContent = p.label; const inp = document.createElement('input'); @@ -173,22 +174,25 @@ class PartialEditor { inp.value = val.toFixed(3); inp.step = p.step; inp.min = '0'; + if (p.max) inp.max = p.max; inp.addEventListener('change', (e) => { if (!this.partials) return; - const v = parseFloat(e.target.value); + let v = parseFloat(e.target.value); if (isNaN(v)) return; - if (!this.partials[index].replicas) this.partials[index].replicas = { ...repDefaults }; - this.partials[index].replicas[p.key] = v; + if (p.max) v = Math.min(v, parseFloat(p.max)); + if (!this.partials[index].harmonics) this.partials[index].harmonics = { ...harmDefaults }; + this.partials[index].harmonics[p.key] = v; if (this.viewer) this.viewer.render(); }); sinInputs[p.key] = inp; const jog = this._makeJogSlider(inp, { step: parseFloat(p.step), + max: p.max ? parseFloat(p.max) : undefined, onUpdate: (newVal) => { if (!this.partials || !this.partials[index]) return; - if (!this.partials[index].replicas) this.partials[index].replicas = { ...repDefaults }; - this.partials[index].replicas[p.key] = newVal; + if (!this.partials[index].harmonics) this.partials[index].harmonics = { ...harmDefaults }; + this.partials[index].harmonics[p.key] = newVal; if (this.viewer) this.viewer.render(); } }); @@ -213,9 +217,9 @@ class PartialEditor { const sr = this.viewer ? this.viewer.audioBuffer.sampleRate : 44100; const fs = sc ? sc.fftSize : 2048; const {spread_above, spread_below} = autodetectSpread(p, sc, fs, sr); - if (!p.replicas) p.replicas = { ...repDefaults }; - p.replicas.spread_above = spread_above; - p.replicas.spread_below = spread_below; + if (!p.harmonics) p.harmonics = { ...harmDefaults }; + p.harmonics.spread_above = spread_above; + p.harmonics.spread_below = spread_below; sinInputs['spread_above'].value = spread_above.toFixed(4); sinInputs['spread_below'].value = spread_below.toFixed(4); }); -- cgit v1.2.3