Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394#!/usr/bin/env node// chillwave/bin/harmonize.mjs — render a HARMONY vocal stem against an// alternate .vocal.np (e.g. helpabeach.vocal-harmony.np = diatonic 3rd// up). Reuses the cached lead vocal MP3 + words.json from sing.mjs;// no ElevenLabs call.//// Pipeline:// 1. score-pitch (WORLD f0 → helpabeach.vocal-harmony.np)// 2. score-stretch (rubberband → 70 BPM beat grid)//// Output: out/helpabeach-vocal-<suffix>-sung.mp3 (suffix defaults to "harmony")//// Usage:// node pop/chillwave/bin/harmonize.mjs # 3rd up default// node pop/chillwave/bin/harmonize.mjs --score helpabeach.vocal-octave.np --suffix octave// node pop/chillwave/bin/harmonize.mjs --transpose 12 --suffix octave-up//// Prereq: sing.mjs has run at least once (need cached vocal mp3 + words.json).
import { existsSync } from "node:fs";import { resolve, dirname } from "node:path";import { fileURLToPath } from "node:url";import { spawnSync } from "node:child_process";
const HERE = dirname(fileURLToPath(import.meta.url));const LANE = resolve(HERE, "..");const REPO = resolve(LANE, "../..");
const flags = {};for (let i = 2; i < process.argv.length; i++) { const a = process.argv[i]; if (!a.startsWith("--")) continue; const n = process.argv[i + 1]; if (n === undefined || n.startsWith("--")) flags[a.slice(2)] = true; else { flags[a.slice(2)] = n; i++; }}
const SCORE_FILE = flags.score || "helpabeach.vocal-harmony.np";const SUFFIX = flags.suffix || "harmony";const TRANSPOSE = String(flags.transpose ?? 0);const BPM = String(flags.bpm ?? 84);
const VOCAL_MP3 = `${LANE}/out/helpabeach-vocal.mp3`;const WORDS_JSON = `${LANE}/out/helpabeach-vocal-words.json`;if (!existsSync(VOCAL_MP3) || !existsSync(WORDS_JSON)) { console.error(`✗ missing cached source — run sing.mjs first`); console.error(` expected: ${VOCAL_MP3}`); console.error(` ${WORDS_JSON}`); process.exit(1);}const SCORE_PATH = resolve(LANE, SCORE_FILE);if (!existsSync(SCORE_PATH)) { console.error(`✗ score not found: ${SCORE_PATH}`); process.exit(1);}
const PITCHED_MP3 = `${LANE}/out/helpabeach-vocal-${SUFFIX}-pitched.mp3`;const PITCHED_ALIGN = `${LANE}/out/helpabeach-vocal-${SUFFIX}-pitched-alignment.json`;const SUNG_MP3 = `${LANE}/out/helpabeach-vocal-${SUFFIX}-sung.mp3`;
console.log(`▸ harmony stem · score=${SCORE_FILE} · suffix=${SUFFIX} · transpose=${TRANSPOSE}`);
console.log(`▸ score-pitch (WORLD f0 → ${SCORE_FILE})`);const r = spawnSync("node", [ `${REPO}/pop/bin/score-pitch.mjs`, "--slug", "helpabeach", "--section", "all", "--score", SCORE_PATH, "--vocal", VOCAL_MP3, "--words", WORDS_JSON, "--transpose", TRANSPOSE, "--vibrato-hz", "5.0", "--vibrato-cents", "14", "--out", PITCHED_MP3,], { cwd: `${REPO}/pop`, stdio: ["ignore", "inherit", "inherit"] });if (r.status !== 0) { console.error("✗ score-pitch failed"); process.exit(1); }
console.log(`▸ score-stretch (rubberband → ${BPM} BPM beat grid)`);const r2 = spawnSync("node", [ `${REPO}/pop/bin/score-stretch.mjs`, "--slug", "helpabeach", "--section", "all", "--score", SCORE_PATH, "--in", PITCHED_MP3, "--alignment", PITCHED_ALIGN, "--bpm", BPM, "--max-stretch", "12.0", "--out", SUNG_MP3,], { cwd: `${REPO}/pop`, stdio: ["ignore", "inherit", "inherit"] });if (r2.status !== 0) { console.error("✗ score-stretch failed"); process.exit(1); }
console.log(`\n✓ ${SUFFIX} stem → ${SUNG_MP3.replace(REPO + "/", "")}`);console.log(` mix: node bin/render.mjs --slug helpabeach \\`);console.log(` --vocal-stem out/helpabeach-vocal-sung.mp3 \\`);console.log(` --vocal-harmony out/helpabeach-vocal-${SUFFIX}-sung.mp3`);