diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-18 17:59:49 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-18 17:59:49 +0100 |
| commit | 49e4e374bc51517d8119e4c82d46e3a94ea0b75b (patch) | |
| tree | e0ac136257faba9dc322978a5cd1df52012541c5 /tools/mq_editor/app.js | |
| parent | b9f6429b6cc741c5240c1fea8bbf4c6244e4e5d1 (diff) | |
feat(mq_editor): add explore mode for interactive partial tracking
Hover to preview a tracked partial from mouse position (peak-snapped,
forward+backward MQ tracking). Click to commit. Toggle with ⊕ Explore
button or X key. Escape exits explore mode.
handoff(Gemini): explore mode added in mq_extract.trackFromSeed + viewer.js/app.js
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'tools/mq_editor/app.js')
| -rw-r--r-- | tools/mq_editor/app.js | 34 |
1 files changed, 34 insertions, 0 deletions
diff --git a/tools/mq_editor/app.js b/tools/mq_editor/app.js index b0b578b..41df49a 100644 --- a/tools/mq_editor/app.js +++ b/tools/mq_editor/app.js @@ -45,6 +45,14 @@ let audioContext = null; let currentSource = null; let extractedPartials = null; let stftCache = null; +let exploreModeActive = false; + +function setExploreMode(enabled) { + exploreModeActive = enabled; + const btn = document.getElementById('exploreBtn'); + btn.classList.toggle('explore-active', enabled); + if (viewer) viewer.setExploreMode(enabled); +} // Undo/redo const undoStack = []; @@ -152,6 +160,26 @@ function loadAudioBuffer(buffer, label) { viewer.onPartialSelect = (i) => editor.onPartialSelect(i); viewer.onRender = () => editor.onRender(); viewer.onBeforeChange = pushUndo; + viewer.onExploreMove = (time, freq) => { + if (!viewer.frames || viewer.frames.length === 0) return; + const params = { + hopSize: Math.max(64, parseInt(hopSize.value) || 64), + sampleRate: audioBuffer.sampleRate, + deathAge: parseInt(deathAgeEl.value), + phaseErrorWeight: parseFloat(phaseErrorWeightEl.value), + }; + viewer.setPreviewPartial(trackFromSeed(viewer.frames, time, freq, params)); + }; + viewer.onExploreCommit = (partial) => { + if (!extractedPartials) extractedPartials = []; + pushUndo(); + extractedPartials.unshift(partial); + editor.setPartials(extractedPartials); + viewer.setPartials(extractedPartials); + viewer.setKeepCount(getKeepCount()); + viewer.selectPartial(0); + setStatus(`Explore: added partial (${extractedPartials.length} total)`, 'info'); + }; if (label.startsWith('Test WAV')) validateTestWAVPeaks(stftCache); }, 10); } @@ -256,6 +284,7 @@ function runExtraction() { autoSpreadAllBtn.disabled = false; document.getElementById('newPartialBtn').disabled = false; document.getElementById('clearAllBtn').disabled = false; + document.getElementById('exploreBtn').disabled = false; undoStack.length = 0; redoStack.length = 0; _updateUndoRedoBtns(); }, 50); } @@ -305,6 +334,7 @@ function clearAllPartials() { document.getElementById('newPartialBtn').addEventListener('click', createNewPartial); document.getElementById('clearAllBtn').addEventListener('click', clearAllPartials); +document.getElementById('exploreBtn').addEventListener('click', () => setExploreMode(!exploreModeActive)); document.getElementById('undoBtn').addEventListener('click', undo); document.getElementById('redoBtn').addEventListener('click', redo); @@ -480,7 +510,11 @@ document.addEventListener('keydown', (e) => { } else if (e.code === 'KeyE') { e.preventDefault(); if (!extractBtn.disabled) extractBtn.click(); + } else if (e.code === 'KeyX' && !e.ctrlKey && !e.metaKey) { + e.preventDefault(); + if (!document.getElementById('exploreBtn').disabled) setExploreMode(!exploreModeActive); } else if (e.code === 'Escape') { + if (exploreModeActive) { setExploreMode(false); return; } if (viewer) viewer.selectPartial(-1); } }); |
