import { test } from "node:test"; import assert from "node:assert/strict"; import { build } from "esbuild"; const compiled = await build({ entryPoints: ["client/src/lib/aiFir.ts"], bundle: true, write: false, format: "esm", platform: "node", }); const fir = await import( "data:text/javascript;base64," + Buffer.from(compiled.outputFiles[0].text).toString("base64") ); function pair(n = 8192) { const left = Array(n).fill(0), right = Array(n).fill(0); left[10] = 0.4; right[12] = 0.2; left[200] = 0.1; right[202] = 0.05; left[0] = -0; return { left, right }; } function adjustment(extra = {}) { return { snapshot_id: "snapshot", name: "test", explanation: "Less late energy", gain_db: 0, tail_gain_db: 0, smoothing: 0, ...extra, }; } function storage() { const data = new Map(); return { setItem: (k, v) => data.set(k, v), getItem: k => data.get(k) ?? null, }; } function proposal() { const before = pair(); return { id: "p", device: "device1", before, after: fir.renderFir(before, adjustment({ tail_gain_db: -6 })), adjustment: adjustment({ tail_gain_db: -6 }), created: "now", }; } function binary(values) { const b = new ArrayBuffer(values.length * 4), v = new DataView(b); values.forEach((x, i) => v.setFloat32(i * 4, x, true)); return b; } function mockDevice(p) { const current = { left: binary(p.before.left), right: binary(p.before.right), }; const posts = []; return { posts, current, getFirChannel: async side => current[side].slice(0), uploadFirChannel: async (side, data) => { posts.push(side); current[side] = data.slice(0); }, }; } test("decode validates raw float32 lengths/nonfinite and mismatched ear counts", () => { assert.throws(() => fir.decodeFir(new ArrayBuffer(4))); const b = binary(pair().left); new DataView(b).setFloat32(0, NaN, true); assert.throws(() => fir.decodeFir(b)); assert.throws(() => fir.validatePair({ left: pair().left, right: pair(8191).right }) ); }); test("identity, common attenuation and common envelope keep taps and stereo timing", () => { for (const n of [8191, 8192]) { const p = pair(n), a = fir.renderFir(p, adjustment()); assert.equal(a.left.length, n); assert.equal(a.right.length, n); assert.equal(a.left[10], Math.fround(p.left[10])); assert.equal(a.right[12], Math.fround(p.right[12])); const q = fir.renderFir(p, adjustment({ gain_db: -6, tail_gain_db: -12 })); assert.ok(q.left[200] < q.left[10] / 4); assert.equal(q.left.indexOf(Math.max(...q.left)), 10); assert.equal(q.right.indexOf(Math.max(...q.right)), 12); assert.ok(q.left.every((x, i) => Math.abs(x) <= Math.abs(p.left[i]))); } }); test("bounded transforms reject gain boosts, NaN and out-of-range smoothing", () => { for (const a of [ { gain_db: 1 }, { gain_db: NaN }, { tail_gain_db: -25 }, { smoothing: 2 }, ]) assert.throws(() => fir.renderFir(pair(), adjustment(a))); }); test("smoothing uses documented causal 3 tap kernel", () => { const p = pair(); const q = fir.renderFir(p, adjustment({ smoothing: 1 })); assert.equal(q.left[10], Math.fround(0.1)); assert.equal(q.left[11], Math.fround(0.2)); assert.equal(q.left[12], Math.fround(0.1)); }); test("cache preserves exact float32 bytes including signed zero, isolated by device", () => { const p = proposal(), s = storage(); fir.saveFirBackup(s, { version: 1, proposal: p, state: "pending" }); const loaded = fir.loadFirBackup(s, p.device); assert.ok(Object.is(loaded.proposal.before.left[0], -0)); assert.deepEqual(binary(loaded.proposal.before.left), binary(p.before.left)); assert.equal(fir.loadFirBackup(s, "different"), null); }); test("backup failure or changed device blocks ALL POSTs", async () => { const p = proposal(), d = mockDevice(p); await assert.rejects( fir.applyFirVersion( d, { setItem() { throw Error("quota"); }, getItem() { return null; }, }, { version: 1, proposal: p, state: "pending" }, "B", true ) ); assert.deepEqual(d.posts, []); new DataView(d.current.left).setFloat32(100, 1, true); await assert.rejects( fir.applyFirVersion( d, storage(), { version: 1, proposal: p, state: "pending" }, "B", true ), /FIR_CHANGED/ ); assert.deepEqual(d.posts, []); }); test("backup precedes left/right upload; verified A/B restores byte-exact original without switch writes", async () => { const p = proposal(), s = storage(), d = mockDevice(p); const send = d.uploadFirChannel; d.uploadFirChannel = async (side, data) => { assert.ok(fir.loadFirBackup(s, p.device)); await send(side, data); }; const done = await fir.applyFirVersion( d, s, { version: 1, proposal: p, state: "pending" }, "B", true ); assert.equal(done.state, "B"); assert.deepEqual(d.posts, ["left", "right"]); const restored = await fir.applyFirVersion(d, s, done, "A"); assert.equal(restored.state, "A"); assert.deepEqual(d.current.left, binary(p.before.left)); assert.deepEqual(d.current.right, binary(p.before.right)); }); test("right-ear failure retains original backup and marks incomplete, no automatic retries", async () => { const p = proposal(), s = storage(), d = mockDevice(p), send = d.uploadFirChannel; d.uploadFirChannel = async (side, data) => { if (side === "right") throw Error("502"); await send(side, data); }; await assert.rejects( fir.applyFirVersion( d, s, { version: 1, proposal: p, state: "pending" }, "B", true ) ); const cached = fir.loadFirBackup(s, p.device); assert.equal(cached.state, "incomplete"); assert.deepEqual(binary(cached.proposal.before.left), binary(p.before.left)); assert.deepEqual(d.posts, ["left"]); }); test("readback mismatch does not report success", async () => { const p = proposal(), s = storage(), d = mockDevice(p); d.uploadFirChannel = async () => {}; await assert.rejects( fir.applyFirVersion( d, s, { version: 1, proposal: p, state: "pending" }, "B", true ), /FIR_VERIFY/ ); assert.equal(fir.loadFirBackup(s, p.device).state, "incomplete"); }); test("zero FIR metrics are finite and do not invent sample rate", () => { const p = { left: Array(8192).fill(0), right: Array(8192).fill(0) }; const m = fir.firMetrics(p); assert.equal(m.sample_rate, null); assert.equal(m.left.late_energy_ratio, 0); }); test('A/B WAV exports preserve coefficients and signed zero', () => { const before = pair(); const after = fir.renderFir(before, adjustment({tail_gain_db: -6})); for (const value of [before, after]) { const files = fir.firPairWavs(value); for (const side of ['left', 'right']) { const view = new DataView(files[side]); assert.equal(view.getUint16(20, true), 3); assert.equal(view.getUint32(24, true), 48000); assert.deepEqual(fir.decodeFir(files[side].slice(58)), value[side].map(Math.fround)); } } });