1import { describe, expect, it } from 'vitest';
2import { decimateResult, lowpass } from '../src/sim/antialias';
3import type { SimResult } from '../src/sim/types';
5const fs = 48000;
7// a result holding only a pickup signal at R fs
8function result(x: Float32Array, R: number): SimResult {
9 return {
10 audio: x,
11 fs: fs * R,
12 frames: new Float32Array(0),
13 frameTimes: new Float64Array(0),
14 energy: new Float64Array(0),
15 balance: new Float64Array(0),
16 dt: 1 / (fs * R),
17 stepsPerSample: 1,
18 omegaMax: 0,
19 wallSeconds: 0,
20 maxStretch: 0,
21 };
22}
24const sine = (f: number, R: number, n: number) => Float32Array.from({ length: n * R }, (_, i) => Math.sin((2 * Math.PI * f * i) / (fs * R)));
26// RMS of the middle half (away from the edges)
27function rms(x: Float32Array): number {
28 const a = Math.floor(x.length / 4);
29 const b = Math.floor((3 * x.length) / 4);
30 let s = 0;
31 for (let i = a; i < b; i++) s += x[i] * x[i];
32 return Math.sqrt(s / (b - a));
33}
35describe('anti-aliased pickup', () => {
36 for (const R of [2, 5]) {
37 it(`R = ${R}: passes the audible band, removes what would fold back`, () => {
38 const h = lowpass(R);
39 expect(h.reduce((s, v) => s + v, 0)).toBeCloseTo(1, 12);
40 const n = 8000;
41 // 1 kHz and 20 kHz pass (to 0.1 dB)
42 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);
43 // 26 and 36 kHz would fold back to 22 and 12 kHz: at least 80 dB down
44 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);
45 });
46 }
48 it('a partial result is a prefix of the complete one', () => {
49 const R = 4;
50 const x = Float32Array.from({ length: 6000 * R }, (_, i) => Math.sin(i * 0.01) * Math.exp(-i / 9000) + 0.3 * Math.sin(i * 1.7));
51 const full = decimateResult(result(x, R), R, true).audio;
52 const part = decimateResult(result(x.slice(0, 3000 * R), R), R, false).audio;
53 expect(part.length).toBeGreaterThan(2800);
54 expect(Array.from(part)).toEqual(Array.from(full.subarray(0, part.length)));
55 });
56});