import test from "node:test"; import assert from "node:assert/strict"; import { Stft, fft } from "../src/analysis/spectrogram.ts"; import { encodeWav, scaleForPlayback } from "../src/audio/player.ts"; test("FFT of a sine peaks at the right bin", () => { const n = 1024, re = new Float32Array(n), im = new Float32Array(n); for (let i = 0; i < n; i++) re[i] = Math.sin((2 * Math.PI * 64 * i) / n); fft(re, im); let best = 0; for (let b = 1; b < n / 2; b++) if (Math.hypot(re[b], im[b]) > Math.hypot(re[best], im[best])) best = b; assert.equal(best, 64); assert.ok(Math.abs(Math.hypot(re[64], im[64]) - n / 2) < 1e-2); }); test("incremental STFT tracks a chirp's centroid", () => { const fs = 44100, st = new Stft(fs, 1024, 256); const sig = new Float32Array(fs); for (let i = 0; i < fs; i++) { const t = i / fs; sig[i] = Math.sin(2 * Math.PI * (500 * t + 2000 * t * t)); } let cols = 0; for (let s = 0; s < fs; s += 1000) cols += st.push(sig.subarray(s, Math.min(fs, s + 1000))); assert.equal(cols, st.columns.length); assert.ok(cols > 150); const early = st.centroid[5], late = st.centroid[cols - 5]; assert.ok(early > 400 && early < 1200 && late > 3500 && late < 5500, `${early} ${late}`); }); test("WAV encoding and playback scaling", () => { const p = new Float32Array([0, 2, -4, 1]); const s = scaleForPlayback(p, true); assert.ok(Math.abs(Math.max(...Array.from(s).map(Math.abs)) - 0.9) < 1e-6); const blob = encodeWav(s, 44100); assert.equal(blob.size, 44 + 8); }); test("band summary reports staggered onsets for a chirp", async () => { const { bandSummary } = await import("../src/analysis/spectrogram.ts"); const fs = 44100, st = new Stft(fs, 1024, 256), sig = new Float32Array(fs); for (let i = 0; i < fs; i++) { const t = i / fs; sig[i] = Math.sin(2 * Math.PI * (200 * t + 4000 * t * t)); } st.push(sig); const s = bandSummary(st); const b250 = s.bands.find((b) => b.lo === 250)!, b2k = s.bands.find((b) => b.lo === 2000)!; assert.ok(b250.onsetMs < b2k.onsetMs, `${b250.onsetMs} vs ${b2k.onsetMs}`); assert.ok(s.centroidHz.length > 3 && s.centroidHz[0].hz < s.centroidHz[s.centroidHz.length - 1].hz); });