// Prepare a song ahead of time so viewing is fast (EVOLUTION.md: "the prepare // step"). Writes the full processed analysis + seed as a compact JSON artifact // that the browser loads instead of re-analysing the WAV. // // node tools/prepare.js # drone, runner, dusk @ 12345 // node tools/prepare.js --songs runner # one song // node tools/prepare.js --songs drone,runner --seeds 12345,777 --out out // // Artifacts land in /prepared/-.json. The WAV is still needed // at viewing time for audio, but analysis is skipped. deserializeSong() must // produce a world bit-identical to the live WAV path — test/prepare.test.js // asserts that. import { writeFileSync, mkdirSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { loadSong } from './load.js'; import { serializeSong } from '../src/song/serialize.js'; const HERE = dirname(fileURLToPath(import.meta.url)); const ROOT = resolve(HERE, '..'); const BANK = resolve(ROOT, '../flow-state/test/songs'); function args(argv) { const out = {}; for (let i = 2; i < argv.length; i += 2) out[argv[i].replace(/^--/, '')] = argv[i + 1]; return out; } const opt = args(process.argv); const names = (opt.songs || 'drone,runner,dusk').split(',').map((s) => s.trim()).filter(Boolean); const seeds = (opt.seeds || '12345').split(',').map(Number).filter((n) => Number.isFinite(n)); const outDir = resolve(ROOT, opt.out || 'out'); const dir = resolve(outDir, 'prepared'); mkdirSync(dir, { recursive: true }); let count = 0; for (const name of names) { for (const seed of seeds) { const { song } = loadSong(resolve(BANK, `${name}.wav`), { fps: 30, seed }); const file = resolve(dir, `${name}-${seed}.json`); const json = JSON.stringify(serializeSong(song)); writeFileSync(file, json); const kb = (Buffer.byteLength(json) / 1024).toFixed(1); console.log(`${file} ${kb.padStart(7)} KB ${song.name}`); count++; } } console.log(`prepared ${count} song${count === 1 ? '' : 's'} → ${dir}`);