From f43169185592a85d5bd30b9699671eb08a39dfda Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 18 Feb 2026 05:27:28 +0100 Subject: feat(mq_editor): log-scale frequency axis with clean freqToY/canvasToFreq API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all inline linear freq→Y math with freqToY()/canvasToFreq() pair. freqStart set to 20Hz (log-safe). Freq axis ticks now log-spaced. handoff(Gemini): log scale done, all render paths use freqToY/canvasToFreq --- tools/mq_editor/viewer.js | 50 +++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 23 deletions(-) (limited to 'tools/mq_editor/viewer.js') diff --git a/tools/mq_editor/viewer.js b/tools/mq_editor/viewer.js index 3ca2f87..b267806 100644 --- a/tools/mq_editor/viewer.js +++ b/tools/mq_editor/viewer.js @@ -23,8 +23,8 @@ class SpectrogramViewer { this.t_view_min = 0; this.t_view_max = audioBuffer.duration; - // Fixed frequency bounds - this.freqStart = 0; + // Fixed frequency bounds (log scale: freqStart must be > 0) + this.freqStart = 20; this.freqEnd = 16000; // Tooltip @@ -179,7 +179,6 @@ class SpectrogramViewer { // Draw frequency bins const numBins = fftSize / 2; const binFreqWidth = sampleRate / fftSize; - const freqNorm = 1. / (this.freqEnd - this.freqStart); for (let bin = 0; bin < numBins; ++bin) { const freq = bin * binFreqWidth; @@ -193,10 +192,8 @@ class SpectrogramViewer { // Power law for better peak visibility const intensity = Math.pow(clamped, 2.); - const freqNorm0 = (freq - this.freqStart) * freqNorm; - const freqNorm1 = (freqNext - this.freqStart) * freqNorm; - const y0 = Math.floor(height - freqNorm1 * height); - const y1 = Math.floor(height - freqNorm0 * height); + const y0 = Math.floor(this.freqToY(freqNext)); + const y1 = Math.floor(this.freqToY(Math.max(freq, this.freqStart))); const binHeight = Math.max(1, y1 - y0); const color = this.getSpectrogramColor(intensity); @@ -218,7 +215,6 @@ class SpectrogramViewer { ]; const timeDuration = this.t_view_max - this.t_view_min; - const freqRange = this.freqEnd - this.freqStart; for (let p = 0; p < partials.length; ++p) { const partial = partials[p]; @@ -239,7 +235,7 @@ class SpectrogramViewer { if (f < this.freqStart || f > this.freqEnd) continue; const x = (t - this.t_view_min) / timeDuration * width; - const y = height - (f - this.freqStart) / freqRange * height; + const y = this.freqToY(f); if (!started) { ctx.moveTo(x, y); @@ -269,7 +265,7 @@ class SpectrogramViewer { if (freq < this.freqStart || freq > this.freqEnd) continue; const x = (t - this.t_view_min) / timeDuration * width; - const y = height - (freq - this.freqStart) / freqRange * height; + const y = this.freqToY(freq); if (!started) { ctx.moveTo(x, y); @@ -299,7 +295,6 @@ class SpectrogramViewer { 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'; @@ -312,7 +307,7 @@ class SpectrogramViewer { 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; + const y = this.freqToY(peak.freq); ctx.fillRect(x - 1, y - 1, 3, 3); } } @@ -324,10 +319,9 @@ class SpectrogramViewer { if (v < this.freqStart || v > this.freqEnd) return; const timeDuration = this.t_view_max - this.t_view_min; - const freqRange = this.freqEnd - this.freqStart; const x = (t - this.t_view_min) / timeDuration * this.canvas.width; - const y = this.canvas.height - (v - this.freqStart) / freqRange * this.canvas.height; + const y = this.freqToY(v); this.ctx.beginPath(); this.ctx.arc(x, y, 4, 0, 2 * Math.PI); @@ -350,7 +344,6 @@ class SpectrogramViewer { ctx.lineWidth = 1; const timeDuration = this.t_view_max - this.t_view_min; - const freqRange = this.freqEnd - this.freqStart; // Time axis const timeStep = this.getAxisStep(timeDuration); @@ -367,20 +360,19 @@ class SpectrogramViewer { t += timeStep; } - // Frequency axis - const freqStep = this.getAxisStep(freqRange); - let f = Math.ceil(this.freqStart / freqStep) * freqStep; - while (f <= this.freqEnd) { - const y = height - (f - this.freqStart) / freqRange * height; + // Frequency axis (log-spaced ticks) + const freqTicks = [20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 16000]; + for (const f of freqTicks) { + if (f < this.freqStart || f > this.freqEnd) continue; + const y = this.freqToY(f); ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); - const label = f >= 1000 ? (f/1000).toFixed(1) + 'k' : f.toFixed(0); + const label = f >= 1000 ? (f/1000).toFixed(0) + 'k' : f.toFixed(0); ctx.fillText(label + 'Hz', 2, y - 2); - f += freqStep; } } @@ -457,8 +449,20 @@ class SpectrogramViewer { return this.t_view_min + (x / this.canvas.width) * (this.t_view_max - this.t_view_min); } + // freq -> canvas Y (log scale) + freqToY(freq) { + const logMin = Math.log2(this.freqStart); + const logMax = Math.log2(this.freqEnd); + const norm = (Math.log2(Math.max(freq, this.freqStart)) - logMin) / (logMax - logMin); + return this.canvas.height * (1 - norm); + } + + // canvas Y -> freq (log scale, inverse of freqToY) canvasToFreq(y) { - return this.freqEnd - (y / this.canvas.height) * (this.freqEnd - this.freqStart); + const logMin = Math.log2(this.freqStart); + const logMax = Math.log2(this.freqEnd); + const norm = 1 - (y / this.canvas.height); + return Math.pow(2, logMin + norm * (logMax - logMin)); } getIntensityAt(time, freq) { -- cgit v1.2.3