Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971#!/usr/bin/env node// render2.mjs — "whistlegraph factory --- remix (v2, the pressed voice)"//// v2 = v1's arrangement with the voice itself machine-pressed. The rule// (established on the cult lane): vocals never skip the aesthetivox —// an exposed unprocessed take is too raw to ship. v1 stamped raw slices// on principle; v2 keeps the principle and applies it to the voice:// every vocal in the score is a WORLD resynthesis from bin/aesthetivox.py// (vox/), pitch-corrected at strength 0.95 with 18 ms smoothing — tight// enough to staircase, because quantized pitch IS the cookie-cutter// concept. Full correction strengthens this record instead of sanding it.//// · stamps and lines — snapped to D natural minor on the measured// 146.83 Hz tonic, +2.4 dB singer's formant. A pressed voice.// · the wrong-die words (takes b/c) — same press against the CHROMATIC// grid: their pitches stay theirs (cutter-b B2, factory-c C#4); the// die is wrong but the stamping is exact.// · OUT OF SPEC — the drift is no longer varispeed on raw takes: copy// k plays a render whose CORRECTION GRID is detuned ±(k·9) cents// (vox/<word>.d<k>.wav) — the aesthetivox targets going out of// calibration. Timing slip, die bounces and wobble stay mechanical.// · the count-in — loner's treated-speech move: range-compressed// (^0.55), down a semitone, darkened. The operator through the// intercom, no longer raw speech.// · the final bird — WORLD-resynthesized, range-compressed and// darkened with its centre LOCKED to G#2. Treated, still outside// the factory's key.//// Everything below the bank loader is v1's score, unchanged where it// could be. v1's header follows — the arrangement it describes is still// the arrangement.//// ─────────────────────────────────────────────────────────────────────// (v1) "whistlegraph factory --- remix (the stamp)"//// A remix of `fact` — "factory 🏭 cookie-cutter🎄personalities"// (@whistlegraph, 2021-02-04, 50.8M views). The poem is three lines://// factory / cookie cutter / personalities// we must break free from the states that we're in// spinning away, I hear a bird//// and the title is an instruction. A cookie cutter's whole job is to make// IDENTICAL copies — so the arrangement's central move is a sampler doing// exactly that: the same take of the same line stamped onto the grid again// and again with zero humanization, until the middle line of the poem// comes true and the copies drift out of spec. The poem already carries// the arc; nothing had to be invented, only obeyed://// I POWER-ON 0:00 Motor hum, relay clicks finding their rhythm.// The take's own count-in — "a one, two, ready,// and…" — is the operator switching the machine on.// II THE STAMP 0:19 The kick is a press. Line 1 stamped every two// bars, IDENTICAL every time — same sample, same// gain, no jitter. That is not laziness, it is the// subject. Conveyor ticks cross the stereo field// left to right, one belt-length per bar.// III FULL SPEC 0:58 The whole poem cycles over the full groove and// the harmony starts to move (Dm · Dm · Bb · C) —// the factory at capacity, everything to tolerance.// IV OUT OF 1:36 Each successive stamp slips: cents of detune// SPEC accumulate, milliseconds of timing slop, double-// strikes, conveyor steps dropped. "we must break// free" starts punching through between the copies.// V BREAK FREE 2:14 THE TURN. Press stops, kick out. The middle line// alone on the floor of the factory — twice, then// the other pressing of it — while the last ticks// slip their timing. The machine has noticed.// VI SPINNING 2:34 The groove returns rotating instead of stamping:// AWAY "spinning away" circles the stereo field, the// harmony travels (Dm · Bb · F · C), and the bird// arrives — chirped in sine over the top, then the// word itself, an octave up.// VII SHUTDOWN 3:12 The press decelerates. Copies come out slower// and lower (a real varispeed drift, not a filter// gag), the hum winds down through a minor third,// the belt stretches its ticks apart. The last// sound is the long low "bird" from the 02-13 take// — G#2, a note the factory's key never contained —// alone, after the music has stopped.//// ══ MEASURED, NOT GUESSED (bin/harvest.py, receipt in harvest.json) ═══//// · 100 BPM is the chant's own tempo: median syllable IOI 0.299 s =// eighths at 100.3, beat_track 101.4/100.4 on the two 2021-02 takes,// and each poem line spans ~2 bars at 100 (line 2 is 4.80 s against a// 4.80 s cell — the take is already ON this grid).// · D minor is the chant's own key: factory, cookie, spinning, I all sit// on D3 (~147 Hz); the pitch set is D·F·G·A·Bb·C; "bird" lands on Bb2.// · The stamps are raw slices at natural speed — nothing is dragged to// fit, because at the source's own tempo nothing has to be.//// Every mixing rule from pop/cult carries over: 10 ms raised-cosine tails// on every voice, ducks that ramp rather than step, sine-bump bass, no// master tanh, mono-safe equal-power pans with a band-limited antisymmetric// side return, one linear trim at the end. Mastering is cut-v1.sh's job.//// node pop/factory/bin/render2.mjs # → out/factory-remix-v2-full.wav// node pop/factory/bin/render2.mjs --stems # + per-bus stems// bash pop/factory/bin/cut-v2.sh # master + mp3
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";import { execFileSync } from "node:child_process";import { dirname, join, resolve } from "node:path";import { fileURLToPath } from "node:url";import { readWavMono } from "../../lib/wav.mjs";
const HERE = dirname(fileURLToPath(import.meta.url));const LANE = resolve(HERE, "..");const REPO = resolve(LANE, "../..");const OUT = resolve(LANE, "out");mkdirSync(OUT, { recursive: true });
const SR = 48_000;// @jeffrey: "should be slower". The chant was measured at ~100; the// record now runs at 90 and the stamps BREATHE — each 2-bar cell is// 5.33 s against line spans of 4.2–5.0 s, so every pressing gets real// air before the next cycle. The words are NOT stretched to the new// grid: each line is stamped as one die-block at the take's own internal// word offsets (seconds, measured), because a pressing is one gesture.const BPM = 90;const BEAT = 60 / BPM; // 0.667 sconst BAR = 4 * BEAT; // 2.667 sconst BARS = 88; // 234.7 s = 3:55const TAU = Math.PI * 2;const N = Math.round((BARS * BAR + 3.2) * SR);
// Four buses. Drums never duck. The bed (music) takes the full breath, the// voices a quarter-depth duck, the machine layer (presses' hiss, relays,// conveyor) a half-depth one so it sits inside the groove, not on top.const musicL = new Float32Array(N), musicR = new Float32Array(N);const drumsL = new Float32Array(N), drumsR = new Float32Array(N);const voxL = new Float32Array(N), voxR = new Float32Array(N);const machL = new Float32Array(N), machR = new Float32Array(N);const sideB = new Float32Array(N); // bed's side fieldconst sideV = new Float32Array(N); // voices'const sideM = new Float32Array(N); // the machine'sconst dlySend = new Float32Array(N);
const VOXG = 1.42; // the +3 dB the vox bus earned in cult v2const MACHG = 2.40; // the machine layer — first stem pass read // -41 LUFS, a factory with no belt; this // plus the tick gains brings it to ~-26const clamp = (v, a, b) => (v < a ? a : v > b ? b : v);const hz = (midi) => 440 * Math.pow(2, (midi - 69) / 12);const at = (bar, beat = 0) => bar * BAR + beat * BEAT;const smooth = (u) => (u <= 0 ? 0 : u >= 1 ? 1 : u * u * (3 - 2 * u));
// Debug: MUTE=press node bin/render.mjs subtracts a voice; ONLY=bird// renders one alone — the fastest way to argue about one layer.const MUTED = new Set((process.env.MUTE || "").split(",").map((v) => v.trim()).filter(Boolean));const ONLY = new Set((process.env.ONLY || "").split(",").map((v) => v.trim()).filter(Boolean));const allow = (v) => !MUTED.has(v) && (!ONLY.size || ONLY.has(v));if (MUTED.size) console.log(" MUTED:", [...MUTED].join(", "));if (ONLY.size) console.log(" ONLY:", [...ONLY].join(", "));const EVENTS = [];
// The click amnesty — every voice exits through 10 ms of raised cosine.const tailFade = (i, n) => { const u = (n - 1 - i) / (0.010 * SR); return u >= 1 ? 1 : u <= 0 ? 0 : u * u * (3 - 2 * u);};
// ── sample bank ───────────────────────────────────────────────────────const BANK = {};function load(name, path) { if (!existsSync(path)) { console.warn(` ! missing sample ${path}`); return; } let p = path; if (!path.endsWith(".wav")) { p = join(OUT, `.cache-${name}.wav`); if (!existsSync(p)) execFileSync("ffmpeg", ["-y", "-v", "error", "-i", path, "-ac", "1", "-ar", String(SR), p]); } const { samples, sampleRate } = readWavMono(p); if (sampleRate !== SR) { const q = join(OUT, `.cache-${name}-rs.wav`); execFileSync("ffmpeg", ["-y", "-v", "error", "-i", p, "-ac", "1", "-ar", String(SR), q]); BANK[name] = readWavMono(q).samples; } else BANK[name] = samples; const s = BANK[name]; let a = 0; while (a < s.length - 1 && Math.abs(s[a]) < 0.008) a++; const t = s.subarray(Math.max(0, a - Math.round(0.002 * SR))); let peak = 0; for (const v of t) peak = Math.max(peak, Math.abs(v)); const g = peak > 1e-6 ? 0.95 / peak : 1; const out = new Float32Array(t.length); for (let i = 0; i < t.length; i++) out[i] = t[i] * g; BANK[name] = out;}
const SAMPLES = resolve(LANE, "samples");if (!existsSync(SAMPLES)) { console.error("samples/ missing — run ../.venv/bin/python bin/harvest.py first"); process.exit(1);}for (const f of readdirSync(SAMPLES)) if (f.endsWith(".wav")) load(f.replace(/\.wav$/, ""), join(SAMPLES, f));
// The aesthetivox bank OVERRIDES the raw slices name-for-name, and the// drifted variants (<word>.d<k>) arrive alongside. v2 refuses to run// without it — falling back to raw takes silently would defeat the rule.const VOX = resolve(LANE, "vox");if (!existsSync(VOX)) { console.error("vox/ missing — run ../.venv/bin/python bin/aesthetivox.py first"); process.exit(1);}let pressed = 0;for (const f of readdirSync(VOX)) if (f.endsWith(".wav")) { load(f.replace(/\.wav$/, ""), join(VOX, f)); pressed++; }console.log(` aesthetivox bank: ${pressed} pressed renders (raw slices overridden)`);
const DEMOS = resolve(REPO, "pop/demos/samples");for (const [n, f] of Object.entries({ hatC: "perc-hat-c.mp3", hatO: "perc-hat-o.mp3", clap: "perc-clap.mp3", ride: "perc-ride.mp3", snap: "perc-snap.mp3",})) load(n, join(DEMOS, f));
// ── space ─────────────────────────────────────────────────────────────function spatial(az) { const itd = Math.round(0.00027 * SR * Math.sin(az)); const shadow = 0.35 * Math.sin(az); return { itd, gl: 1 - shadow, gr: 1 + shadow };}
function emit(bus, i, mono, pan, sp, sideAmt, dly = 0) { if (i < 0 || i >= N) return; const a = (Math.PI / 4) * (1 + pan); const cl = Math.cos(a), cr = Math.sin(a); if (bus === "drums") { drumsL[i] += mono * cl; drumsR[i] += mono * cr; } else if (bus === "vox") { voxL[i] += mono * cl; voxR[i] += mono * cr; } else if (bus === "mach") { machL[i] += mono * cl; machR[i] += mono * cr; } else { musicL[i] += mono * cl; musicR[i] += mono * cr; } if (dly) dlySend[i] += mono * dly; if (sp && sideAmt) { const li = i + sp.itd, ri = i - sp.itd; const l = li >= 0 && li < N ? mono * sp.gl : 0; const r = ri >= 0 && ri < N ? mono * sp.gr : 0; const s = 0.5 * (l - r) * sideAmt; if (bus === "vox") sideV[i] += s; else if (bus === "mach") sideM[i] += s; else sideB[i] += s; }}
// ── sidechain ─────────────────────────────────────────────────────────const kicks = [];function buildEnv(triggers) { const e = new Float32Array(N).fill(1); const pre = Math.round(0.010 * SR); for (const { t, depth, atk, rel } of triggers) { const i0 = Math.round(t * SR) - pre; const span = pre + Math.round((atk + rel) * SR); for (let i = 0; i < span; i++) { const j = i0 + i; if (j < 0) continue; if (j >= N) break; const dt = i / SR - 0.010; let g; if (dt < atk) g = 1 - depth * clamp((dt + 0.010) / (0.010 + atk), 0, 1); else { const u = clamp((dt - atk) / rel, 0, 1); g = (1 - depth) + depth * smooth(u); } if (g < e[j]) e[j] = g; } } return e;}
// ── the kick — cult v5's thick electro press-foot, unchanged ──────────const SATN = Math.tanh(2.4);function kick(t, gain = 1, { weight = 1 } = {}) { if (!allow("kick")) return; kicks.push(t); EVENTS.push({ t: +t.toFixed(4), voice: "kick", bus: "drums", dur: 0.52, gain: +gain.toFixed(3) }); const n = Math.round(0.52 * SR), i0 = Math.round(t * SR); let ph = 0, sub = 0; for (let i = 0; i < n; i++) { const u = i / SR; const f = 47 + 153 * Math.exp(-u * 62); ph += (TAU * f) / SR; sub += (TAU * 44) / SR; const slam = Math.exp(-u * 34), tail = Math.exp(-u * 7.0); const env = (0.62 * slam + 0.52 * tail) * Math.min(1, u / 0.0009); const body = Math.tanh(Math.sin(ph) * env * 2.4) / SATN; const low = Math.sin(sub) * Math.exp(-u * 5.6) * 0.40 * weight; const click = Math.exp(-u * 300) * 0.13 * Math.sin(TAU * 1600 * u) + Math.exp(-u * 760) * 0.075 * Math.sin(TAU * 3900 * u); emit("drums", i0 + i, (body + low + click) * 0.74 * gain * tailFade(i, n), 0, null, 0); }}
// ── THE PRESS — the stamp made audible ────────────────────────────────// A die coming down is a thud plus metal. The thud is a short 120→55 Hz// sweep; the metal is three INHARMONIC partials in free-bar ratios// (1 : 2.76 : 5.40 — a struck bar, not a chord) around a base tuned to// the section, with a 2 ms noise chiff for the contact. Fast decay: a// press strikes, it does not ring like a bell.function press(t, gain = 1, { base = 392, pan = 0, ring = 1 } = {}) { if (!allow("press")) return; EVENTS.push({ t: +t.toFixed(4), voice: "press", bus: "drums", dur: 0.30, gain: +gain.toFixed(3), hz: base }); const n = Math.round(0.30 * SR), i0 = Math.round(t * SR); const sp = spatial(pan * 1.2); const part = [1, 2.76, 5.40].map((r) => ({ f: base * r, p: 0 })); let ph = 0, hp = 0, prev = 0; for (let i = 0; i < n; i++) { const u = i / SR; const f = 55 + 65 * Math.exp(-u * 90); ph += (TAU * f) / SR; const thud = Math.sin(ph) * Math.exp(-u * 26) * 0.85; let metal = 0; for (let k = 0; k < 3; k++) { part[k].p += (TAU * part[k].f) / SR; metal += Math.sin(part[k].p) * Math.exp(-u * (34 + k * 26)) * [0.42, 0.30, 0.16][k]; } const w = nrnd(); hp = 0.80 * (hp + w - prev); prev = w; const chiff = hp * Math.exp(-u * 480) * 0.55; const env = Math.min(1, u / 0.0012); emit("drums", i0 + i, (thud + metal * ring + chiff) * env * 0.62 * gain * tailFade(i, n), pan, sp, 0.25); }}
// ── pneumatic hiss — the press lifting off ────────────────────────────function hiss(t, gain = 1, { pan = 0.3, dur = 0.22 } = {}) { if (!allow("hiss")) return; EVENTS.push({ t: +t.toFixed(4), voice: "hiss", bus: "mach", dur, gain: +gain.toFixed(3), pan }); const n = Math.round((dur + 0.03) * SR), i0 = Math.round(t * SR); const sp = spatial(pan * 1.2); let lp = 0; for (let i = 0; i < n; i++) { const u = i / SR; lp += 0.16 * (nrnd() - lp); let env = Math.min(1, u / 0.010) * Math.exp(-u * (3.4 / dur) * 1.6); if (u > dur) env *= Math.max(0, 1 - (u - dur) / 0.03); emit("mach", i0 + i, lp * env * 1.10 * gain * tailFade(i, n), pan, sp, 0.6); }}
// ── sine-bump bass (cult's, verbatim) ─────────────────────────────────function bass(t, midi, dur, gain = 1, slideFrom = null) { if (!allow("bass")) return; EVENTS.push({ t: +t.toFixed(4), voice: "bass", bus: "music", midi, dur: +dur.toFixed(3), gain: +gain.toFixed(3) }); const n = Math.round((dur + 0.12) * SR), i0 = Math.round(t * SR); const f1 = hz(midi), f0 = slideFrom !== null ? hz(slideFrom) : f1; let p1 = 0, p2 = 0, p3 = 0, lp = 0; for (let i = 0; i < n; i++) { const u = i / SR; const glide = smooth(clamp(u / 0.075, 0, 1)); const f = f0 + (f1 - f0) * glide; p1 += (TAU * f) / SR; p2 += (TAU * f * 0.5) / SR; p3 += (TAU * f * 2) / SR; let env = Math.min(1, u / 0.012); if (u > dur) env *= Math.max(0, 1 - (u - dur) / 0.12); const s = Math.sin(p1) + 0.52 * Math.sin(p2) + 0.10 * Math.sin(p3); lp += 0.50 * (s - lp); emit("music", i0 + i, lp * 0.40 * env * gain * tailFade(i, n), 0, null, 0); }}
// Harmonized sines — pad and stab, for the sections that travel.function sines(t, midis, dur, gain, pan, sideAmt = 0.5, bright = 1, dly = 0, attack = 0.020) { if (!allow(dur > 1 ? "pad" : "stab")) return; EVENTS.push({ t: +t.toFixed(4), voice: dur > 1 ? "pad" : "stab", bus: "music", midis, dur: +dur.toFixed(3), gain: +gain.toFixed(3), pan: +pan.toFixed(2) }); const n = Math.round((dur + 0.40) * SR), i0 = Math.round(t * SR); const sp = spatial(pan * 1.2); const ph = midis.map(() => [0, 0, 0]); let lp = 0; for (let i = 0; i < n; i++) { const u = i / SR; let s = 0; for (let v = 0; v < midis.length; v++) { const f = hz(midis[v]); ph[v][0] += (TAU * f) / SR; ph[v][1] += (TAU * f * 1.0035) / SR; ph[v][2] += (TAU * f * 2) / SR; s += Math.sin(ph[v][0]) + 0.7 * Math.sin(ph[v][1]) + 0.08 * bright * Math.sin(ph[v][2]); } s /= midis.length * 1.8; lp += 0.28 * (s - lp); let env = Math.min(1, u / attack); if (u > dur) env *= Math.max(0, 1 - (u - dur) / 0.40); emit("music", i0 + i, lp * env * gain * tailFade(i, n), pan, sp, sideAmt, dly); }}
// ── MOTOR HUM — the factory's floor tone ──────────────────────────────// D1 + D2 + a whisper of the 3rd harmonic, fluttering at 0.4 Hz with an// 8.3 Hz rotation roughness (a motor is a pitch with a wobble budget).// `bend` glides the whole stack — shutdown winds it down a minor third.function hum(t, dur, gain = 1, { bend = 1, fadeIn = 2.0, fadeOut = 2.0 } = {}) { if (!allow("hum")) return; EVENTS.push({ t: +t.toFixed(4), voice: "hum", bus: "music", dur: +dur.toFixed(2), gain: +gain.toFixed(3), ...(bend !== 1 ? { bend } : {}) }); const n = Math.round(dur * SR), i0 = Math.round(t * SR); let p1 = 0, p2 = 0, p3 = 0, lp = 0; for (let i = 0; i < n; i++) { const u = i / SR; const g = Math.pow(bend, smooth(u / dur)); // slow spectral fall const f = 36.71 * g; // D1, bending p1 += (TAU * f) / SR; p2 += (TAU * f * 2.003) / SR; p3 += (TAU * f * 3) / SR; const flutter = 1 + 0.05 * Math.sin(TAU * 0.4 * u) + 0.018 * Math.sin(TAU * 8.3 * u); const s = 0.9 * Math.sin(p1) + 0.55 * Math.sin(p2) + 0.13 * Math.sin(p3); lp += 0.09 * (s - lp); let env = smooth(clamp(u / fadeIn, 0, 1)); if (u > dur - fadeOut) env *= smooth(clamp((dur - u) / fadeOut, 0, 1)); emit("music", i0 + i, lp * flutter * env * 0.30 * gain * tailFade(i, n), 0, null, 0); }}
// ── clicks and taps — relays and belt ─────────────────────────────────let nseed = 20210204; // the post date is the seedconst nrnd = () => { nseed ^= nseed << 13; nseed >>>= 0; nseed ^= nseed >>> 17; nseed ^= nseed << 5; nseed >>>= 0; return (nseed / 4294967295) * 2 - 1;};function noiseHit(t, { gain = 1, pan = 0, side = 0.5, dur = 0.004, tone = 0, q = 0.35, dly = 0, evVoice = "click" } = {}) { if (!allow(evVoice)) return; EVENTS.push({ t: +t.toFixed(4), voice: evVoice, bus: "mach", dur: +dur.toFixed(4), gain: +gain.toFixed(3), pan: +pan.toFixed(2) }); const n = Math.round((dur + 0.006) * SR), i0 = Math.round(t * SR); const sp = spatial(pan * 1.2); let hp = 0, prev = 0, lp = 0, p = 0; for (let i = 0; i < n; i++) { const u = i / SR; const w = nrnd(); hp = 0.86 * (hp + w - prev); prev = w; lp += 0.42 * (hp - lp); let v = lp; if (tone) { p += (TAU * tone) / SR; v = v * (1 - q) + Math.sin(p) * q * Math.exp(-u * 120); } const env = Math.min(1, u / 0.0009) * Math.exp(-u * (tone ? 150 : 620)); emit("mach", i0 + i, v * env * 1.10 * gain * tailFade(i, n), pan, sp, side, dly); }}const click = (t, o = {}) => noiseHit(t, { dur: 0.004, tone: 0, evVoice: "click", ...o });const tick = (t, o = {}) => noiseHit(t, { dur: 0.014, tone: 1450, q: 0.38, evVoice: "tick", ...o });
// The conveyor: sixteen ticks a bar that CROSS the stereo field — one// belt-length per bar, left to right, accent every fourth. `drop` is the// fraction of steps the belt loses (out-of-spec wear), deterministic.function conveyor(bar, gain = 1, { drop = 0 } = {}) { for (let k = 0; k < 16; k++) { if (drop > 0 && rnd() < drop) continue; const t = at(bar) + k * (BEAT / 4); const pan = -0.65 + 1.3 * (k / 15); const acc = k % 4 === 0 ? 1.0 : 0.55; tick(t, { gain: 0.34 * gain * acc, pan, side: 0.7 }); }}
// ── THE BIRD — a sine whistle, not a sample ───────────────────────────// Two-note chirps gliding up a fourth, vibrato arriving late, high over// the machine and echoed through the dub delay. The one voice in the// track with no grid at all.function birdcall(t, f0 = 2349, { gain = 1, pan = 0.4, chirps = 3 } = {}) { if (!allow("bird")) return; EVENTS.push({ t: +t.toFixed(4), voice: "bird", bus: "vox", hz: f0, gain: +gain.toFixed(3), pan: +pan.toFixed(2), chirps }); const sp = spatial(pan * 1.2); for (let c = 0; c < chirps; c++) { const t0 = t + c * 0.21, dur = 0.13, n = Math.round(dur * SR); const i0 = Math.round(t0 * SR); let p = 0; for (let i = 0; i < n; i++) { const u = i / SR; const gl = smooth(clamp(u / 0.09, 0, 1)); const vib = 1 + 0.012 * Math.sin(TAU * 31 * u) * smooth(u / 0.05); const f = f0 * (0.82 + 0.33 * gl) * vib; p += (TAU * f) / SR; const env = Math.sin(Math.PI * clamp(u / dur, 0, 1)); emit("vox", i0 + i, Math.sin(p) * env * 0.16 * gain * tailFade(i, n), pan, sp, 0.75, 0.5); } }}
// ── the one-shot player (cult's, with the wiggle) ─────────────────────// `wigDrift` is the shutdown's whole trick: cents of varispeed drift// applied over the note, so a copy can come out of the press slower and// lower — a real deceleration of the read head, not a post effect.const missing = new Set();function shot(name, t, { gain = 1, pan = 0, semis = 0, bus = "vox", side = 0.5, dark = 0, dur = null, dly = 0, off = 0, evVoice = null, wig = 0, wigHz = 5.0, wigPhase = 0, wigDrift = 0, wigIn = 0.45,} = {}) { const s = BANK[name]; if (!s) { missing.add(name); return; } if (!allow(evVoice ?? "word")) return; const step = Math.pow(2, semis / 12); const start = Math.max(0, Math.min(s.length - 2, Math.round(off * SR))); let avail = Math.floor((s.length - 2 - start) / step); if (wig) avail = Math.floor(avail * 0.965); if (wigDrift) { // The drift ramps in on a smoothstep, so the average rate over the note // is 2^(drift/2 /1200). A slowing read (negative drift) therefore needs // MORE output samples to cover the same input — and either way we stop // short of the end, because running out mid-read would exit without the // tail fade, which is the one thing we never allow. const avg = Math.pow(2, (wigDrift * 0.5) / 1200); avail = Math.floor((avail / Math.max(avg, 0.05)) * (wigDrift > 0 ? 0.90 : 0.94)); } const n = dur ? Math.min(avail, Math.round(dur * SR)) : avail; if (n <= 4) return; const i0 = Math.round(t * SR); EVENTS.push({ t: +t.toFixed(4), voice: evVoice ?? "word", bus, sample: name, dur: +(n / SR).toFixed(3), gain: +gain.toFixed(3), pan: +pan.toFixed(2), ...(semis ? { semis } : {}), ...(wig ? { wiggleCents: +wig.toFixed(1) } : {}), ...(wigDrift ? { driftCents: +wigDrift.toFixed(1) } : {}), }); const sp = spatial(pan * 1.2); const span = n / SR; const ramp = Math.max(1e-4, wigIn); let lp = 0, pos = start; for (let i = 0; i < n; i++) { const q = pos | 0; if (q + 1 >= s.length) break; const f = pos - q; let v = s[q] + (s[q + 1] - s[q]) * f; if (dark > 0) { lp += (1 - dark) * (v - lp); v = lp; } const env = Math.min(1, i / (0.0015 * SR)); emit(bus, i0 + i, v * env * gain * tailFade(i, n), pan, sp, side, dly); if (wig || wigDrift) { const u = i / SR; const d = smooth(u / ramp); const cents = d * wig * Math.sin(TAU * wigHz * u + wigPhase) + wigDrift * smooth(u / span); pos += step * Math.pow(2, cents / 1200); } else pos += step; }}const word = (name, t, o = {}) => shot(name, t, { bus: "vox", side: 0.5, ...o });
// ── harmony ───────────────────────────────────────────────────────────// D natural minor — the chant's measured key. Machines don't modulate, so// the stamp sections sit on i; FULL SPEC walks i·i·VI·VII and SPINNING// walks i·VI·III·VII (Dm Bb F C), one chord a bar... the classic row the// poem's escape deserves.const SCALE = [0, 2, 3, 5, 7, 8, 10]; // natural minorconst sd = (i) => SCALE[((i % 7) + 7) % 7] + 12 * Math.floor(i / 7);const D2 = 38; // bass rootconst bassRoot = (deg) => D2 + sd(deg);const triad = (deg, base = 62) => [sd(deg), sd(deg + 2), sd(deg + 4)].map((s) => base + s);
// ── form ──────────────────────────────────────────────────────────────const S = { poweron: [0, 8], stamp: [8, 24], spec: [24, 40], drift: [40, 56], breakfree: [56, 64], spinning: [64, 80], shutdown: [80, 88],};const ACTS = { poweron: "I · POWER-ON", stamp: "II · THE STAMP", spec: "III · FULL SPEC", drift: "IV · OUT OF SPEC", breakfree: "V · BREAK FREE", spinning: "VI · SPINNING AWAY", shutdown: "VII · SHUTDOWN",};const inS = (bar, k) => bar >= S[k][0] && bar < S[k][1];
const SPEC_ROW = [0, 0, 5, 6]; // i · i · VI · VIIconst SPIN_ROW = [0, 5, 2, 6]; // i · VI · III · VIIfunction degAt(bar) { if (inS(bar, "spec")) return SPEC_ROW[(bar - S.spec[0]) % 4]; if (inS(bar, "spinning")) return SPIN_ROW[Math.floor((bar - S.spinning[0]) / 2) % 4]; if (inS(bar, "breakfree")) return bar < 60 ? 5 : 6; // Bb, then C, hanging return 0;}
let seed = 20210204;const rnd = () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296);
// ── THE STAMP — each line one die-block at its own internal timing ────// Word offsets are SECONDS measured from each take's own pressing (the// whisper word timestamps in harvest.json/survey.json), not beats: a// pressing is one gesture and stays one. At 90 the cell gives it air.// In THE STAMP every copy of a given die is identical by construction:// same slices, same gains, no jitter. The drift parameters exist so OUT// OF SPEC can spend them; there the die also changes per batch — the// factory has more than one now.const LINE1_WORDS = ["factory", "cookie", "cutter", "personalities"];const LINE1_OFFS = { // word starts inside each pressing "": [0, 0.78, 1.45, 2.15], // a — the original "-b": [0, 0.78, 1.72, 2.52], "-c": [0, 0.78, 1.45, 2.12], "-d": [0, 0.56, 1.31, 2.05], // the 6.7M bright pressing "-e": [0, 1.52, 2.24, 2.96], // the low slow 2024 pressing "-f": [0, 0.61, 2.26, 3.77], // cookay cubber — runs long, in character};const LINE1_G = [1.0, 0.95, 0.97, 1.0];// v2: `driftK` replaces v1's varispeed `cents` — copy k plays the render// whose correction grid was itself detuned ±(k·9) cents by aesthetivox.py.// The detune is baked into the audio; the read head stays honest.function stampLine1(bar, g = 1, { slipMs = 0, driftK = 0, wig = 0, dblAt = -1, dark = 0, take = "",} = {}) { const offs = LINE1_OFFS[take] ?? LINE1_OFFS[""]; for (let w = 0; w < LINE1_WORDS.length; w++) { const name = LINE1_WORDS[w]; let nm = take && BANK[name + take] ? name + take : name; if (driftK && BANK[`${nm}.d${driftK}`]) nm = `${nm}.d${driftK}`; const slip = slipMs ? ((w % 2 ? -1 : 1) * slipMs) / 1000 : 0; const t = at(bar) + offs[w] + slip; word(nm, t, { gain: 0.92 * g * LINE1_G[w], dark, wig, wigHz: 4.3 + w * 0.7, wigPhase: w * 1.9, pan: 0, side: 0.4, evVoice: "stamp", }); if (dblAt === w) // a die bounce — the copy struck twice word(nm, t + 0.048, { gain: 0.34 * g, semis: -0.18, dark: 0.3, pan: 0.25, side: 0.5, evVoice: "stamp" }); }}
// ══ THE SCORE ═════════════════════════════════════════════════════════
// ── I · POWER-ON (bars 0–8) ───────────────────────────────────────────// The hum arrives, relays find a rhythm, and the operator counts the// machine in. The count-in is the original take's own first two seconds.hum(at(0), at(8) - at(0) + 1.5, 0.9, { fadeIn: 3.0, fadeOut: 0.8 });// relay clicks, sparse then finding the eighth gridfor (let b = 1; b < 8; b++) { const density = b < 3 ? 2 : b < 5 ? 4 : 8; for (let k = 0; k < density; k++) { const t = at(b) + (k * BAR) / density + (b < 5 ? rnd() * 0.06 - 0.03 : 0); click(t, { gain: 0.45 + 0.05 * b, pan: (k % 2 ? 0.4 : -0.4), side: 0.7 }); }}hiss(at(3), 0.6, { pan: -0.4, dur: 0.30 });hiss(at(5, 2), 0.7, { pan: 0.4, dur: 0.26 });// the PA finds its voice before the operator does — "here's the factory"// (the 2022 follow-along take, intercom-treated)word("heres-the-factory", at(1, 2), { gain: 0.72, dark: 0.1, pan: -0.2, side: 0.5, dly: 0.3, evVoice: "intercom" });word("count-in", at(4), { gain: 1.05, pan: 0, side: 0.3, evVoice: "count" });// the machine memorized the operator: "and" again, an octave down, darkword("and", at(6, 2), { gain: 0.55, semis: -12, dark: 0.45, pan: -0.3, side: 0.5, evVoice: "count" });press(at(7, 3.5), 0.7, { base: 294, ring: 0.7 }); // the die seats itself
// ── II · THE STAMP (bars 8–24) ────────────────────────────────────────for (let b = 8; b < 24; b++) { kick(at(b, 0), 1.0); kick(at(b, 1), 0.96); kick(at(b, 2), 1.0); kick(at(b, 3), 0.96); conveyor(b, b < 10 ? 0.6 : 1.0); // offbeat sine-bump bass on the root — the factory floor moving for (let e = 0; e < 4; e++) bass(at(b, e + 0.5), D2, 0.22, 0.80); if (b % 2 === 0) { press(at(b), 1.0, { base: 392 }); hiss(at(b, 3.5), 0.55, { pan: 0.35 }); }}// The die ROTATES between batches now — a, then the bright 6.7M pressing// (d), then the low 2024 one (e) — but within a batch every copy is// identical. More dies, same discipline.const STAMP_DIES = ["", "", "", "-d", "", "-e", "-d", ""];for (let c = 0; c < 8; c++) { const b = 8 + c * 2; if (c === 0) continue; // one empty cycle: press first, words second stampLine1(b, 1.0, { take: STAMP_DIES[c] });}
// ── III · FULL SPEC (bars 24–40) ──────────────────────────────────────// The whole poem, twice around an 8-bar cell, the harmony walking.for (let b = 24; b < 40; b++) { kick(at(b, 0), 1.0); kick(at(b, 1), 0.96); kick(at(b, 2), 1.0); kick(at(b, 3), 0.96); conveyor(b, 1.0); const root = bassRoot(degAt(b)); for (let e = 0; e < 4; e++) bass(at(b, e + 0.5), root, 0.22, 0.80); // @jeffrey: "the snares / claps are too much" — one clap, backbeat // only, and quieter; the conveyor ticks are the timekeeper. if (BANK.clap) shot("clap", at(b, 3), { bus: "drums", gain: 0.22, pan: -0.1, side: 0.3, evVoice: "clap" }); if (b % 4 === 0) press(at(b), 1.0, { base: 392 }); // a low pad breathing under the poem, one chord a bar (the row moves // per bar, so the pad must too) sines(at(b), triad(degAt(b), 50), BAR - 0.05, 0.16, 0, 0.6, 0.6, 0, 0.7);}// Cell one is the original pressing; cell two swaps in the other dies'// lines — the bright d pressing of line 2, the low e pressing of line 3.for (const [cell, l2, l3] of [[24, "line2", "line3"], [32, "line2-d", "line3-e"]]) { stampLine1(cell, 1.0, { take: cell === 32 ? "-d" : "" }); word(l2, at(cell + 2), { gain: 1.00, pan: -0.12, side: 0.5, evVoice: "line" }); word(l3, at(cell + 4), { gain: 1.00, pan: 0.12, side: 0.5, evVoice: "line" }); // bars +6,+7: the machine answers — personalities echoed through the dub word("personalities", at(cell + 6), { gain: 0.55, semis: -5, dark: 0.35, pan: 0.4, side: 0.7, dly: 0.5, evVoice: "echo" }); word("cutter", at(cell + 7), { gain: 0.50, semis: -5, dark: 0.35, pan: -0.4, side: 0.7, dly: 0.5, evVoice: "echo" });}
// ── IV · OUT OF SPEC (bars 40–56) ─────────────────────────────────────// Eight more stamps, and stamp k is k steps out of tolerance: cents and// milliseconds accumulate, the die bounces, the belt drops steps, and the// middle line starts forcing its way between the copies.for (let b = 40; b < 56; b++) { const k = (b - 40) / 2; kick(at(b, 0), 1.0); kick(at(b, 1), 0.96); kick(at(b, 2), 1.0); kick(at(b, 3), 0.96); conveyor(b, 1.0, { drop: (b - 40) / 40 }); for (let e = 0; e < 4; e++) bass(at(b, e + 0.5), D2, 0.22, 0.80); if (b % 2 === 0) { press(at(b), 1.0, { base: 392 * Math.pow(2, -k * 0.005) }); hiss(at(b, 3.5), 0.5, { pan: 0.35 }); }}for (let k = 0; k < 8; k++) { const b = 40 + k * 2; stampLine1(b, 1.0 - k * 0.025, { slipMs: k * 9, driftK: k, wig: k * 3.2, // grid detuned ±(k·9)c, baked dblAt: k >= 3 ? (k % LINE1_WORDS.length) : -1, dark: k * 0.03, // wrong dies in the press — b, then the 2026 "cookay cubber" (f), // then the bright c take: k === 5 ? "-b" : k === 6 ? "-f" : k === 7 ? "-c" : "", }); if (k >= 2) word("break-free", at(b + 1, 1.5), { gain: 0.55 + k * 0.05, pan: (k % 2 ? 0.45 : -0.45), side: 0.6, dly: 0.35, evVoice: "interject" }); if (k >= 5) word("we", at(b + 1, 3), { gain: 0.45, semis: -2, dark: 0.2, pan: (k % 2 ? -0.5 : 0.5), side: 0.7, dly: 0.4, evVoice: "interject" });}
// ── V · BREAK FREE (bars 56–64) ───────────────────────────────────────// The turn. Press stops, kick out, belt slipping. The middle line of the// poem, alone on the factory floor — then the OTHER pressing of it, the// same words in a different voice, which is what breaking out of a// cookie-cutter personality sounds like.hum(at(56), at(64) - at(56) + 1.0, 0.75, { fadeIn: 0.6, fadeOut: 1.2 });sines(at(56), triad(5, 50), BAR * 4 - 0.2, 0.15, 0, 0.7, 0.5, 0.2, 1.4); // Bb, hangingsines(at(60), triad(6, 50), BAR * 3.5, 0.14, 0, 0.7, 0.5, 0.2, 1.4); // C, askingword("line2", at(56, 1), { gain: 1.12, pan: 0, side: 0.4, evVoice: "line" });word("break-free", at(58, 3), { gain: 0.6, dly: 0.55, pan: 0.4, side: 0.7, evVoice: "echo" });word("line2-b", at(60), { gain: 0.95, pan: -0.3, dark: 0.15, side: 0.6, evVoice: "line" });// and the PA justifies itself, right at the turn: "it has a score and it// can be performed the same way every time" (the 58 s talk, treated)word("intercom-sameway", at(61, 3), { gain: 0.55, dark: 0.1, pan: 0.25, side: 0.6, dly: 0.4, evVoice: "intercom" });// the last ticks slip their timing — the belt creeping out of clockfor (let k = 0; k < 14; k++) { const t = at(56) + k * (BEAT * 1.07 + k * 0.013); if (t > at(63, 2)) break; tick(t, { gain: 0.30, pan: -0.5 + (k % 3) * 0.5, side: 0.7 });}// three presses, tightening — the machine trying to restart itselfpress(at(62, 2), 0.55, { base: 349, ring: 0.8 });press(at(63, 0.5), 0.70, { base: 370, ring: 0.9 });press(at(63, 2.5), 0.85, { base: 392, ring: 1.0 });hiss(at(63, 3.2), 0.8, { pan: 0, dur: 0.35 });
// ── VI · SPINNING AWAY (bars 64–80) ───────────────────────────────────// The groove comes back rotating instead of stamping. "spinning away"// circles the field — each repetition a step further around — while the// harmony finally travels and the bird arrives over the top.for (let b = 64; b < 80; b++) { kick(at(b, 0), 1.0); kick(at(b, 1), 0.96); kick(at(b, 2), 1.0); kick(at(b, 3), 0.96); conveyor(b, 0.85); const root = bassRoot(degAt(b)); for (let e = 0; e < 4; e++) bass(at(b, e + 0.5), root, 0.22, 0.82); if (BANK.clap) // backbeat only, thinned shot("clap", at(b, 3), { bus: "drums", gain: 0.24, pan: -0.1, side: 0.3, evVoice: "clap" }); if (BANK.hatO && b % 2 === 1) shot("hatO", at(b, 2.5), { bus: "drums", gain: 0.22, pan: 0.3, side: 0.5, evVoice: "hat" }); if (BANK.ride && b >= 72) shot("ride", at(b, 0.5), { bus: "drums", gain: 0.15, pan: -0.3, side: 0.5, evVoice: "hat" }); if (b % 2 === 0) sines(at(b), triad(degAt(b), 62), BAR * 2 - 0.1, 0.13, 0.2, 0.7, 1.0, 0.25, 0.6);}for (let r = 0; r < 8; r++) { const b = 64 + r * 2; const pan = 0.8 * Math.sin((TAU * r) / 4); // a full circle every 8 bars const nm = r % 2 === 0 ? "spinning-away" : "spinning-away-c"; word(nm, at(b, 0), { gain: 0.95, pan, side: 0.75, dly: 0.25, evVoice: "spin" });}word("line3", at(70), { gain: 1.0, pan: 0.1, side: 0.5, evVoice: "line" });// the last line of the section is the d pressing — the one whose "bird"// holds for two seconds, ringing over the shutdown's first pressword("line3-d", at(78), { gain: 0.95, pan: -0.1, side: 0.5, evVoice: "line" });// the 2022 takes keep saying bird — a chain of them, drifting rightwardword("bird-chain-h", at(74, 2), { gain: 0.6, pan: 0.5, dark: 0.1, side: 0.7, dly: 0.45, evVoice: "birdword" });// and the stretched 3.5 s "spinning" from the follow-along take, laid// under the circling like a second humword("spinning-g", at(71), { gain: 0.55, dark: 0.2, pan: -0.55, side: 0.8, dly: 0.4, evVoice: "spin" });birdcall(at(66, 2.5), 2349, { gain: 1.0, pan: 0.5 }); // D7 territorybirdcall(at(70, 2.5), 2093, { gain: 0.9, pan: -0.5, chirps: 2 }); // C7birdcall(at(74, 2.5), 2349, { gain: 1.0, pan: 0.55 });birdcall(at(77, 1.5), 2794, { gain: 0.8, pan: -0.35, chirps: 2 }); // F7word("bird", at(68, 2), { gain: 0.8, semis: 12, pan: 0.45, side: 0.7, dly: 0.5, evVoice: "birdword" });word("bird-c", at(76, 2), { gain: 0.75, pan: -0.45, side: 0.7, dly: 0.5, evVoice: "birdword" });
// ── VII · SHUTDOWN (bars 80–88) ───────────────────────────────────────// The press decelerates: each copy comes out slower and lower — a real// varispeed drift on the read head — the hum winds down a minor third,// the belt stretches, the kick thins to nothing.hum(at(80), at(88) - at(80) - 1.0, 0.85, { bend: 0.84, fadeIn: 1.0, fadeOut: 3.0 });for (let b = 80; b < 84; b++) { kick(at(b, 0), 1.0 - (b - 80) * 0.08); kick(at(b, 2), 0.9 - (b - 80) * 0.08); for (let e = 0; e < 2; e++) bass(at(b, e * 2 + 0.5), D2, 0.22, 0.7 - (b - 80) * 0.06);}kick(at(84, 0), 0.62); kick(at(85, 0), 0.5); kick(at(86, 0), 0.4);// the belt stretching its ticks apart{ let t = at(80), gap = BEAT / 2; while (t < at(87)) { tick(t, { gain: 0.30, pan: -0.4 + rnd() * 0.8, side: 0.7 }); gap *= 1.13; t += gap; }}stampLine1(80, 0.95); // one last perfect copystampLine1(82, 0.85, { slipMs: 6, driftK: 1 }); // already saggingword("line1", at(84), { gain: 0.75, wigDrift: -420, dark: 0.2, side: 0.5, evVoice: "stamp" });// the last copy never finishes: the read head grinds down an octave and// the die stops mid-word (dur truncates it; the tail fade catches it)word("line1", at(86), { gain: 0.60, wigDrift: -1050, dark: 0.4, side: 0.5, dur: 3.3, evVoice: "stamp" });press(at(80), 0.9, { base: 392 });press(at(82), 0.75, { base: 370 });press(at(84), 0.6, { base: 330, ring: 0.8 });press(at(86), 0.45, { base: 294, ring: 0.6 });hiss(at(86, 2), 0.9, { pan: 0, dur: 0.5 }); // the final exhaleclick(at(87, 1.7), { gain: 0.5, pan: 0.2 }); // the main breaker// and then, after the machine: the bird. G#2 — a note the factory's key// never contained. It was outside the whole time.word("bird-b", at(87, 2), { gain: 1.0, pan: 0, side: 0.2, evVoice: "birdword" });
if (missing.size) console.warn(" ! missing samples:", [...missing].join(", "));
// ── dub delay ─────────────────────────────────────────────────────────{ const D = Math.round(0.75 * BEAT * SR); // dotted eighth = 0.45 s const FB = 0.42; const damp = 1 - Math.exp((-TAU * 2600) / SR); const hpRc = 1 / (TAU * 180), hpA = hpRc / (hpRc + 1 / SR); const bL = new Float32Array(N + D + 1), bR = new Float32Array(N + D + 1); let dL = 0, dR = 0, hpL = 0, hpR = 0, pL = 0, pR = 0; for (let i = 0; i < N; i++) { const tapL = i >= D ? bL[i - D] : 0; const tapR = i >= D ? bR[i - D] : 0; dL += damp * (tapR - dL); dR += damp * (tapL - dR); bL[i] = dlySend[i] + dR * FB; bR[i] = dL * FB; hpL = hpA * (hpL + bL[i] - pL); pL = bL[i]; hpR = hpA * (hpR + bR[i] - pR); pR = bR[i]; musicL[i] += hpL * 0.50; musicR[i] += hpR * 0.50; }}
// ── the duck ──────────────────────────────────────────────────────────const bedEnv = buildEnv(kicks.map((t) => ({ t, depth: 0.50, atk: 0.009, rel: 0.31 })));
// ── stems ─────────────────────────────────────────────────────────────const writeStereo = (path, L, R) => { const bytes = N * 2 * 4; const buf = Buffer.alloc(44 + bytes); buf.write("RIFF", 0, "ascii"); buf.writeUInt32LE(36 + bytes, 4); buf.write("WAVE", 8, "ascii"); buf.write("fmt ", 12, "ascii"); buf.writeUInt32LE(16, 16); buf.writeUInt16LE(3, 20); buf.writeUInt16LE(2, 22); buf.writeUInt32LE(SR, 24); buf.writeUInt32LE(SR * 8, 28); buf.writeUInt16LE(8, 32); buf.writeUInt16LE(32, 34); buf.write("data", 36, "ascii"); buf.writeUInt32LE(bytes, 40); for (let i = 0; i < N; i++) { buf.writeFloatLE(L[i], 44 + i * 8); buf.writeFloatLE(R[i], 44 + i * 8 + 4); } writeFileSync(path, buf);};if (process.argv.includes("--stems")) { const dir = resolve(OUT, "stems"); mkdirSync(dir, { recursive: true }); const mk = (L, R, env, g) => { const a = new Float32Array(N), b = new Float32Array(N); for (let i = 0; i < N; i++) { a[i] = L[i] * (env ? env[i] : 1) * g; b[i] = R[i] * (env ? env[i] : 1) * g; } return [a, b]; }; const voxDuck = new Float32Array(N), machDuck = new Float32Array(N); for (let i = 0; i < N; i++) { voxDuck[i] = Math.pow(bedEnv[i], 0.25); machDuck[i] = Math.pow(bedEnv[i], 0.5); } writeStereo(resolve(dir, "v2-vox.wav"), ...mk(voxL, voxR, voxDuck, VOXG)); writeStereo(resolve(dir, "v2-music.wav"), ...mk(musicL, musicR, bedEnv, 1)); writeStereo(resolve(dir, "v2-drums.wav"), ...mk(drumsL, drumsR, null, 1)); writeStereo(resolve(dir, "v2-mach.wav"), ...mk(machL, machR, machDuck, MACHG)); console.log(` stems → ${dir}`);}
// ── Special Sign side return ──────────────────────────────────────────const sideOut = new Float32Array(N);{ const hpRc = 1 / (TAU * 80), hpA = hpRc / (hpRc + 1 / SR); const lpK = 1 - Math.exp((-TAU * 11500) / SR); let hp = 0, lp = 0, prev = 0, send = 0.9; for (let i = 0; i < N; i++) { const be = bedEnv[i]; const s = sideB[i] * be + sideV[i] * Math.pow(be, 0.25) + sideM[i] * Math.pow(be, 0.5) * MACHG; hp = hpA * (hp + s - prev); prev = s; lp += lpK * (hp - lp); const bar = (i / SR) / BAR; const target = bar < S.stamp[0] ? 0.88 : bar < S.spec[0] ? 0.60 : bar < S.drift[0] ? 0.70 : bar < S.breakfree[0] ? 0.78 : bar < S.spinning[0] ? 0.55 : bar < S.shutdown[0] ? 0.92 : 0.70; send += 0.00004 * (target - send); sideOut[i] = lp * send; }}
// ── sum ───────────────────────────────────────────────────────────────// Clean: duck, fade, measure, ONE linear trim. No master tanh anywhere.let peak = 0;const L = new Float32Array(N), R = new Float32Array(N);for (let i = 0; i < N; i++) { const be = bedEnv[i]; const dv = Math.pow(be, 0.25); const dm = Math.pow(be, 0.5); const fadeIn = Math.min(1, i / (0.014 * SR)); const fadeOut = Math.min(1, (N - 1 - i) / (1.4 * SR)); const fade = Math.max(0, Math.min(fadeIn, fadeOut)); const l = (musicL[i] * be + voxL[i] * dv * VOXG + machL[i] * dm * MACHG + drumsL[i] + sideOut[i]) * fade; const r = (musicR[i] * be + voxR[i] * dv * VOXG + machR[i] * dm * MACHG + drumsR[i] - sideOut[i]) * fade; L[i] = l; R[i] = r; if (Math.abs(l) > peak) peak = Math.abs(l); if (Math.abs(r) > peak) peak = Math.abs(r);}const norm = peak > 1e-9 ? 0.92 / peak : 1;for (let i = 0; i < N; i++) { L[i] *= norm; R[i] *= norm; }console.error(`# pre-master peak ${peak.toFixed(6)} · linear trim ${norm.toFixed(3)}`);
const outWav = resolve(OUT, "factory-remix-v2-full.wav");writeStereo(outWav, L, R);
EVENTS.sort((a, b) => a.t - b.t || (a.bus < b.bus ? -1 : 1));const voiceCounts = {};for (const e of EVENTS) voiceCounts[e.voice] = (voiceCounts[e.voice] ?? 0) + 1;console.log(` ${EVENTS.length} events · ${Object.entries(voiceCounts).map(([k, v]) => `${k} ${v}`).join(" · ")}`);writeFileSync(resolve(OUT, "factory-remix-v2.events.json"), JSON.stringify({ schema: "aesthetic.computer/pop-events/v1", track: "whistlegraph factory --- remix (v2, the pressed voice)", renderer: "pop/factory/bin/render2.mjs", bpm: BPM, key: "D minor", narrative: { logline: "A cookie cutter's whole job is identical copies — so the sampler stamps the " + "phrase out identically until the poem's middle line comes true and the copies " + "drift out of spec.", premise: "The source take's own count-in switches the machine on; its own last word — " + "the long low bird from the second pressing, on a note outside the factory's key — " + "switches it off.", turn: "2:14 — BREAK FREE. Press stops, kick out, belt slipping: the middle line of the " + "poem alone on the factory floor, then the same words from the other pressing.", acts: Object.fromEntries(Object.entries(S).map(([k, [a, b]]) => [k, { act: ACTS[k], bars: [a, b], seconds: [+at(a).toFixed(2), +at(b).toFixed(2)], }])), }, measured: { tempo: "median syllable IOI 0.299 s = eighths at 100.3 BPM; beat_track 101.4/100.4 " + "on the two 2021-02 takes; each poem line spans ~2 bars at 100", key: "chant pitch set D·F·G·A·Bb·C rooted on D3 (~147 Hz); 'bird' lands on Bb2; " + "the 02-13 take's long bird is G#2 — outside the key, kept as the last word", }, events: EVENTS,}, null, 1));console.log(`✓ ${outWav}`);