diff options
| -rw-r--r-- | tools/mq_editor/editor.js | 55 | ||||
| -rw-r--r-- | tools/mq_editor/index.html | 49 | ||||
| -rw-r--r-- | tools/mq_editor/viewer.js | 6 |
3 files changed, 103 insertions, 7 deletions
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; diff --git a/tools/mq_editor/index.html b/tools/mq_editor/index.html index 5e1e5c4..fa7543c 100644 --- a/tools/mq_editor/index.html +++ b/tools/mq_editor/index.html @@ -172,8 +172,14 @@ align-items: center; } .synth-grid span { color: #888; font-size: 12px; } - .synth-grid input[type="number"] { - width: 100%; + .synth-field-wrap { + display: flex; + align-items: center; + gap: 4px; + } + .synth-field-wrap input[type="number"] { + flex: 1; + min-width: 0; background: #333; color: #ccc; border: 1px solid #444; @@ -183,6 +189,37 @@ font-family: monospace; box-sizing: border-box; } + .synth-field-wrap input[type="number"]:focus { border-color: #666; outline: none; } + .jog-slider { + width: 44px; + height: 16px; + background: #1e1e1e; + border: 1px solid #444; + border-radius: 3px; + cursor: ew-resize; + position: relative; + flex-shrink: 0; + user-select: none; + overflow: hidden; + } + .jog-slider::before { + content: ''; + position: absolute; + left: 50%; top: 3px; bottom: 3px; + width: 1px; + background: #484848; + transform: translateX(-50%); + } + .jog-thumb { + position: absolute; + top: 3px; bottom: 3px; + width: 6px; + background: #888; + border-radius: 2px; + left: calc(50% - 3px); + transition: left 0.12s ease; + } + .jog-slider:hover .jog-thumb { background: #aaa; } .synth-grid input[type="number"]:focus { border-color: #666; outline: none; } .synth-section { border-top: 1px solid #444; @@ -362,6 +399,10 @@ playBtn.disabled = false; setStatus('Computing STFT cache...', 'info'); + // Reset partials from previous file + extractedPartials = null; + editor.setPartials(null); + setTimeout(() => { const signal = audioBuffer.getChannelData(0); stftCache = new STFTCache(signal, audioBuffer.sampleRate, fftSize, Math.max(64, parseInt(hopSize.value) || 64)); @@ -458,7 +499,9 @@ setStatus(`Extracted ${result.partials.length} partials`, 'info'); viewer.setPartials(result.partials); viewer.setKeepCount(getKeepCount()); - viewer.selectPartial(-1); + // Refresh panels: re-select if index still valid, else clear + const prevSel = viewer.selectedPartial; + viewer.selectPartial(prevSel >= 0 && prevSel < result.partials.length ? prevSel : -1); } catch (err) { setStatus('Extraction error: ' + err.message, 'error'); diff --git a/tools/mq_editor/viewer.js b/tools/mq_editor/viewer.js index 3b2e1b2..76c57e2 100644 --- a/tools/mq_editor/viewer.js +++ b/tools/mq_editor/viewer.js @@ -312,10 +312,10 @@ class SpectrogramViewer { ctx.fill(); // Dashed boundary lines - ctx.globalAlpha = 0.4; + ctx.globalAlpha = 0.75; ctx.strokeStyle = color; - ctx.lineWidth = 1; - ctx.setLineDash([3, 4]); + ctx.lineWidth = 1.5; + ctx.setLineDash([4, 3]); ctx.beginPath(); ctx.moveTo(upper[0][0], upper[0][1]); for (let i = 1; i < upper.length; ++i) ctx.lineTo(upper[i][0], upper[i][1]); |
