summaryrefslogtreecommitdiff
path: root/tools/mq_editor/viewer.js
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-18 05:27:28 +0100
committerskal <pascal.massimino@gmail.com>2026-02-18 05:27:28 +0100
commitf43169185592a85d5bd30b9699671eb08a39dfda (patch)
tree20db612bad3418fd888aa93f267f02e4351eafc2 /tools/mq_editor/viewer.js
parent1d3039f3e2e9269e69364a9b6da90fd28b36c18f (diff)
feat(mq_editor): log-scale frequency axis with clean freqToY/canvasToFreq API
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
Diffstat (limited to 'tools/mq_editor/viewer.js')
-rw-r--r--tools/mq_editor/viewer.js50
1 files changed, 27 insertions, 23 deletions
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) {