diff options
Diffstat (limited to 'tools/mq_editor/viewer.js')
| -rw-r--r-- | tools/mq_editor/viewer.js | 38 |
1 files changed, 36 insertions, 2 deletions
diff --git a/tools/mq_editor/viewer.js b/tools/mq_editor/viewer.js index c841acb..1ac1afd 100644 --- a/tools/mq_editor/viewer.js +++ b/tools/mq_editor/viewer.js @@ -54,7 +54,9 @@ 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 // Selection and editing this.selectedPartial = -1; @@ -178,6 +180,7 @@ class SpectrogramViewer { selectPartial(index) { this._partialSpecCache = null; + this._partialRangeCache = null; this.selectedPartial = index; this.render(); if (this.onPartialSelect) this.onPartialSelect(index); @@ -690,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); @@ -709,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); @@ -721,12 +730,19 @@ 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, // return {squaredAmp, maxDB, sampleRate, fftSize}. Uses this.synthOpts (forceResonator etc). // freqCurve times are shifted so synthesizeMQ's t=0 aligns with tStart = time − window/2. _computePartialSpectrum(partial, time) { + if (this.onGetSynthOpts) this.synthOpts = this.onGetSynthOpts(); const sampleRate = this.audioBuffer.sampleRate; const FFT_SIZE = 2048; const windowDuration = FFT_SIZE / sampleRate; @@ -771,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() { |
