Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
11 kB · 238 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239#!/usr/bin/env node// cornerfive.mjs — a /pop remix of the whistlegraph "five in the corner x2".//// The whistled TikTok (src/whistle.wav, analyzed in src/whistle.analysis.json)// is the topline — the "vocal" — and everything under it is composed// bottom-up from AC instruments: a downtempo boom-bap bed in B major /// G# minor at 73.2 BPM, chosen so four bars (13.11s) land exactly on the// length of one whistle pass, for seamless looping.//// Bed instruments (all synthesized here, no samples but the whistle):// • kick — pitched sine drop + click// • clap — layered noise bursts on the 2 & 4 backbeat// • hat — high-passed noise, 8ths with offbeat accent// • bass — lowpassed saw, syncopated roots G#–E–B–F#// • pad — detuned-saw triads (vi–IV–I–V), slow attack// • bells — sine arpeggio sparkle in the back half// The whistle gets a high-pass, slap delay, and a Schroeder reverb, and// is sidechain-ducked under the kick so it breathes with the groove.//// Usage: node bin/cornerfive.mjs (writes out/cornerfive.mp3)
import { readWavMono } from "../../lib/wav.mjs";import { spawnSync } from "node:child_process";import { writeFileSync, readFileSync, existsSync } from "node:fs";import { dirname, resolve } from "node:path";import { fileURLToPath } from "node:url";
const HERE = dirname(fileURLToPath(import.meta.url));const ROOT = resolve(HERE, "..");const SRC_SYNCED = resolve(ROOT, "src/whistle-synced.wav"); // rubberband grid+keyconst SRC_NOTES = resolve(ROOT, "src/whistle-synced.notes.json"); // the melodyconst SRC_RAW = resolve(ROOT, "src/whistle.wav"); // the real recordingconst OUT_WAV = resolve(ROOT, "out/cornerfive.wav");const OUT_MP3 = resolve(ROOT, "out/cornerfive.mp3");
// Default to the SYNCED + tuned whistle (rubberband, pitch-preserving).// Pass --raw to use the untouched original recording instead.const WANT_RAW = process.argv.includes("--raw");
const SR = 44100;const BPM = 73.2;const BEAT = 60 / BPM; // 0.8197 sconst BAR = 4 * BEAT; // 3.2787 sconst STEP = BAR / 16; // 16th noteconst TARGET_SEC = 84; // 1:24const BARS = Math.ceil(TARGET_SEC / BAR);const N = Math.ceil(TARGET_SEC * SR);const mix = new Float32Array(N);
// ── tiny DSP toolkit ───────────────────────────────────────────────────const TAU = Math.PI * 2;let seed = 0x5f3759df;function rnd() { seed ^= seed << 13; seed ^= seed >>> 17; seed ^= seed << 5; return ((seed >>> 0) / 0xffffffff) * 2 - 1; }const clamp = (x, a, b) => (x < a ? a : x > b ? b : x);const softclip = (x) => Math.tanh(x);function add(buf, pos, val) { const i = pos | 0; if (i >= 0 && i < buf.length) buf[i] += val; }
// add a windowed voice rendered by fn(t) -> sample, with AD envelopefunction voice(buf, startSec, durSec, attack, release, gain, fn) { const s0 = Math.floor(startSec * SR); const len = Math.floor(durSec * SR); const aN = Math.max(1, Math.floor(attack * SR)); const rN = Math.max(1, Math.floor(release * SR)); for (let k = 0; k < len; k++) { const t = k / SR; let env = 1; if (k < aN) env = k / aN; else if (k > len - rN) env = Math.max(0, (len - k) / rN); add(buf, s0 + k, fn(t) * env * gain); }}
const noteHz = (m) => 440 * Math.pow(2, (m - 69) / 12);
// ── kick ─────────────────────────────────────────────────────────────────function kick(buf, at, gain = 1) { voice(buf, at, 0.40, 0.001, 0.34, gain, (t) => { const f = 165 * Math.exp(-t * 38) + 48; // snappy drop → deep sub const click = Math.exp(-t * 1300) * rnd() * 0.7; // beater click const body = Math.sin(TAU * f * t + 7 * Math.exp(-t * 55)); return softclip(body * 1.5) + click; // saturated punch });}
// ── sine bell — pure sine + soft octave, bell decay (the "sister" line) ──function bell(buf, at, midi, gain = 0.3) { const f = noteHz(midi); voice(buf, at, 1.3, 0.003, 1.25, gain, (t) => { const e = Math.exp(-t * 3.0); return (Math.sin(TAU * f * t) + 0.3 * Math.sin(TAU * 2 * f * t) * Math.exp(-t * 6)) * e; }); // gentle sub an octave down for a little low-end body (not boomy) voice(buf, at, 0.9, 0.003, 0.85, gain * 0.35, (t) => Math.sin(TAU * (f / 2) * t) * Math.exp(-t * 4));}
// ── kick across the whole track ─────────────────────────────────────────const KICK = [1,0,0,0, 1,0,0,0, 1,0,1,0, 1,0,0,0]; // four-on-floor + push on "and of 3"const kickTimes = [];for (let bar = 0; bar < BARS; bar++) { for (let s = 0; s < 16; s++) { if (!KICK[s]) continue; const at = bar * BAR + s * STEP; if (at >= TARGET_SEC) break; kick(mix, at, 1.0); kickTimes.push(at); }}
// ── the whistle "vocal": tuned → time-snapped → up-front reverb ─────────const useSynced = !WANT_RAW && existsSync(SRC_SYNCED);console.log(useSynced ? "→ loading SYNCED whistle (rubberband grid-sync + autotuned to its own melody)…" : "→ loading ORIGINAL whistle (true to the recording)…");const { samples: wSrc, sampleRate: wSr } = readWavMono(useSynced ? SRC_SYNCED : SRC_RAW);// resample to SR if needed (linear)let wSamples = wSrc;if (wSr !== SR) { const ratio = SR / wSr, out = new Float32Array(Math.floor(wSrc.length * ratio)); for (let i = 0; i < out.length; i++) { const x = i / ratio, i0 = x | 0, f = x - i0; out[i] = (wSrc[i0] || 0) * (1 - f) + (wSrc[i0 + 1] || 0) * f; } wSamples = out;}
// The tuned whistle is already time-warped onto the grid + pitch-snapped by// bin/tune-whistle.mjs — one continuous stream, no slicing here (that's what// kept it buttery instead of choppy). Just place it as-is.const w = wSamples;
// normalize + high-pass (rumble out) + high-shelf air (clarity / presence)let peak = 0; for (const v of w) peak = Math.max(peak, Math.abs(v));const wg = peak > 0 ? 0.9 / peak : 1;const wet = new Float32Array(w.length);{ let prev = 0, hp = 0, lp = 0; const a = Math.exp(-TAU * 250 / SR); for (let i = 0; i < w.length; i++) { const x = w[i] * wg; hp = a * (hp + x - prev); prev = x; // one-pole high-pass ~250 Hz lp += 0.25 * (hp - lp); // split lows/highs const high = hp - lp; // upper band wet[i] = hp + 0.7 * high; // +high-shelf → clearer, more present }}// Schroeder reverb (4 combs + 2 allpass), summed wet+dryfunction reverb(input, mixWet = 0.32) { const combs = [1116, 1188, 1277, 1356].map((d) => Math.floor(d * SR / 44100)); const fb = 0.78; const allp = [556, 441].map((d) => Math.floor(d * SR / 44100)); const cb = combs.map((d) => new Float32Array(d)); const cbi = combs.map(() => 0); const ab = allp.map((d) => new Float32Array(d)); const abi = allp.map(() => 0); const out = new Float32Array(input.length + SR); // tail for (let i = 0; i < out.length; i++) { const x = i < input.length ? input[i] : 0; let acc = 0; for (let c = 0; c < cb.length; c++) { const buf = cb[c]; const idx = cbi[c]; const y = buf[idx]; buf[idx] = x + y * fb; cbi[c] = (idx + 1) % buf.length; acc += y; } acc /= cb.length; for (let a = 0; a < ab.length; a++) { const buf = ab[a]; const idx = abi[a]; const y = buf[idx]; const z = -0.5 * acc + y; buf[idx] = acc + 0.5 * y; abi[a] = (idx + 1) % buf.length; acc = z; } out[i] = (i < input.length ? input[i] : 0) * (1 - mixWet) + acc * mixWet; } return out;}const whistle = reverb(wet, 0.10); // nearly dry → clearest// slap delay (one tap, ~1/8 note) — very subtleconst slap = Math.floor(STEP * 2 * SR);for (let i = whistle.length - 1; i >= slap; i--) whistle[i] += whistle[i - slap] * 0.09;
// sidechain envelope: the VOCAL ducks under every kick, so the whistle// pumps with the beat. 1 = open, dips to 0.4 on each kick then recovers.const duck = new Float32Array(N).fill(1);const dDur = Math.floor(0.18 * SR);for (const kt of kickTimes) { const s0 = Math.floor(kt * SR); for (let k = 0; k < dDur; k++) { const i = s0 + k; if (i >= N) break; const d = 0.40 + 0.60 * (k / dDur); // dip to -8dB then recover if (d < duck[i]) duck[i] = d; }}
// loop the whistle through the WHOLE track, ducked under the kick; the sine// bells follow the same melody, an octave-spanning "sister" line.const loopLen = w.length / SR; // one synced pass (~13.1s)const melody = useSynced && existsSync(SRC_NOTES) ? JSON.parse(readFileSync(SRC_NOTES, "utf8")).notes : [];for (let t0 = 0; t0 < TARGET_SEC; t0 += loopLen) { const off = Math.floor(t0 * SR); for (let i = 0; i < whistle.length; i++) { const di = off + i; if (di >= N) break; add(mix, di, whistle[i] * 1.2 * duck[di]); } for (const nt of melody) { // sister bells track the vocal const at = t0 + nt.startSec; if (at >= TARGET_SEC) break; bell(mix, at, nt.midi, 0.26); }}
// ── master: soft-clip, normalize, write WAV ─────────────────────────────let mpeak = 0; for (let i = 0; i < N; i++) { mix[i] = softclip(mix[i] * 1.1); mpeak = Math.max(mpeak, Math.abs(mix[i])); }const norm = mpeak > 0 ? 0.97 / mpeak : 1;// gentle fade in/outconst fadeN = Math.floor(0.5 * SR);function writeWav(path, data) { const n = data.length, buf = Buffer.alloc(44 + n * 2); buf.write("RIFF", 0); buf.writeUInt32LE(36 + n * 2, 4); buf.write("WAVE", 8); buf.write("fmt ", 12); buf.writeUInt32LE(16, 16); buf.writeUInt16LE(1, 20); buf.writeUInt16LE(1, 22); buf.writeUInt32LE(SR, 24); buf.writeUInt32LE(SR * 2, 28); buf.writeUInt16LE(2, 32); buf.writeUInt16LE(16, 34); buf.write("data", 36); buf.writeUInt32LE(n * 2, 40); for (let i = 0; i < n; i++) { let v = data[i] * norm; if (i < fadeN) v *= i / fadeN; if (i > n - fadeN) v *= (n - i) / fadeN; buf.writeInt16LE(Math.round(clamp(v, -1, 1) * 32767), 44 + i * 2); } writeFileSync(path, buf);}writeWav(OUT_WAV, mix);console.log(`✓ wav → ${OUT_WAV.replace(process.env.HOME, "~")} (${(N / SR).toFixed(1)}s)`);
// ── encode mp3 (loudnorm to streaming target) ───────────────────────────console.log("→ ffmpeg → mp3…");const r = spawnSync("ffmpeg", [ "-hide_banner", "-loglevel", "error", "-y", "-i", OUT_WAV, "-af", "loudnorm=I=-11:TP=-1.0:LRA=11", "-c:a", "libmp3lame", "-b:a", "320k", OUT_MP3,], { stdio: "inherit" });if (r.status !== 0) { console.error("✗ ffmpeg failed"); process.exit(1); }console.log(`✓ mp3 → ${OUT_MP3.replace(process.env.HOME, "~")}`);