// Run the AudioWorklet processor in node with stubbed globals: one mallet hit and one stick hit, // half a second of output. Writes a raw float32 signal, a WAV and a JSON of the hit reports. // node tests/worklet_node.ts [seconds] import { readFileSync, writeFileSync } from "node:fs"; import { parseModes } from "../src/modes.ts"; import { derive } from "../src/derived.ts"; import { parseConfigYaml } from "../src/config/yaml.ts"; import { buildHitPatch } from "../src/patch.ts"; import { defaultMallet, defaultStick } from "../src/config/schema.ts"; import { encodeWav } from "../src/hits.ts"; const g = globalThis as unknown as Record; const messages: unknown[] = []; g.sampleRate = 44100; g.currentFrame = 0; let Proc: new () => { port: { onmessage: ((ev: { data: unknown }) => void) | null }; process(i: Float32Array[][], o: Float32Array[][]): boolean }; g.AudioWorkletProcessor = class { port = { onmessage: null, postMessage: (m: unknown) => messages.push(m) }; }; g.registerProcessor = (_name: string, ctor: typeof Proc) => { Proc = ctor; }; await import("../src/worklet.ts"); const modes = parseModes(readFileSync(process.argv[2]).buffer as ArrayBuffer); const prefix = process.argv[3], seconds = Number(process.argv[4] ?? "0.5"); const proc = new Proc!(); const gain = new Float32Array(2 * modes.nModes); for (let i = 0; i < gain.length; i++) gain[i] = modes.gainFront[i] - modes.gainBack[i]; const cfg = parseConfigYaml(JSON.stringify(modes.meta.config)).config!; const der = derive(modes, cfg); proc.port.onmessage!({ data: { type: "init", freq: der.freq, sigma: der.sigma, gain } }); const hitsToPlay = [ { t: 0.0, kind: "mallet", r: 0.13716, phi: 0.3, v: 8, substeps: 2 }, { t: 0.25, kind: "stick", r: 0.194, phi: 2.0, v: 7, substeps: 6 }, ]; if (seconds > 0.6) hitsToPlay.push( { t: 0.5, kind: "mallet", r: 0.13716, phi: 1.3, v: 6, substeps: 2 }, { t: 0.75, kind: "stick", r: 0.194, phi: 3.0, v: 5, substeps: 6 }, { t: 1.0, kind: "mallet", r: 0.10, phi: -1.0, v: 9, substeps: 2 }, { t: 1.25, kind: "stick", r: 0.18, phi: 0.5, v: 8, substeps: 6 }, ); const fs = 44100, B = 128, nBlocks = Math.ceil(seconds * fs / B); const out = new Float32Array(nBlocks * B); let id = 1; const t0 = performance.now(); let maxBlockMs = 0; let slow: string[] = []; const runs = Number(process.env.RUNS ?? "1"); for (let run = 0; run < runs; run++) { if (run > 0) { const cfg = parseConfigYaml(JSON.stringify(modes.meta.config)).config!; const der = derive(modes, cfg); proc.port.onmessage!({ data: { type: "init", freq: der.freq, sigma: der.sigma, gain } }); slow = []; maxBlockMs = 0; } for (let b = 0; b < nBlocks; b++) { for (const h of hitsToPlay) { if (Math.round(h.t * fs / B) === b) { const ex = h.kind === "stick" ? defaultStick() : defaultMallet(); const patch = buildHitPatch(modes, ex, h.r, h.phi, h.v, h.kind === "mallet" ? Number(process.env.MALLET_POINTS ?? "1") : 7)!; proc.port.onmessage!({ data: { type: "hit", id: id++, phi: patch.phi, offsets: patch.offsets, kPoint: patch.kPoint, alpha: patch.alpha, mass: patch.mass, gap: patch.gap, velocity: h.v } }); } } const buf = [[new Float32Array(B)]]; const tb = performance.now(); proc.process([], buf); const cost = performance.now() - tb; if (cost > 2.9) slow.push(`block ${b} (t=${(b * B / fs * 1e3).toFixed(1)} ms): ${cost.toFixed(1)} ms`); maxBlockMs = Math.max(maxBlockMs, cost); out.set(buf[0][0], b * B); g.currentFrame = (b + 1) * B; } } const wall = (performance.now() - t0) / 1000; let peak = 0, finite = true; for (const x of out) { if (!Number.isFinite(x)) finite = false; peak = Math.max(peak, Math.abs(x)); } console.log(`rendered ${seconds} s in ${wall.toFixed(2)} s (${(100 * wall / seconds).toFixed(0)} % of real time), worst block ${maxBlockMs.toFixed(2)} ms of ${(1000 * B / fs).toFixed(2)} ms; finite ${finite}; peak ${peak.toFixed(3)} Pa`); console.log("slow blocks:", slow.join("; ") || "none"); for (const m of messages) if ((m as { type: string }).type !== "load") console.log(JSON.stringify(m)); writeFileSync(prefix + ".f32", Buffer.from(out.buffer)); const wav = encodeWav(out, fs); writeFileSync(prefix + ".wav", Buffer.from(await wav.arrayBuffer())); writeFileSync(prefix + "_hits.json", JSON.stringify(hitsToPlay));