diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/mq_editor/app.js | 12 | ||||
| -rw-r--r-- | tools/mq_editor/viewer.js | 61 |
2 files changed, 44 insertions, 29 deletions
diff --git a/tools/mq_editor/app.js b/tools/mq_editor/app.js index 9df00fb..d54e5f3 100644 --- a/tools/mq_editor/app.js +++ b/tools/mq_editor/app.js @@ -46,16 +46,28 @@ document.getElementById('hpK2').addEventListener('input', function() { document.getElementById('hpK2Val').textContent = k >= 1.0 ? 'bypass' : fmtHz(f); }); +function invalidatePartialSpectrum() { + if (!viewer) return; + viewer.synthOpts = getSynthParams().opts; + viewer._partialSpecCache = null; + viewer.renderPartialSpectrum(viewer.spectrumTime, true); +} + // Show/hide global resonator params when forceResonator toggled document.getElementById('forceResonator').addEventListener('change', function() { document.getElementById('globalResParams').style.display = this.checked ? '' : 'none'; + invalidatePartialSpectrum(); }); document.getElementById('globalR').addEventListener('input', function() { document.getElementById('globalRVal').textContent = parseFloat(this.value).toFixed(4); + invalidatePartialSpectrum(); }); document.getElementById('globalGain').addEventListener('input', function() { document.getElementById('globalGainVal').textContent = parseFloat(this.value).toFixed(2); + invalidatePartialSpectrum(); }); +document.getElementById('forceRGain').addEventListener('change', invalidatePartialSpectrum); + let audioBuffer = null; let viewer = null; let audioContext = null; diff --git a/tools/mq_editor/viewer.js b/tools/mq_editor/viewer.js index 82e9c24..1ed609c 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.synthOpts = {}; // synth options forwarded to synthesizeMQ (forceResonator, etc.) // Selection and editing this.selectedPartial = -1; @@ -646,10 +647,10 @@ class SpectrogramViewer { ctx.fillText(useSynth ? 'SYNTH [a]' : 'ORIG [a]', 4, 10); } - // Draw synthesized power spectrum of the selected partial at `time` into partialSpectrumCanvas. + // Draw synthesized power spectrum of the selected partial into partialSpectrumCanvas. // X-axis: log frequency (same scale as main view). Y-axis: dB (normalised to peak). - // force=true bypasses cache — used by render() when params change. - // Otherwise cached on {partialIndex, time} for mouse-move performance. + // specTime = mouse time if inside partial's [t0,t3], else center of partial's interval. + // Cached on {partialIndex, specTime}; force=true bypasses cache (param changes, synth toggle). renderPartialSpectrum(time, force = false) { const ctx = this.partialSpectrumCtx; if (!ctx) return; @@ -659,34 +660,36 @@ class SpectrogramViewer { const height = canvas.height; const p = this.selectedPartial; - // Cache check — skip if same partial+time unless forced by param change + const showMsg = (msg) => { + ctx.fillStyle = '#1e1e1e'; ctx.fillRect(0, 0, width, height); + ctx.font = '9px monospace'; ctx.fillStyle = '#333'; + ctx.fillText(msg, 4, height / 2 + 4); + this._partialSpecCache = null; + }; + + if (p < 0 || !this.partials || p >= this.partials.length) { showMsg('no partial'); return; } + + const partial = this.partials[p]; + const curve = partial.freqCurve; + if (!curve) { showMsg('no curve'); return; } + + // Use mouse time if inside partial's window, else center of partial + const specTime = (time >= curve.t0 && time <= curve.t3) + ? time + : (curve.t0 + curve.t3) / 2; + + // Cache check — must happen before clearing the canvas if (!force && this._partialSpecCache && this._partialSpecCache.partialIndex === p && - this._partialSpecCache.time === time) return; + this._partialSpecCache.time === specTime) return; ctx.fillStyle = '#1e1e1e'; ctx.fillRect(0, 0, width, height); ctx.font = '9px monospace'; - if (p < 0 || !this.partials || p >= this.partials.length) { - ctx.fillStyle = '#333'; - ctx.fillText('no partial', 4, height / 2 + 4); - this._partialSpecCache = {partialIndex: p, time}; - return; - } - - const partial = this.partials[p]; - const curve = partial.freqCurve; - if (!curve || time < curve.t0 || time > curve.t3) { - ctx.fillStyle = '#333'; - ctx.fillText('out of range', 4, height / 2 + 4); - this._partialSpecCache = {partialIndex: p, time}; - return; - } - // Synthesize window → FFT → power spectrum - const specData = this._computePartialSpectrum(partial, time); - this._partialSpecCache = {partialIndex: p, time, specData}; + const specData = this._computePartialSpectrum(partial, specTime); + this._partialSpecCache = {partialIndex: p, time: specTime, specData}; const {squaredAmp, maxDB, sampleRate, fftSize} = specData; const numBins = fftSize / 2; @@ -718,12 +721,12 @@ class SpectrogramViewer { } ctx.fillStyle = color; - ctx.fillText('P#' + p + ' @' + time.toFixed(3) + 's', 4, 10); + ctx.fillText('P#' + p + ' @' + specTime.toFixed(3) + 's', 4, 10); } - // Synthesise a 2048-sample Hann-windowed frame of `partial` centred on `time`, - // run FFT, and return {squaredAmp, maxDB, sampleRate, fftSize}. - // freqCurve times are shifted so synthesizeMQ's t=0 aligns with tStart = time - window/2. + // 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) { const sampleRate = this.audioBuffer.sampleRate; const FFT_SIZE = 2048; @@ -742,7 +745,7 @@ class SpectrogramViewer { }, }; - const pcm = synthesizeMQ([shiftedPartial], sampleRate, windowDuration, true, {}); + const pcm = synthesizeMQ([shiftedPartial], sampleRate, windowDuration, true, this.synthOpts); // Hann window for (let i = 0; i < FFT_SIZE; ++i) { @@ -753,7 +756,7 @@ class SpectrogramViewer { const real = new Float32Array(FFT_SIZE); const imag = new Float32Array(FFT_SIZE); for (let i = 0; i < FFT_SIZE; ++i) real[i] = pcm[i]; - fftRadix2(real, imag, FFT_SIZE, 1); + fftForward(real, imag, FFT_SIZE); // Power spectrum const squaredAmp = new Float32Array(FFT_SIZE / 2); |
