import { describe, expect, it } from "vitest"; import { SpectrumAnalyzer } from "../src/fft.ts"; function sineFrame(freqHz: number, sampleRate = 48000, samples = 1024): Buffer { const buf = Buffer.alloc(samples * 2 * 2); // s16le stereo for (let i = 0; i < samples; i++) { const v = Math.round(Math.sin((2 * Math.PI * freqHz * i) / sampleRate) * 16000); buf.writeInt16LE(v, i * 4); buf.writeInt16LE(v, i * 4 + 2); } return buf; } describe("SpectrumAnalyzer", () => { it("produces N bars of normalized [0..1] floats", () => { const a = new SpectrumAnalyzer({ bars: 48, sampleRate: 48000 }); a.feed(sineFrame(1000)); const bars = a.bars(); expect(bars).toHaveLength(48); bars.forEach((v) => { expect(v).toBeGreaterThanOrEqual(0); expect(v).toBeLessThanOrEqual(1); }); }); it("a 1khz sine puts most energy in low bars (logarithmic binning)", () => { const a = new SpectrumAnalyzer({ bars: 48, sampleRate: 48000 }); a.feed(sineFrame(1000)); const bars = a.bars(); const lowSum = bars.slice(0, 16).reduce((s, v) => s + v, 0); const highSum = bars.slice(32).reduce((s, v) => s + v, 0); expect(lowSum).toBeGreaterThan(highSum * 5); }); it("silence produces all-near-zero bars", () => { const a = new SpectrumAnalyzer({ bars: 72, sampleRate: 48000 }); a.feed(Buffer.alloc(1024 * 4)); const bars = a.bars(); bars.forEach((v) => expect(v).toBeLessThan(0.01)); }); });