From 99e94d18ce54d1ad73c7c0349119d4cd8fb4f965 Mon Sep 17 00:00:00 2001 From: skal Date: Tue, 17 Feb 2026 21:28:58 +0100 Subject: feat(mq_editor): peaks display, STFT cache refactor, threshold reactivity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - STFTCache: cache squaredAmplitude (re²+im²) per frame, add getSquaredAmplitude(t) - getMagnitudeDB uses cached sq amp (10*log10, no sqrt) - detectPeaks uses squaredAmp from cache instead of recomputing FFT - extractPartials: use cache frames, return {partials, frames} - Press 'p' to toggle raw peak overlay in viewer - threshold input: step=any, change event triggers re-extraction - runExtraction() shared by button and threshold change handoff(Claude): mq_partial peaks/cache refactor complete --- tools/mq_editor/viewer.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'tools/mq_editor/viewer.js') diff --git a/tools/mq_editor/viewer.js b/tools/mq_editor/viewer.js index c57d693..5065498 100644 --- a/tools/mq_editor/viewer.js +++ b/tools/mq_editor/viewer.js @@ -8,6 +8,8 @@ class SpectrogramViewer { this.audioBuffer = audioBuffer; this.stftCache = stftCache; this.partials = []; + this.frames = []; + this.showPeaks = false; // Fixed time bounds this.t_min = 0; @@ -57,6 +59,15 @@ class SpectrogramViewer { this.render(); } + setFrames(frames) { + this.frames = frames; + } + + togglePeaks() { + this.showPeaks = !this.showPeaks; + this.render(); + } + reset() { this.zoom_factor = 1.0; this.t_center = this.audioBuffer.duration / 2; @@ -94,6 +105,7 @@ class SpectrogramViewer { render() { this.renderSpectrogram(); + if (this.showPeaks) this.renderPeaks(); this.renderPartials(); this.drawAxes(); this.drawPlayhead(); @@ -271,6 +283,31 @@ class SpectrogramViewer { } } + // Render raw peaks from mq_extract (before partial tracking) + renderPeaks() { + const {ctx, canvas, frames} = this; + if (!frames || frames.length === 0) return; + + const timeDuration = this.t_view_max - this.t_view_min; + const freqRange = this.freqEnd - this.freqStart; + + ctx.fillStyle = '#fff'; + + for (const frame of frames) { + const t = frame.time; + if (t < this.t_view_min || t > this.t_view_max) continue; + + const x = (t - this.t_view_min) / timeDuration * canvas.width; + + for (const peak of frame.peaks) { + if (peak.freq < this.freqStart || peak.freq > this.freqEnd) continue; + + const y = canvas.height - (peak.freq - this.freqStart) / freqRange * canvas.height; + ctx.fillRect(x - 1, y - 1, 3, 3); + } + } + } + // Draw control point drawControlPoint(t, v) { if (t < this.t_view_min || t > this.t_view_max) return; -- cgit v1.2.3