Files
whisper_web/tests/audio.test.ts
T

94 lines
3.1 KiB
TypeScript

import { describe, test, expect } from "bun:test";
import {
TARGET_SAMPLE_RATE,
downmixToMono,
resampleLinear,
} from "../src/lib/audio";
// Pure helpers only — no browser APIs, no network, no model weights.
describe("downmixToMono", () => {
test("throws on zero channels", () => {
expect(() => downmixToMono([])).toThrow();
});
test("single channel is copied, not aliased", () => {
const channel = Float32Array.from([0.1, -0.5, 1]);
const mono = downmixToMono([channel]);
expect(Array.from(mono)).toEqual(Array.from(channel));
mono[0] = 42;
expect(channel[0]).toBeCloseTo(0.1);
});
test("averages stereo channels", () => {
const left = Float32Array.from([1, 0, -1, 0.5]);
const right = Float32Array.from([0, 0, 1, 0.5]);
const mono = downmixToMono([left, right]);
expect(Array.from(mono)).toEqual([0.5, 0, 0, 0.5]);
});
test("truncates to the shortest channel", () => {
const a = Float32Array.from([1, 1, 1]);
const b = Float32Array.from([1, 1]);
expect(downmixToMono([a, b]).length).toBe(2);
});
});
describe("resampleLinear", () => {
test("rejects non-positive rates", () => {
const input = new Float32Array(4);
expect(() => resampleLinear(input, 0, 16_000)).toThrow();
expect(() => resampleLinear(input, 44_100, -1)).toThrow();
});
test("identity when rates match (copy, not alias)", () => {
const input = Float32Array.from([0.25, -0.25]);
const out = resampleLinear(input, 16_000, 16_000);
expect(Array.from(out)).toEqual([0.25, -0.25]);
out[0] = 9;
expect(input[0]).toBeCloseTo(0.25);
});
test("empty input stays empty", () => {
expect(resampleLinear(new Float32Array(0), 44_100, 16_000).length).toBe(0);
});
test("output length scales with the rate ratio", () => {
const oneSecondAt48k = new Float32Array(48_000);
const out = resampleLinear(oneSecondAt48k, 48_000, TARGET_SAMPLE_RATE);
expect(out.length).toBe(TARGET_SAMPLE_RATE);
});
test("preserves a constant (DC) signal", () => {
const input = new Float32Array(1000).fill(0.7);
const out = resampleLinear(input, 44_100, 16_000);
for (const sample of out) {
expect(sample).toBeCloseTo(0.7, 5);
}
});
test("downsampling a linear ramp stays a linear ramp", () => {
const n = 800;
const ramp = new Float32Array(n);
for (let i = 0; i < n; i++) ramp[i] = i / (n - 1);
const out = resampleLinear(ramp, 32_000, 16_000);
// Linear interpolation reproduces a linear function exactly at every
// sampled position: out[i] ≈ (i * step) / (n - 1).
const step = 32_000 / 16_000;
for (let i = 0; i < out.length; i++) {
const expected = Math.min((i * step) / (n - 1), 1);
expect(out[i]!).toBeCloseTo(expected, 5);
}
});
test("upsampling interpolates between neighbours", () => {
const input = Float32Array.from([0, 1]);
const out = resampleLinear(input, 1, 2);
expect(out.length).toBe(4);
expect(out[0]).toBeCloseTo(0);
expect(out[1]).toBeCloseTo(0.5);
expect(out[2]).toBeCloseTo(1); // clamped at the last sample
expect(out[3]).toBeCloseTo(1);
});
});