summaryrefslogtreecommitdiff
path: root/tools/mq_editor/viewer.js
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-19 06:46:57 +0100
committerskal <pascal.massimino@gmail.com>2026-02-19 06:46:57 +0100
commit83eec3cece795f56f4edc1298a008216cb9511a0 (patch)
treeab55930912d50b063838f92a2314598d6be0d291 /tools/mq_editor/viewer.js
parentc804808870cf3775362c02e40ea7d3d082ed0d91 (diff)
feat(mq_editor): UI revamp — params panel, layout, partial spectrum
- Move Synthesis controls (integratePhase, jitter, spread, resonator, LP/HP filters) and Auto Spread All into the ⚙ Params dropdown - Group Extract Partials / +Partial / ✕ Clear All in one toolbar group - Add per-partial Sine/Res mode toggle in the property panel (Mode row) - Move partial mini-spectrum below the right panel (right-col layout) - Partial mini-spectrum: dynamic dB range scanned across full duration (8 samples, [peak−60, peak]), cached on partial select - Print bezier amplitude A= in red at top-right of partial spectrum - Status/info messages set to 80% gray (#ccc) handoff(Claude): UI revamp complete, TODO items implemented. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'tools/mq_editor/viewer.js')
-rw-r--r--tools/mq_editor/viewer.js36
1 files changed, 34 insertions, 2 deletions
diff --git a/tools/mq_editor/viewer.js b/tools/mq_editor/viewer.js
index 4744b96..1ac1afd 100644
--- a/tools/mq_editor/viewer.js
+++ b/tools/mq_editor/viewer.js
@@ -54,6 +54,7 @@ class SpectrogramViewer {
this.partialSpectrumCanvas = document.getElementById('partialSpectrumCanvas');
this.partialSpectrumCtx = this.partialSpectrumCanvas ? this.partialSpectrumCanvas.getContext('2d') : null;
this._partialSpecCache = null; // {partialIndex, time, specData?} — see renderPartialSpectrum
+ this._partialRangeCache = null; // {partialIndex, dbMin, dbMax} — scanned across full partial duration
this.synthOpts = {}; // synth options forwarded to synthesizeMQ (forceResonator, etc.)
this.onGetSynthOpts = null; // callback() → opts; called before each spectrum compute
@@ -179,6 +180,7 @@ class SpectrogramViewer {
selectPartial(index) {
this._partialSpecCache = null;
+ this._partialRangeCache = null;
this.selectedPartial = index;
this.render();
if (this.onPartialSelect) this.onPartialSelect(index);
@@ -691,7 +693,13 @@ class SpectrogramViewer {
const specData = this._computePartialSpectrum(partial, specTime);
this._partialSpecCache = {partialIndex: p, time: specTime, specData};
- const {squaredAmp, maxDB, sampleRate, fftSize} = specData;
+ // dB range: scanned across full partial duration, cached per partial
+ if (!this._partialRangeCache || this._partialRangeCache.partialIndex !== p) {
+ this._partialRangeCache = this._computePartialRange(p, partial);
+ }
+ const {dbMin: DB_MIN, dbMax: DB_MAX} = this._partialRangeCache;
+
+ const {squaredAmp, sampleRate, fftSize} = specData;
const numBins = fftSize / 2;
const binWidth = sampleRate / fftSize;
const color = this.partialColor(p);
@@ -710,7 +718,7 @@ class SpectrogramViewer {
for (let b = bStart; b <= bEnd; ++b) if (squaredAmp[b] > maxSq) maxSq = squaredAmp[b];
const magDB = 10 * Math.log10(Math.max(maxSq, 1e-20));
- const barH = Math.round(this.normalizeDB(magDB, maxDB) * (height - 12));
+ const barH = Math.round(Math.max(0, Math.min(1, (magDB - DB_MIN) / (DB_MAX - DB_MIN))) * (height - 12));
if (barH <= 0) continue;
const grad = ctx.createLinearGradient(0, height - barH, 0, height);
@@ -722,6 +730,12 @@ class SpectrogramViewer {
ctx.fillStyle = color;
ctx.fillText('P#' + p + ' @' + specTime.toFixed(3) + 's', 4, 10);
+
+ const amp = evalBezierAmp(curve, specTime);
+ ctx.fillStyle = '#f44';
+ ctx.textAlign = 'right';
+ ctx.fillText('A=' + amp.toFixed(3), width - 3, 10);
+ ctx.textAlign = 'left';
}
// Synthesise a 2048-sample Hann-windowed frame of `partial` centred on `time`, run FFT,
@@ -773,6 +787,24 @@ class SpectrogramViewer {
return {squaredAmp, maxDB, sampleRate, fftSize: FFT_SIZE};
}
+ // Scan the partial across its full duration to find the peak dB level, then derive
+ // [dbMin, dbMax] as [peak − 60, peak]. Cached per partialIndex; only called once on select.
+ _computePartialRange(partialIndex, partial) {
+ const fc = partial.freqCurve;
+ if (!fc) return {partialIndex, dbMin: -60, dbMax: 0};
+ const N = 8;
+ let globalMaxSq = 1e-20;
+ for (let i = 0; i < N; ++i) {
+ const t = fc.t0 + (fc.t3 - fc.t0) * (i + 0.5) / N;
+ const {squaredAmp} = this._computePartialSpectrum(partial, t);
+ for (let b = 0; b < squaredAmp.length; ++b) {
+ if (squaredAmp[b] > globalMaxSq) globalMaxSq = squaredAmp[b];
+ }
+ }
+ const dbMax = 10 * Math.log10(globalMaxSq);
+ return {partialIndex, dbMin: dbMax - 60, dbMax};
+ }
+
// --- View management ---
updateViewBounds() {