summaryrefslogtreecommitdiff
path: root/tools/mq_editor/fft.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/mq_editor/fft.js')
-rw-r--r--tools/mq_editor/fft.js86
1 files changed, 86 insertions, 0 deletions
diff --git a/tools/mq_editor/fft.js b/tools/mq_editor/fft.js
index 8610222..0668906 100644
--- a/tools/mq_editor/fft.js
+++ b/tools/mq_editor/fft.js
@@ -101,3 +101,89 @@ function realFFT(signal) {
return spectrum;
}
+
+// STFT Cache - Pre-computes and caches windowed FFT frames
+class STFTCache {
+ constructor(signal, sampleRate, fftSize, hopSize) {
+ this.signal = signal;
+ this.sampleRate = sampleRate;
+ this.fftSize = fftSize;
+ this.hopSize = hopSize;
+ this.frames = []; // Array of {time, offset, spectrum}
+ this.compute();
+ }
+
+ compute() {
+ this.frames = [];
+ const numFrames = Math.floor((this.signal.length - this.fftSize) / this.hopSize);
+
+ for (let frameIdx = 0; frameIdx < numFrames; ++frameIdx) {
+ const offset = frameIdx * this.hopSize;
+ const time = offset / this.sampleRate;
+
+ // Extract frame
+ const frame = this.signal.slice(offset, offset + this.fftSize);
+
+ // Apply Hann window
+ const windowed = new Float32Array(this.fftSize);
+ for (let i = 0; i < this.fftSize; ++i) {
+ const w = 0.5 - 0.5 * Math.cos(2 * Math.PI * i / this.fftSize);
+ windowed[i] = frame[i] * w;
+ }
+
+ // Compute FFT
+ const spectrum = realFFT(windowed);
+
+ this.frames.push({time, offset, spectrum});
+ }
+ }
+
+ setHopSize(hopSize) {
+ if (hopSize === this.hopSize) return;
+ this.hopSize = hopSize;
+ this.compute();
+ }
+
+ setFFTSize(fftSize) {
+ if (fftSize === this.fftSize) return;
+ this.fftSize = fftSize;
+ this.compute();
+ }
+
+ getNumFrames() {
+ return this.frames.length;
+ }
+
+ getFrameAtIndex(frameIdx) {
+ if (frameIdx < 0 || frameIdx >= this.frames.length) return null;
+ return this.frames[frameIdx];
+ }
+
+ getFrameAtTime(t) {
+ if (this.frames.length === 0) return null;
+
+ // Find closest frame
+ const frameIdx = Math.floor(t * this.sampleRate / this.hopSize);
+ return this.getFrameAtIndex(frameIdx);
+ }
+
+ getFFT(t) {
+ const frame = this.getFrameAtTime(t);
+ return frame ? frame.spectrum : null;
+ }
+
+ // Get magnitude in dB at specific time and frequency
+ getMagnitudeDB(t, freq) {
+ const spectrum = this.getFFT(t);
+ if (!spectrum) return -80;
+
+ const bin = Math.round(freq * this.fftSize / this.sampleRate);
+ if (bin < 0 || bin >= this.fftSize / 2) return -80;
+
+ const re = spectrum[bin * 2];
+ const im = spectrum[bin * 2 + 1];
+ const mag = Math.sqrt(re * re + im * im);
+ return 20 * Math.log10(Math.max(mag, 1e-10));
+ }
+}
+