Files
zhila/scripts/render-smoke.mjs

74 lines
3.0 KiB
JavaScript

#!/usr/bin/env node
// Render the current arrangement through the daemon and verify the WAV.
import { readFileSync, existsSync } from 'node:fs';
const PORT = 4317;
const ws = new WebSocket(`ws://127.0.0.1:${PORT}`);
const pending = new Map();
let rendered = null;
let resolveRendered;
const renderedReady = new Promise((resolve) => { resolveRendered = resolve; });
ws.onopen = () => ws.send(JSON.stringify({ type: 'hello', protocol: 1, token: 'dev', client: 'render-smoke' }));
ws.onmessage = (message) => {
if (typeof message.data !== 'string') return;
const parsed = JSON.parse(message.data);
if (parsed.type === 'snapshot') {
ws.send(JSON.stringify({ type: 'command', id: 1, command: { type: 'render' } }));
} else if (parsed.type === 'result' && parsed.id === 1) {
pending.set(1, parsed.result);
} else if (parsed.type === 'event' && parsed.event?.kind === 'rendered') {
rendered = parsed.event;
resolveRendered();
}
};
await renderedReady;
if (!rendered) throw new Error('no rendered event');
const path = rendered.path;
if (!existsSync(path)) throw new Error(`render file missing: ${path}`);
const file = readFileSync(path);
if (file.toString('ascii', 0, 4) !== 'RIFF' || file.toString('ascii', 8, 12) !== 'WAVE') throw new Error('not a RIFF/WAVE file');
let offset = 12;
let fmt = null;
let data = null;
while (offset + 8 <= file.length) {
const id = file.toString('ascii', offset, offset + 4);
const size = file.readUInt32LE(offset + 4);
if (id === 'fmt ') fmt = { offset: offset + 8, size };
if (id === 'data') {
data = { offset: offset + 8, size: Math.min(size, file.length - offset - 8) };
break;
}
offset += 8 + size + (size % 2);
}
if (!fmt || !data) throw new Error('missing fmt or data chunk');
let format = file.readUInt16LE(fmt.offset);
const channels = file.readUInt16LE(fmt.offset + 2);
const rate = file.readUInt32LE(fmt.offset + 4);
const bits = file.readUInt16LE(fmt.offset + 14);
if (format === 0xfffe) format = file.readUInt16LE(fmt.offset + 24);
if (format !== 3) throw new Error(`expected float WAV, got format ${format}`);
if (channels !== 2 || rate !== 48000 || bits !== 32) throw new Error(`unexpected spec ${channels}/${rate}/${bits}`);
const bytes = file.subarray(data.offset, data.offset + data.size);
let peak = 0;
for (let cursor = 0; cursor + 4 <= bytes.length; cursor += 4) {
const sample = bytes.readFloatLE(cursor);
peak = Math.max(peak, Math.abs(sample));
}
const duration = data.size / (channels * 4) / rate;
console.log(JSON.stringify({
path,
bytes: file.length,
format, channels, rate, bits,
durationSec: Number(duration.toFixed(3)),
eventDurationSec: rendered.duration_sec,
peak: Number(peak.toFixed(3)),
renderedState: pending.get(1)?.state?.revision
}, null, 2));
if (peak < 0.01) throw new Error('render is silent');
if (duration < 15) throw new Error('render is shorter than the arrangement');
if (Math.abs(duration - rendered.duration_sec) > 0.05) throw new Error('duration mismatch');
ws.close();
console.log('render smoke passed');