diff options
Diffstat (limited to 'tools/mq_editor/editor.js')
| -rw-r--r-- | tools/mq_editor/editor.js | 62 |
1 files changed, 31 insertions, 31 deletions
diff --git a/tools/mq_editor/editor.js b/tools/mq_editor/editor.js index 0854ec0..b07664e 100644 --- a/tools/mq_editor/editor.js +++ b/tools/mq_editor/editor.js @@ -25,8 +25,7 @@ class PartialEditor { // Private state this._selectedIndex = -1; - this._dragPointIndex = -1; - this._dragCompanionOff = null; // {dt, dv} offset of companion handle relative to anchor + this._dragPointIndex = -1; this._amp = { tMin: 0, tMax: 1, ampTop: 1 }; this._setupButtons(); @@ -125,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); @@ -157,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'); @@ -174,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(); } }); @@ -214,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); }); @@ -323,10 +326,10 @@ class PartialEditor { if (!dragging) return; const dx = e.clientX - startX; const half = slider.offsetWidth / 2; - const clamped = Math.max(-half, Math.min(half, dx)); + const clamped = clamp(dx, -half, half); thumb.style.transition = 'none'; thumb.style.left = `calc(50% - 3px + ${clamped}px)`; - const newVal = Math.max(min, Math.min(max, startVal + dx * sensitivity)); + const newVal = clamp(startVal + dx * sensitivity, min, max); inp.value = newVal.toFixed(decimals); onUpdate(newVal); }; @@ -496,13 +499,8 @@ class PartialEditor { for (let i = 0; i < 4; ++i) { if (Math.hypot(this._tToX(curve['t' + i]) - x, this._ampToY(curve['a' + i]) - y) <= 8) { if (this.onBeforeChange) this.onBeforeChange(); - this._dragPointIndex = i; - this._dragCompanionOff = null; - if (i === 0) - this._dragCompanionOff = { da: curve.a1 - curve.a0 }; - else if (i === 3) - this._dragCompanionOff = { da: curve.a2 - curve.a3 }; - canvas.style.cursor = 'grabbing'; + this._dragPointIndex = i; + canvas.style.cursor = (i === 1 || i === 2) ? 'move' : 'ns-resize'; e.preventDefault(); return; } @@ -516,10 +514,11 @@ class PartialEditor { const curve = this.partials[this._selectedIndex].freqCurve; const i = this._dragPointIndex; curve['a' + i] = Math.max(0, this._yToAmp(y)); - if (this._dragCompanionOff) { - const off = this._dragCompanionOff; - if (i === 0) { curve.a1 = curve.a0 + off.da; } - else { curve.a2 = curve.a3 + off.da; } + // Inner control points are also horizontally draggable + if (i === 1) { + curve.t1 = clamp(this._xToT(x), curve.t0 + 1e-4, curve.t2 - 1e-4); + } else if (i === 2) { + curve.t2 = clamp(this._xToT(x), curve.t1 + 1e-4, curve.t3 - 1e-4); } this._renderAmpEditor(); if (this.viewer) this.viewer.render(); @@ -531,13 +530,14 @@ class PartialEditor { if (this._selectedIndex >= 0 && this.partials) { const curve = this.partials[this._selectedIndex]?.freqCurve; if (curve) { - let near = false; + let cursor = 'crosshair'; for (let i = 0; i < 4; ++i) { if (Math.hypot(this._tToX(curve['t' + i]) - x, this._ampToY(curve['a' + i]) - y) <= 8) { - near = true; break; + cursor = (i === 1 || i === 2) ? 'move' : 'ns-resize'; + break; } } - canvas.style.cursor = near ? 'grab' : 'crosshair'; + canvas.style.cursor = cursor; } } }); |
