import { describe, expect, it } from 'vitest'; import { decimateResult, lowpass } from '../src/sim/antialias'; import type { SimResult } from '../src/sim/types'; const fs = 48000; // a result holding only a pickup signal at R fs function result(x: Float32Array, R: number): SimResult { return { audio: x, fs: fs * R, frames: new Float32Array(0), frameTimes: new Float64Array(0), energy: new Float64Array(0), balance: new Float64Array(0), dt: 1 / (fs * R), stepsPerSample: 1, omegaMax: 0, wallSeconds: 0, maxStretch: 0, }; } const sine = (f: number, R: number, n: number) => Float32Array.from({ length: n * R }, (_, i) => Math.sin((2 * Math.PI * f * i) / (fs * R))); // RMS of the middle half (away from the edges) function rms(x: Float32Array): number { const a = Math.floor(x.length / 4); const b = Math.floor((3 * x.length) / 4); let s = 0; for (let i = a; i < b; i++) s += x[i] * x[i]; return Math.sqrt(s / (b - a)); } describe('anti-aliased pickup', () => { for (const R of [2, 5]) { it(`R = ${R}: passes the audible band, removes what would fold back`, () => { const h = lowpass(R); expect(h.reduce((s, v) => s + v, 0)).toBeCloseTo(1, 12); const n = 8000; // 1 kHz and 20 kHz pass (to 0.1 dB) for (const f of [1000, 20000]) expect(Math.abs(20 * Math.log10(rms(decimateResult(result(sine(f, R, n), R), R, true).audio) / Math.SQRT1_2))).toBeLessThan(0.1); // 26 and 36 kHz would fold back to 22 and 12 kHz: at least 80 dB down for (const f of [26000, 36000]) expect(20 * Math.log10(rms(decimateResult(result(sine(f, R, n), R), R, true).audio) / Math.SQRT1_2)).toBeLessThan(-80); }); } it('a partial result is a prefix of the complete one', () => { const R = 4; const x = Float32Array.from({ length: 6000 * R }, (_, i) => Math.sin(i * 0.01) * Math.exp(-i / 9000) + 0.3 * Math.sin(i * 1.7)); const full = decimateResult(result(x, R), R, true).audio; const part = decimateResult(result(x.slice(0, 3000 * R), R), R, false).audio; expect(part.length).toBeGreaterThan(2800); expect(Array.from(part)).toEqual(Array.from(full.subarray(0, part.length))); }); });