/ proof-of-concept / cymsimweb
Sign in
proof-of-concept / cymsimweb
cymsimweb / tests / analysis.test.ts
71 lines · 3.2 KBCodeBlameHistory
2import assert from "node:assert/strict";
3import { Stft, fft } from "../src/analysis/spectrogram.ts";
4import { encodeWav, scaleForPlayback } from "../src/audio/player.ts";
6test("FFT of a sine peaks at the right bin", () => {
7 const n = 1024, re = new Float32Array(n), im = new Float32Array(n);
8 for (let i = 0; i < n; i++) re[i] = Math.sin((2 * Math.PI * 64 * i) / n);
9 fft(re, im);
10 let best = 0;
11 for (let b = 1; b < n / 2; b++) if (Math.hypot(re[b], im[b]) > Math.hypot(re[best], im[best])) best = b;
12 assert.equal(best, 64);
13 assert.ok(Math.abs(Math.hypot(re[64], im[64]) - n / 2) < 1e-2);
14});
16test("incremental STFT tracks a chirp's centroid", () => {
17 const fs = 44100, st = new Stft(fs, 1024, 256);
18 const sig = new Float32Array(fs);
19 for (let i = 0; i < fs; i++) { const t = i / fs; sig[i] = Math.sin(2 * Math.PI * (500 * t + 2000 * t * t)); }
20 let cols = 0;
21 for (let s = 0; s < fs; s += 1000) cols += st.push(sig.subarray(s, Math.min(fs, s + 1000)));
22 assert.equal(cols, st.columns.length);
23 assert.ok(cols > 150);
24 const early = st.centroid[5], late = st.centroid[cols - 5];
25 assert.ok(early > 400 && early < 1200 && late > 3500 && late < 5500, `${early} ${late}`);
26});
28test("WAV encoding and playback scaling", () => {
29 const p = new Float32Array([0, 2, -4, 1]);
30 const s = scaleForPlayback(p, true);
31 assert.ok(Math.abs(Math.max(...Array.from(s).map(Math.abs)) - 0.9) < 1e-6);
32 const blob = encodeWav(s, 44100);
33 assert.equal(blob.size, 44 + 8);
34});
36test("band summary reports staggered onsets for a chirp", async () => {
37 const { bandSummary } = await import("../src/analysis/spectrogram.ts");
38 const fs = 44100, st = new Stft(fs, 1024, 256), sig = new Float32Array(fs);
39 for (let i = 0; i < fs; i++) { const t = i / fs; sig[i] = Math.sin(2 * Math.PI * (200 * t + 4000 * t * t)); }
40 st.push(sig);
41 const s = bandSummary(st);
42 const b250 = s.bands.find((b) => b.lo === 250)!, b2k = s.bands.find((b) => b.lo === 2000)!;
43 assert.ok(b250.onsetMs < b2k.onsetMs, `${b250.onsetMs} vs ${b2k.onsetMs}`);
44 assert.ok(s.centroidHz.length > 3 && s.centroidHz[0].hz < s.centroidHz[s.centroidHz.length - 1].hz);
45});
47test("movie store quantizes, times and seeks frames", async () => {
48 const { Movie, movieEvery } = await import("../src/analysis/movie.ts");
49 const fs = 44100, N = 50;
50 assert.equal(movieEvery(fs, 2000), 22);
51 const m = new Movie(N, fs, { fps: 2000, budgetBytes: 1e6 });
52 for (let f = 0; f < 10; f++) {
53 const w = new Float32Array(N);
54 for (let n = 0; n < N; n++) w[n] = 1e-3 * Math.sin(n + f);
55 m.push(w, f * 22);
56 }
57 assert.equal(m.length, 10);
58 assert.equal(m.bytes, 10 * N * 2);
59 assert.ok(Math.abs(m.duration - (9 * 22) / fs) < 1e-12);
60 // dequantization is accurate to about 1e-4 of the frame's peak
61 const back = m.read(3);
62 for (let n = 0; n < N; n++) assert.ok(Math.abs(back[n] - 1e-3 * Math.sin(n + 3)) < 1e-3 * 1e-4);
63 assert.equal(m.indexAtTime(0), 0);
64 assert.equal(m.indexAtTime((3 * 22) / fs), 3);
65 assert.equal(m.indexAtTime(10), 9);
66 // the budget stops capture rather than growing without bound
67 const small = new Movie(N, fs, { fps: 2000, budgetBytes: 3 * N * 2 });
68 for (let f = 0; f < 10; f++) small.push(new Float32Array(N), f * 22);
69 assert.equal(small.length, 3);
70 assert.ok(small.full);
71});
↑↓ move↵openescclose