Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101#!/usr/bin/env node// run-c.mjs — pop-facing entry point for the physically-modeled bell.//// Builds the C engine if stale, renders a strike (geometry + material -> WAV),// masters it to mp3, and can also emit the modes JSON + the 3-D visualization.//// Usage:// node pop/bell/c/run-c.mjs --note A4 --material bronze --geometry church \// --out /tmp/bell.mp3 [--master bell|bright|raw] [--dur 8] [--vel 0.9]// ... --viz /tmp/bell.mp4 also render the 3-D mode visualization// ... --print-modes print the solved partial table//// Materials: bronze brass steel aluminum silver glass gold// Geometries: church handbell tubular bowl glass
import { spawnSync } from "node:child_process";import { existsSync, mkdirSync, statSync, unlinkSync } from "node:fs";import { dirname, resolve } from "node:path";import { fileURLToPath } from "node:url";
const HERE = dirname(fileURLToPath(import.meta.url));const ENGINE = resolve(HERE, "bell");const VIZ = resolve(HERE, "..", "bin", "viz.mjs");
const args = process.argv.slice(2);const val = (k, d = null) => { const i = args.indexOf(k); return i >= 0 && i + 1 < args.length ? args[i + 1] : d;};const note = val("--note", "A4");const material = val("--material", "bronze");const geometry = val("--geometry", "church");const dur = val("--dur", "8");const vel = val("--vel", "0.9");const outMp3 = val("--out", "/tmp/bell.mp3");const master = val("--master", "bell");const vizOut = val("--viz", null);const printModes = args.includes("--print-modes");
// Build engine if missing or stale.const cSrc = resolve(HERE, "bell.c");if (!existsSync(ENGINE) || statSync(cSrc).mtimeMs > statSync(ENGINE).mtimeMs) { console.log("[run-c] building bell…"); const b = spawnSync("bash", [resolve(HERE, "build.sh")], { stdio: "inherit" }); if (b.status !== 0) process.exit(1);}
mkdirSync(dirname(resolve(outMp3)), { recursive: true });const wav = `${outMp3}.tmp.wav`;const modesJson = vizOut ? `${outMp3}.modes.json` : null;
const engArgs = [ "--note", note, "--material", material, "--geometry", geometry, "--dur", dur, "--vel", vel, "--out", wav,];if (modesJson) engArgs.push("--modes", modesJson);if (printModes) engArgs.push("--print-modes");const r = spawnSync(ENGINE, engArgs, { stdio: "inherit" });if (r.status !== 0) { console.error("✗ bell engine failed"); process.exit(1); }
// Master chains (the WAV is already peak-normalized; these add polish).const MASTERS = { // Keep the long shimmering tail, gentle glue, a touch of air. bell: [ "highpass=f=40", "acompressor=threshold=-20dB:ratio=2:attack=20:release=300:makeup=1.5:knee=8", "treble=g=1.2:f=9000", "alimiter=limit=0.95:attack=4:release=120", ], // More presence for clangier/percussive uses. bright: [ "highpass=f=60", "equalizer=f=3500:t=q:w=1.2:g=2", "acompressor=threshold=-18dB:ratio=2.6:attack=8:release=180:makeup=2.5:knee=6", "treble=g=2:f=8500", "alimiter=limit=0.97:attack=3:release=80", ], raw: [],};const chain = MASTERS[master];if (!chain) { console.error(`unknown master: ${master}`); process.exit(1); }
const ffArgs = ["-hide_banner", "-y", "-loglevel", "error", "-i", wav];if (chain.length) ffArgs.push("-af", chain.join(","));ffArgs.push("-c:a", "libmp3lame", "-q:a", "2", resolve(outMp3));const ff = spawnSync("ffmpeg", ffArgs, { stdio: "inherit" });if (ff.status !== 0) { console.error("✗ ffmpeg failed"); process.exit(1); }console.log(`✓ ${outMp3} (bell · ${geometry}/${material} · ${master}-mastered)`);
// Optional 3-D visualization (uses the rendered WAV for audio + reactive flash).if (vizOut) { const v = spawnSync("node", [ VIZ, "--modes", modesJson, "--audio", wav, "--out", resolve(vizOut), "--dur", dur, ], { stdio: "inherit" }); if (v.status !== 0) { console.error("✗ viz failed"); process.exit(1); } console.log(`✓ ${vizOut} (3-D mode visualization)`);}
try { unlinkSync(wav); } catch {}