Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990#!/usr/bin/env node// bake.mjs — generate c/score.h for the maytrax C engine (americomputadora// pattern). Reads kit.json (the freesound one-shot roster), shouts.json// (jeffrey-pvc + apple-say drop shouts) and the hellsine church-bell, and// emits a header of sample-path tables the C renderer loads at startup.//// Two root references are baked per pitched role because the original JS// used both:// • detected f0 — smpNote() path (sub / reese / pad). Computed in C at// load (port of detectF0), so only `pitched` is needed.// • nominal root — rateFor() path (lead / choir / strings / animal / wolf// / bass). The kit.json `root` string → midi, baked here.//// usage: node c/bake.mjs
import { readFileSync, writeFileSync, existsSync } from "node:fs";import { resolve, dirname } from "node:path";import { fileURLToPath } from "node:url";
const HERE = dirname(fileURLToPath(import.meta.url));const ROOT = dirname(HERE); // pop/maytraxconst POP = dirname(ROOT); // pop
const BPM = 160;const SR = 48000;
const NN = { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 };const noteToMidi = (s) => { const m = /^([A-G])([#b]?)(-?\d)$/.exec(s || "C4"); if (!m) return 60; return NN[m[1]] + (m[2] === "#" ? 1 : m[2] === "b" ? -1 : 0) + (parseInt(m[3], 10) + 1) * 12;};
const kit = JSON.parse(readFileSync(resolve(ROOT, "kit.json"), "utf8"));const roles = Object.keys(kit);
// shouts ------------------------------------------------------------------const keyOf = { wake: "wake_up", real: "it_s_real", hold: "hold_on", let: "let_go", rabbit: "follow_the_white_rabbit", now: "now",};let shouts = {};const shPath = resolve(ROOT, "shouts.json");if (existsSync(shPath)) shouts = JSON.parse(readFileSync(shPath, "utf8"));const shoutRows = Object.entries(keyOf).map(([k, slug]) => { const e = shouts[slug]; const j = e?.path && existsSync(e.path) ? e.path : ""; const a = e?.path ? e.path.replace(/\.wav$/, "-say-sung.wav") : ""; return { k, j, a: a && existsSync(a) ? a : "" };});
const bell = resolve(POP, "hellsine/samples/church-bell.wav");
// emit --------------------------------------------------------------------const esc = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');const L = [];L.push("// score.h — generated by c/bake.mjs. do not hand-edit.");L.push(`#define SCORE_BPM ${BPM}`);L.push(`#define SCORE_SR ${SR}`);L.push("");L.push(`#define N_ROLES ${roles.length}`);L.push(`static const char *ROLE_NAME[N_ROLES] = {`);L.push(" " + roles.map((r) => `"${r}"`).join(", "));L.push("};");L.push(`static const char *ROLE_PATH[N_ROLES] = {`);for (const r of roles) L.push(` "${esc(kit[r].path)}",`);L.push("};");L.push(`static const int ROLE_PITCHED[N_ROLES] = {`);L.push(" " + roles.map((r) => (kit[r].pitched ? 1 : 0)).join(", "));L.push("};");L.push(`// nominal root midi from kit.json (rateFor path); -1 = unpitched`);L.push(`static const int ROLE_NOMINAL[N_ROLES] = {`);L.push(" " + roles.map((r) => (kit[r].pitched ? noteToMidi(kit[r].root || "C4") : -1)).join(", "));L.push("};");L.push("");L.push(`static const char *BELL_PATH = "${esc(bell)}";`);L.push("");L.push(`#define N_SHOUTS ${shoutRows.length}`);L.push(`static const char *SHOUT_KEY[N_SHOUTS] = { ${shoutRows.map((s) => `"${s.k}"`).join(", ")} };`);L.push(`static const char *SHOUT_J[N_SHOUTS] = {`);for (const s of shoutRows) L.push(` "${esc(s.j)}",`);L.push("};");L.push(`static const char *SHOUT_A[N_SHOUTS] = {`);for (const s of shoutRows) L.push(` "${esc(s.a)}",`);L.push("};");L.push("");
writeFileSync(resolve(HERE, "score.h"), L.join("\n") + "\n");console.log(`✓ score.h · ${roles.length} roles · ${shoutRows.filter((s) => s.j).length}/6 jeffrey + ${shoutRows.filter((s) => s.a).length}/6 apple shouts · bell ${existsSync(bell) ? "✓" : "✗"}`);