// The teaser's sound, through real ffmpeg: a hit starts on the frame its pop // lands on, nothing clips, and `hits: false` is digital silence. // // The onset of hit i is measured as the first sample where the graph WITH it // differs from the same graph WITHOUT it: the hits overlap (the second tier // lands 0.1 s into the title's decay), so "the first loud sample after the // cue" would find the previous hit's tail. Every layer up to the limiter is // linear and the noise is a hash of the sample number, so the difference is // hit i alone until the limiter engages -- after its onset. // // Run with: pnpm test:scripts import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import test from "node:test"; import { teaserAudioGraph } from "./build-video.mjs"; import { teaserHits, teaserSeconds } from "./deck.mjs"; const have = spawnSync("ffmpeg", ["-version"]).status === 0; const RENDER = { fps: 30, audioRate: 48000, audioChannels: 2 }; const FERRET = Object.freeze({ type: "teaser", id: "fin", seconds: 7, lines: ["Pirate Software", { text: "The Largest Ferret Rescue in the United States", break: "in the United States" }, "February 2027"], tail: "?", }); /** The graph rendered to raw float samples, channel 0. */ function samples(graph) { const r = spawnSync("ffmpeg", [ "-nostdin", "-v", "error", "-filter_complex", graph, "-map", "[ta]", "-f", "f32le", "-ac", "2", "-", ], { maxBuffer: 1 << 26 }); assert.equal(r.status, 0, String(r.stderr)); const f = new Float32Array(r.stdout.buffer, r.stdout.byteOffset, r.stdout.length / 4); const ch0 = new Float32Array(f.length / 2); for (let i = 0; i < ch0.length; i += 1) ch0[i] = f[2 * i]; return { ch0, all: f }; } test("each hit's onset lands within a frame of its pop", { skip: !have && "no ffmpeg" }, () => { const hits = teaserHits(FERRET); const full = samples(teaserAudioGraph(hits, { seconds: 7, render: RENDER })).ch0; assert.equal(full.length, 7 * 48000); const frame = 1 / RENDER.fps; hits.forEach((h, i) => { if (h.kind !== "hit") return; const without = samples(teaserAudioGraph(hits.filter((_, j) => j !== i), { seconds: 7, render: RENDER })).ch0; let first = -1; for (let n = 0; n < full.length; n += 1) { if (Math.abs(full[n] - without[n]) > 1e-4) { first = n; break; } } assert.ok(first >= 0, `${h.role} made no sound`); const onset = first / 48000; assert.ok(Math.abs(onset - h.at) <= frame, `${h.role}: onset ${onset.toFixed(4)}s, pop ${h.at}s`); }); }); test("nothing clips: the sum stays under −6 dBFS (about) and well under full scale", { skip: !have && "no ffmpeg" }, () => { const { all } = samples(teaserAudioGraph(teaserHits(FERRET), { seconds: 7, render: RENDER })); let peak = 0; for (const v of all) peak = Math.max(peak, Math.abs(v)); assert.ok(peak > 0.2, `peak ${peak}`); // it is not silent assert.ok(peak <= 0.5 * 1.03, `peak ${peak} (${(20 * Math.log10(peak)).toFixed(2)} dBFS)`); // The tightest beat packs five lines' hits together; they still sum cleanly. const packed = { ...FERRET, seconds: undefined, beat: 0.4, lines: ["A", { text: "B c", break: "c" }, "D", "E", "F"] }; const seconds = teaserSeconds(packed); const s = samples(teaserAudioGraph(teaserHits(packed), { seconds, render: RENDER })).all; assert.equal(s.length, Math.round(seconds * 48000) * 2); let p2 = 0; for (const v of s) p2 = Math.max(p2, Math.abs(v)); assert.ok(p2 > 0.2 && p2 <= 0.5 * 1.03, `packed card peak ${p2}`); }); test("a wider beat moves every hit's onset with it", { skip: !have && "no ffmpeg" }, () => { const slow = { ...FERRET, beat: 1.3, seconds: undefined }; const seconds = teaserSeconds(slow); const hits = teaserHits(slow); const full = samples(teaserAudioGraph(hits, { seconds, render: RENDER })).ch0; const frame = 1 / RENDER.fps; hits.forEach((h, i) => { if (h.kind !== "hit") return; const without = samples(teaserAudioGraph(hits.filter((_, j) => j !== i), { seconds, render: RENDER })).ch0; const first = full.findIndex((v, n) => Math.abs(v - without[n]) > 1e-4); assert.ok(first >= 0, `${h.role} made no sound`); assert.ok(Math.abs(first / 48000 - h.at) <= frame, `${h.role}: onset ${(first / 48000).toFixed(4)}s, pop ${h.at}s`); }); }); test("hits: false is digital silence, exactly as long", { skip: !have && "no ffmpeg" }, () => { const { all } = samples(teaserAudioGraph(teaserHits({ ...FERRET, hits: false }), { seconds: 7, render: RENDER })); assert.equal(all.length, 7 * 48000 * 2); assert.ok(all.every((v) => v === 0)); });