diff options
Diffstat (limited to 'tools/mq_editor/test_fft.html')
| -rw-r--r-- | tools/mq_editor/test_fft.html | 229 |
1 files changed, 0 insertions, 229 deletions
diff --git a/tools/mq_editor/test_fft.html b/tools/mq_editor/test_fft.html deleted file mode 100644 index b4e7f48..0000000 --- a/tools/mq_editor/test_fft.html +++ /dev/null @@ -1,229 +0,0 @@ -<!DOCTYPE html> -<!-- - test_fft.html — Isolated FFT correctness tests for fft.js - Open directly in a browser (no server needed). - - Tests fftForward(), realFFT(), and STFTCache from fft.js. - - Test summary: - 1 DC impulse — all-ones input → bin 0 must equal N - 2 Single tone — 440 Hz pure sine → peak bin matches expected frequency - 3 STFT magnitude — STFTCache.getMagnitudeDB() returns loud value at 440 Hz - 4 Pair equal — 220 + 880 Hz, both peaks found - 5 Pair equal — 440 + 1320 Hz, both peaks found - 6 Pair wide — 300 + 3000 Hz (decade separation), both peaks found - 7 Pair extreme — 100 + 8000 Hz (80× ratio), both peaks found - 8 Pair unequal — 440 Hz + 2000 Hz at 10:1 amplitude, weak peak still found - 9 Triplet chord — C4 (261.63) + E4 (329.63) + G4 (392) major chord - 10 Triplet octaves — 110 + 220 + 440 Hz octave stack - 11 Triplet harmonic — 500 + 1500 + 4500 Hz (1:3:9 ratio) - 12 Triplet unequal — 440 + 880 + 1760 Hz at 1.0 / 0.5 / 0.25 (decaying harmonics) - - Pass criteria: - - Each expected frequency bin is found within ±guard bins (guard ≈ 2 × bin_width). - - Bin width = SR / N = 32000 / 4096 ≈ 7.8 Hz. - - All spectra are drawn as linear-magnitude plots (0..Nyquist on x-axis). - Colored vertical markers show expected frequency positions. ---> -<html> -<head> -<meta charset="utf-8"> -<title>FFT Test</title> -<style> - body { font-family: monospace; background: #111; color: #ccc; padding: 20px; } - h2 { color: #fff; } - canvas { border: 1px solid #444; display: block; margin: 10px 0; } - .pass { color: #4f4; } - .fail { color: #f44; } - pre { background: #222; padding: 10px; } -</style> -</head> -<body> -<h2>FFT Isolation Test</h2> -<pre id="log"></pre> -<canvas id="spectrum" width="800" height="200"></canvas> -<script src="fft.js"></script> -<script> -const log = document.getElementById('log'); -function print(msg, cls) { - const span = document.createElement('span'); - if (cls) span.className = cls; - span.textContent = msg + '\n'; - log.appendChild(span); -} - -// --- Test 1: Single bin impulse (DC) --- -{ - const N = 8; - const real = new Float32Array([1,1,1,1,1,1,1,1]); - const imag = new Float32Array(N); - fftForward(real, imag, N); - const ok = Math.abs(real[0] - 8) < 1e-4 && Math.abs(imag[0]) < 1e-4; - print(`Test 1 DC impulse: real[0]=${real[0].toFixed(4)} ${ok?'PASS':'FAIL'}`, ok?'pass':'fail'); -} - -// --- Test 2: 440 Hz peak detection --- -{ - const SR = 32000; - const N = 2048; - const signal = new Float32Array(N); - for (let i = 0; i < N; ++i) - signal[i] = Math.sin(2 * Math.PI * 440 * i / SR); - - const spectrum = realFFT(signal); - - let peakBin = 0, peakVal = 0; - for (let i = 0; i < N / 2; ++i) { - const re = spectrum[i * 2], im = spectrum[i * 2 + 1]; - const mag = re * re + im * im; - if (mag > peakVal) { peakVal = mag; peakBin = i; } - } - const peakFreq = peakBin * SR / N; - const ok = Math.abs(peakFreq - 440) < SR / N; - print(`Test 2 440Hz peak: bin=${peakBin} freq=${peakFreq.toFixed(1)}Hz ${ok?'PASS':'FAIL'}`, ok?'pass':'fail'); - - // Draw spectrum - const canvas = document.getElementById('spectrum'); - const ctx = canvas.getContext('2d'); - ctx.fillStyle = '#111'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.strokeStyle = '#0af'; - ctx.beginPath(); - const halfN = N / 2; - for (let i = 0; i < halfN; ++i) { - const re = spectrum[i * 2], im = spectrum[i * 2 + 1]; - const mag = Math.sqrt(re * re + im * im) / (N / 2); - const x = i / halfN * canvas.width; - const y = canvas.height - mag * canvas.height; - i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); - } - ctx.stroke(); - - // Mark 440Hz - ctx.strokeStyle = '#f80'; - ctx.beginPath(); - const x440 = peakBin / halfN * canvas.width; - ctx.moveTo(x440, 0); ctx.lineTo(x440, canvas.height); - ctx.stroke(); - ctx.fillStyle = '#f80'; - ctx.fillText(`440Hz (bin ${peakBin})`, x440 + 4, 16); -} - -// --- Test 3: STFT getMagnitudeDB at t=0 --- -{ - const SR = 32000; - const N = 44032; // ~1.375s - const signal = new Float32Array(N); - for (let i = 0; i < N; ++i) - signal[i] = Math.sin(2 * Math.PI * 440 * i / SR); - - const stft = new STFTCache(signal, SR, 2048, 512); - const db = stft.getMagnitudeDB(0.0, 440); - const ok = db > -10; - print(`Test 3 STFT getMagnitudeDB(0, 440Hz): ${db.toFixed(1)} dB ${ok?'PASS':'FAIL'}`, ok?'pass':'fail'); -} - -// Helper: find top-K peaks in spectrum (bin indices), ignoring neighbors within 'guard' bins -function findPeaks(spectrum, halfN, k, guard) { - const mags = new Float32Array(halfN); - for (let i = 0; i < halfN; ++i) { - const re = spectrum[i * 2], im = spectrum[i * 2 + 1]; - mags[i] = re * re + im * im; - } - const peaks = []; - const used = new Uint8Array(halfN); - for (let p = 0; p < k; ++p) { - let best = -1, bestVal = 0; - for (let i = 1; i < halfN; ++i) { - if (!used[i] && mags[i] > bestVal) { bestVal = mags[i]; best = i; } - } - if (best < 0) break; - peaks.push(best); - for (let g = Math.max(0, best - guard); g <= Math.min(halfN - 1, best + guard); ++g) - used[g] = 1; - } - return peaks; -} - -// Helper: draw labeled spectrum on a new canvas -function drawSpectrum(label, spectrum, N, SR, markerFreqs) { - const halfN = N / 2; - const colors = ['#f80', '#0f8', '#f0f', '#08f']; - const canvas = document.createElement('canvas'); - canvas.width = 800; canvas.height = 160; - const div = document.createElement('div'); - div.style.cssText = 'color:#888;font-family:monospace;font-size:12px;margin-top:8px'; - div.textContent = label; - document.body.appendChild(div); - document.body.appendChild(canvas); - - const ctx = canvas.getContext('2d'); - ctx.fillStyle = '#111'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.strokeStyle = '#0af'; - ctx.beginPath(); - for (let i = 0; i < halfN; ++i) { - const re = spectrum[i * 2], im = spectrum[i * 2 + 1]; - const mag = Math.sqrt(re * re + im * im) / (N / 2); - const x = i / halfN * canvas.width; - const y = canvas.height - mag * canvas.height; - i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); - } - ctx.stroke(); - - markerFreqs.forEach((f, idx) => { - const bin = Math.round(f * N / SR); - const x = bin / halfN * canvas.width; - ctx.strokeStyle = colors[idx % colors.length]; - ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); - ctx.fillStyle = colors[idx % colors.length]; - ctx.fillText(`${f}Hz`, x + 3, 14 + idx * 14); - }); -} - -// Helper: test multi-frequency signal, return pass/fail -function testMultiFreq(label, freqs, amplitudes, SR, N, testNum) { - const signal = new Float32Array(N); - for (let i = 0; i < N; ++i) - for (let f = 0; f < freqs.length; ++f) - signal[i] += (amplitudes ? amplitudes[f] : 1.0) * Math.sin(2 * Math.PI * freqs[f] * i / SR); - - const spectrum = realFFT(signal); - const halfN = N / 2; - const guard = Math.ceil(SR / N * 2); // ~2 bins tolerance - const peaks = findPeaks(spectrum, halfN, freqs.length, guard); - - const detectedFreqs = peaks.map(b => (b * SR / N).toFixed(1)); - const allFound = freqs.every(f => { - const expectedBin = Math.round(f * N / SR); - return peaks.some(b => Math.abs(b - expectedBin) <= guard); - }); - - const freqStr = freqs.map((f, i) => amplitudes ? `${f}Hz@${amplitudes[i].toFixed(1)}` : `${f}Hz`).join(' + '); - print(`Test ${testNum} ${label} [${freqStr}]: peaks=[${detectedFreqs.join(', ')}] ${allFound?'PASS':'FAIL'}`, - allFound ? 'pass' : 'fail'); - - drawSpectrum(`Test ${testNum}: ${label} — ${freqStr}`, spectrum, N, SR, freqs); - return allFound; -} - -const SR = 32000, N = 4096; - -// --- Pairs --- -print('\n-- Pairs --'); -testMultiFreq('pair', [220, 880], null, SR, N, 4); -testMultiFreq('pair', [440, 1320], null, SR, N, 5); -testMultiFreq('pair', [300, 3000], null, SR, N, 6); -testMultiFreq('pair', [100, 8000], null, SR, N, 7); -testMultiFreq('pair unequal amp', [440, 2000], [1.0, 0.1], SR, N, 8); - -// --- Triplets --- -print('\n-- Triplets --'); -testMultiFreq('triplet', [261.63, 329.63, 392.00], null, SR, N, 9); // C E G chord -testMultiFreq('triplet', [110, 220, 440], null, SR, N, 10); // octave stack -testMultiFreq('triplet', [500, 1500, 4500], null, SR, N, 11); // harmonic series -testMultiFreq('triplet unequal', [440, 880, 1760], [1.0, 0.5, 0.25],SR, N, 12); // decaying harmonics -</script> -</body> -</html> |
