Monorepo for Aesthetic.Computer
aesthetic.computer
1#!/usr/bin/env node
2/**
3 * One-shot: parse project XML then run simulation in near real-time with sensible defaults.
4 * Usage:
5 * node run-full-track.mjs [--project <xml>] [--bpm 144] [--rate 1] [--groups <spec>] [--hat-pitches a,b]
6 * Defaults:
7 * project: ../system/public/assets/wipppps/zzzZWAP_extracted.xml
8 * rate: 1 (real-time)
9 * fps: 20
10 * aggregate-window: 8
11 * auto groups: top 2 pitches if --groups omitted & groups-grid is used
12 */
13import { spawn } from 'node:child_process';
14import { resolve, dirname } from 'node:path';
15import { fileURLToPath } from 'node:url';
16import fs from 'node:fs';
17
18const __dirname = dirname(fileURLToPath(import.meta.url));
19
20const args = process.argv.slice(2);
21function getFlag(name, def){ const i=args.indexOf('--'+name); return i!==-1? args[i+1]: def; }
22const has = n => args.includes('--'+n);
23
24const project = resolve(getFlag('project','../system/public/assets/wipppps/zzzZWAP_extracted.xml'));
25const bpm = getFlag('bpm');
26const rate = getFlag('rate','1');
27const fps = getFlag('fps','20');
28const agg = getFlag('aggregate-window','8');
29const hat = getFlag('hat-pitches','');
30let groupsSpec = getFlag('groups');
31const wantGrid = true; // always show groups grid for overview
32
33// If groups not provided, we will quick-scan pitches from analyzer JSON (or generate one) to pick top 2.
34async function ensureReportAndNotes(){
35 if (!fs.existsSync('report.json') || !fs.existsSync('notes.json')) {
36 await new Promise((res,rej)=>{
37 const a = spawn('node', ['analyze-ableton.mjs','--project', project,'--blank','./live-12-blank.xml','--out','report.json','--notes-out','notes.json'], { cwd: __dirname, stdio:'inherit' });
38 a.on('exit', c=> c===0?res():rej(new Error('analyze failed')));
39 });
40 }
41}
42
43function deriveGroups(){
44 if (groupsSpec) return;
45 try {
46 const notesPath = resolve(__dirname,'notes.json');
47 const notes = JSON.parse(fs.readFileSync(notesPath,'utf8'));
48 const freq = new Map();
49 for (const n of notes) { if (n.pitch!=null) freq.set(n.pitch,(freq.get(n.pitch)||0)+1); }
50 const top = [...freq.entries()].sort((a,b)=>b[1]-a[1]).slice(0,2);
51 if (top.length) {
52 groupsSpec = top.map(([p],i)=>`P${p}:${p}:${i===0?'P':'Q'}`).join(';');
53 console.error('[run-full] Auto groups:', groupsSpec);
54 }
55 } catch(e){ /* ignore */ }
56}
57
58(async () => {
59 await ensureReportAndNotes();
60 deriveGroups();
61 const simArgs = ['simulate-ableton.mjs','--auto','--rate',rate,'--fps',fps,'--aggregate-window',agg];
62 if (bpm) simArgs.push('--bpm', bpm);
63 if (hat) simArgs.push('--hat-pitches', hat);
64 if (groupsSpec) simArgs.push('--groups-grid','--groups', groupsSpec);
65 else simArgs.push('--groups-grid');
66 const child = spawn('node', simArgs, { cwd: __dirname, stdio:'inherit' });
67 child.on('exit', code => process.exit(code));
68})();