denpa-radio/streamer/tests/fft.test.ts

42 lines
1.5 KiB
TypeScript

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 200hz sine puts most energy in low bars (logarithmic binning)", () => {
const a = new SpectrumAnalyzer({ bars: 48, sampleRate: 48000 });
for (let n = 0; n < 4; n++) a.feed(sineFrame(200));
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(0.1);
expect(lowSum).toBeGreaterThan(highSum + 0.1);
});
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));
});
});