2import assert from "node:assert/strict";
3import { DECIMATION_SPAN, DELAY_TAPS, decimationKernel, decimationTaps, fractionalDelayTaps } from "../src/model/radiation.ts";
4import { fixture } from "./helpers.ts";
6test("decimation taps equal the Python kernel", () => {
7 const m = fixture.decimation.substeps, taps = decimationTaps(m);
8 assert.equal(fixture.decimation.span, DECIMATION_SPAN);
9 assert.equal(taps.length, DECIMATION_SPAN * m);
10 for (const [w, k, v] of fixture.decimation.taps_sample as [number, number, number][]) assert.ok(Math.abs(taps[w * m + k] - v) < 1e-15, `tap ${w},${k}`);
11 let sum = 0; for (const t of taps) sum += t;
12 assert.ok(Math.abs(sum - 1) < 1e-12);
13});
15test("decimation kernel is a linear-phase low-pass with an 80 dB stopband", () => {
16 const m = 54, fs = 44100, h = decimationKernel(m), L = h.length;
17 const H = (f: number) => { let re = 0, im = 0; for (let i = 0; i < L; i++) { const ph = (-2 * Math.PI * f * (i - L / 2)) / (fs * m); re += h[i] * Math.cos(ph); im += h[i] * Math.sin(ph); } return Math.hypot(re, im); };
18 assert.ok(H(1000) > 0.999 && H(15000) > 0.99 && H(18000) > 0.95);
19 assert.ok(H(22050) < 0.1 && H(24000) < 1e-3 && H(30000) < 1e-4);
20 for (let i = 1; i < L; i++) assert.ok(Math.abs(h[i] - h[L - i]) < 1e-15); // symmetric about d = 0
21});
23test("fractional-delay taps interpolate a band-limited signal", () => {
24 const T = DELAY_TAPS, f = 0.2; // cycles per sample, well inside the band
25 for (const fr of [0, 0.25, 0.5, 0.9]) {
26 const taps = fractionalDelayTaps(fr);
27 assert.equal(taps.length, T);
28 // x(n) = cos(2 pi f n); read x(j - i0 - fr) with i0 = 20 at j = 100 from x(j - i0 - t + T/2 - 1)
29 const j = 100, i0 = 20;
30 let s = 0;
31 for (let t = 0; t < T; t++) s += taps[t] * Math.cos(2 * Math.PI * f * (j - i0 - t + T / 2 - 1));
32 assert.ok(Math.abs(s - Math.cos(2 * Math.PI * f * (j - i0 - fr))) < 2e-3, `fr ${fr}: ${s}`);
33 }
34});