Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188#!/usr/bin/env node// sfx-compose.mjs — build a track whose EVERY sound is generated by the// ElevenLabs SFX endpoint (/api/sfx). No synths, no samples, no vocals:// just text-prompted sound effects layered on a timeline and glued with// a psychedelic ffmpeg FX chain.//// Counterpart to big-pictures/cli.mjs (which builds jeffrey-vocal// tracks). Here the "score" is an arrangement file of sound prompts.// Each cue is content-hash cached by bin/sfx.mjs, so reruns are free.//// Arrangement file format (`<slug>.sfx`):// @title phosphene # ID3 title (default: slug)// @glue swirl # psychedelic glue: swirl | dub | none// @duration 88 # hard trim (seconds); omit = auto//// # cue lines — head is key=val tokens, prompt follows the colon:// # at=SEC gain=G dur=S [loop] fadein=S fadeout=S pan=-1..1 : prompt// at=0 gain=0.5 dur=26 loop fadein=5 fadeout=8 : deep warm analog drone, evolving// at=12 gain=0.6 dur=8 fadein=2 fadeout=4 pan=-0.5 : reverse cymbal swell rising//// Usage:// node bin/sfx-compose.mjs big-pictures/phosphene.sfx// node bin/sfx-compose.mjs phosphene # resolves big-pictures/<slug>.sfx// node bin/sfx-compose.mjs phosphene --force # bust sfx + mix caches// node bin/sfx-compose.mjs phosphene --no-finalize # stop at the raw mix
import { spawnSync } from "node:child_process";import { readFileSync, copyFileSync, existsSync, mkdirSync, statSync } from "node:fs";import { resolve, dirname, basename } from "node:path";import { fileURLToPath } from "node:url";import { homedir } from "node:os";
const HERE = dirname(fileURLToPath(import.meta.url));const POP = resolve(HERE, "..");const OUT = `${POP}/big-pictures/out`;
const argv = process.argv.slice(2);const flags = {};const positional = [];for (let i = 0; i < argv.length; i++) { const a = argv[i]; if (a.startsWith("--")) { const k = a.slice(2); const next = argv[i + 1]; if (next !== undefined && !next.startsWith("--")) { flags[k] = next; i++; } else flags[k] = true; } else positional.push(a);}
if (!positional[0]) { console.error("usage: node bin/sfx-compose.mjs <arrangement.sfx | slug> [--force] [--no-finalize]"); process.exit(1);}
// Resolve the arrangement file: explicit path, or big-pictures/<slug>.sfx.let ARR = resolve(process.cwd(), positional[0]);if (!existsSync(ARR)) { const alt = `${POP}/big-pictures/${positional[0].replace(/\.sfx$/, "")}.sfx`; if (existsSync(alt)) ARR = alt; else { console.error(`✗ arrangement not found: ${positional[0]}`); process.exit(1); }}const SLUG = basename(ARR).replace(/\.sfx$/, "");const FORCE = flags.force === true;const FINALIZE = flags["no-finalize"] !== true;
// ── parse arrangement ─────────────────────────────────────────────────const meta = { title: SLUG, glue: "swirl", duration: null };const cues = [];for (const raw of readFileSync(ARR, "utf8").split("\n")) { const line = raw.trim(); if (!line || line.startsWith("#")) continue; if (line.startsWith("@")) { const sp = line.indexOf(" "); const key = (sp === -1 ? line.slice(1) : line.slice(1, sp)).trim(); const val = sp === -1 ? "" : line.slice(sp + 1).trim(); if (key === "duration") meta.duration = Number(val) || null; else meta[key] = val; continue; } const ci = line.indexOf(":"); if (ci === -1) continue; const prompt = line.slice(ci + 1).trim(); if (!prompt) continue; const cue = { at: 0, gain: 0.7, dur: null, loop: false, fadein: 0, fadeout: 0, pan: 0, prompt }; for (const tok of line.slice(0, ci).trim().split(/\s+/)) { if (!tok) continue; const [k, v] = tok.split("="); if (k === "at") cue.at = Number(v) || 0; else if (k === "gain") cue.gain = Number(v); else if (k === "dur") cue.dur = Math.max(0.5, Math.min(30, Number(v))); else if (k === "loop") cue.loop = true; else if (k === "fadein") cue.fadein = Number(v) || 0; else if (k === "fadeout") cue.fadeout = Number(v) || 0; else if (k === "pan") cue.pan = Math.max(-1, Math.min(1, Number(v) || 0)); } cues.push(cue);}if (!cues.length) { console.error(`✗ no cues in ${ARR}`); process.exit(1); }
const TITLE = flags.title || meta.title;const MIX = `${OUT}/${SLUG}-sfxmix.mp3`;const FINAL = `${OUT}/${SLUG}-final.mp3`;const DESK = `${homedir()}/Desktop/${SLUG}.mp3`;const sfxDir = `${OUT}/sfx/${SLUG}`;mkdirSync(sfxDir, { recursive: true });
function run(cmd, args, cwd = POP) { const r = spawnSync(cmd, args, { cwd, stdio: ["ignore", "inherit", "inherit"] }); if (r.status !== 0) { console.error(`✗ ${cmd} ${args.join(" ")} failed (exit ${r.status})`); process.exit(1); }}function probeDur(p) { const r = spawnSync("ffprobe", ["-v", "error", "-show_entries", "format=duration", "-of", "default=nw=1:nk=1", p], { encoding: "utf8" }); return Number((r.stdout || "0").trim()) || 0;}
console.log(`━━━ ${SLUG} ━━━ ${cues.length} sfx cues · glue=${meta.glue}${meta.duration ? ` · trim ${meta.duration}s` : ""}\n`);
// ── 1 · render every cue through /api/sfx (cached) ───────────────────console.log("▸ 1 · render sfx cues");const stems = [];cues.forEach((c, i) => { const p = `${sfxDir}/${String(i).padStart(2, "0")}.mp3`; const args = ["bin/sfx.mjs", "--text", c.prompt, "--out", p]; if (c.dur != null) args.push("--duration", String(c.dur)); if (c.loop) args.push("--loop"); if (FORCE) args.push("--force"); run("node", args); stems.push(p);});
// ── 2 · lay cues on a timeline + glue ────────────────────────────────console.log("\n▸ 2 · compose timeline + psychedelic glue");const inputs = [];for (const p of stems) inputs.push("-i", p);
const stemFilters = [];const labels = [];let trackEnd = 0;cues.forEach((c, i) => { const len = c.dur != null ? c.dur : probeDur(stems[i]); trackEnd = Math.max(trackEnd, c.at + len); const d = Math.max(0, Math.round(c.at * 1000)); const chain = [`adelay=${d}|${d}`, `volume=${c.gain}`]; if (c.fadein > 0) chain.push(`afade=t=in:st=${c.at}:d=${c.fadein}`); if (c.fadeout > 0) chain.push(`afade=t=out:st=${(c.at + len - c.fadeout).toFixed(3)}:d=${c.fadeout}`); // Stereo placement: pan a copy of the (mono/stereo) stem across the field. if (c.pan !== 0) { const l = (1 - Math.max(0, c.pan)).toFixed(3); const rr = (1 + Math.min(0, c.pan)).toFixed(3); chain.push(`pan=stereo|c0=${l}*c0|c1=${rr}*c1`); } stemFilters.push(`[${i}:a]${chain.join(",")}[s${i}]`); labels.push(`[s${i}]`);});
// Psychedelic glue presets applied to the full mix.const GLUE = { swirl: "aphaser=in_gain=0.5:out_gain=0.8:delay=3.0:decay=0.5:speed=0.4," + "chorus=0.6:0.9:55|65|75:0.4|0.32|0.3:0.25|0.4|0.3:2|2.3|1.3," + "aecho=0.8:0.7:90|330:0.4|0.25", dub: "aecho=0.8:0.85:180|420|760:0.5|0.3|0.18,aphaser=speed=0.3", none: "anull",}[meta.glue] || "anull";
const dur = meta.duration || (trackEnd + 1);// mix → glue → gentle loudness normalize → trim → 44.1k stereoconst post = `amix=inputs=${cues.length}:duration=longest:dropout_transition=0:normalize=0[mix];` + `[mix]${GLUE},loudnorm=I=-15:TP=-1.5:LRA=11,atrim=duration=${dur.toFixed(2)},aresample=44100`;const filter = `${stemFilters.join(";")};${labels.join("")}${post}`;
run("ffmpeg", ["-y", "-loglevel", "error", ...inputs, "-filter_complex", filter, "-c:a", "libmp3lame", "-q:a", "2", MIX]);console.log(` ✓ ${MIX} (${(statSync(MIX).size / 1024 / 1024).toFixed(2)} MB · ~${dur.toFixed(0)}s)`);
// ── 3 · finalize (ID3 + auto cover) ──────────────────────────────────let result = MIX;if (FINALIZE) { console.log("\n▸ 3 · finalize (ID3 + cover)"); run("node", ["bin/finalize.mjs", "--in", MIX.replace(POP + "/", ""), "--slug", SLUG, "--title", TITLE, "--out", FINAL.replace(POP + "/", ""), "--force"]); result = FINAL;}
copyFileSync(result, DESK);console.log(`\n✓ ~/Desktop/${SLUG}.mp3 ${(statSync(DESK).size / 1024 / 1024).toFixed(2)} MB`);