74 lines
3.0 KiB
JavaScript
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');
|