From 890f4fdf96945832d5da078cb795266127cf122d Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 18 Feb 2026 11:05:37 +0100 Subject: feat(mq_editor): jog sliders for synth params, reset partials on WAV load, panel refresh after extract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clear extractedPartials and editor state when loading a new WAV - After extract, refresh right panels (re-select if index still valid) - Synth fields (decay, jitter, spread) get jog sliders: drag to nudge, spring-back on release - Spread extension limit dashed line: alpha 0.4→0.75, lineWidth 1→1.5, dash [3,4]→[4,3] handoff(Gemini): mq_editor UX polish — jog sliders, WAV reset, panel refresh, spread line visibility Co-Authored-By: Claude Sonnet 4.6 --- tools/mq_editor/editor.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) (limited to 'tools/mq_editor/editor.js') diff --git a/tools/mq_editor/editor.js b/tools/mq_editor/editor.js index 9cfcdf1..b767534 100644 --- a/tools/mq_editor/editor.js +++ b/tools/mq_editor/editor.js @@ -146,8 +146,15 @@ class PartialEditor { if (this.viewer) this.viewer.render(); }); inputs[p.key] = inp; + + const jog = this._makeJogSlider(inp, partial, index, p, defaults); + const wrap = document.createElement('div'); + wrap.className = 'synth-field-wrap'; + wrap.appendChild(inp); + wrap.appendChild(jog); + grid.appendChild(lbl); - grid.appendChild(inp); + grid.appendChild(wrap); } // Auto-detect spread button @@ -173,6 +180,52 @@ class PartialEditor { grid.appendChild(autoBtn); } + _makeJogSlider(inp, partial, index, p, defaults) { + const slider = document.createElement('div'); + slider.className = 'jog-slider'; + const thumb = document.createElement('div'); + thumb.className = 'jog-thumb'; + slider.appendChild(thumb); + + const sensitivity = parseFloat(p.step) * 5; + let startX = 0, startVal = 0, dragging = false; + + const onMove = (e) => { + if (!dragging) return; + const dx = e.clientX - startX; + const half = slider.offsetWidth / 2; + const clamped = Math.max(-half, Math.min(half, dx)); + thumb.style.transition = 'none'; + thumb.style.left = `calc(50% - 3px + ${clamped}px)`; + const newVal = Math.max(0, startVal + dx * sensitivity); + inp.value = newVal.toFixed(3); + if (!this.partials || !this.partials[index]) return; + if (!this.partials[index].replicas) this.partials[index].replicas = { ...defaults }; + this.partials[index].replicas[p.key] = newVal; + if (this.viewer) this.viewer.render(); + }; + + const onUp = () => { + if (!dragging) return; + dragging = false; + thumb.style.transition = ''; + thumb.style.left = 'calc(50% - 3px)'; + document.removeEventListener('mousemove', onMove); + document.removeEventListener('mouseup', onUp); + }; + + slider.addEventListener('mousedown', (e) => { + dragging = true; + startX = e.clientX; + startVal = Math.max(0, parseFloat(inp.value) || 0); + document.addEventListener('mousemove', onMove); + document.addEventListener('mouseup', onUp); + e.preventDefault(); + }); + + return slider; + } + _makeCurveUpdater(partialIndex, curveKey, field, pointIndex) { return (e) => { if (!this.partials) return; -- cgit v1.2.3