diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-19 06:46:57 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-19 06:46:57 +0100 |
| commit | 83eec3cece795f56f4edc1298a008216cb9511a0 (patch) | |
| tree | ab55930912d50b063838f92a2314598d6be0d291 /tools/mq_editor/viewer.js | |
| parent | c804808870cf3775362c02e40ea7d3d082ed0d91 (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.js | 36 |
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() { |
