summaryrefslogtreecommitdiff
path: root/tools/mq_editor/app.js
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-18 17:59:49 +0100
committerskal <pascal.massimino@gmail.com>2026-02-18 17:59:49 +0100
commit49e4e374bc51517d8119e4c82d46e3a94ea0b75b (patch)
treee0ac136257faba9dc322978a5cd1df52012541c5 /tools/mq_editor/app.js
parentb9f6429b6cc741c5240c1fea8bbf4c6244e4e5d1 (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.js34
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);
}
});