diff --git a/pop/cutezen/bin/render-cutezenith.mjs b/pop/cutezen/bin/render-cutezenith.mjs new file mode 100644 index 0000000000..f6a14a9bce --- /dev/null +++ b/pop/cutezen/bin/render-cutezenith.mjs @@ -0,0 +1,220 @@ +#!/usr/bin/env node +// render-cutezenith.mjs — cutezenith · the layered peak of the cutezen +// lane: 160 BPM · 96 bars · exactly 2:24 · C major pentatonic · every +// voice family carved from its OWN noise material, summed in parallel: +// +// pink — whistle lead + counter-whistle + breath + drone (the singers) +// brown — kick, sub, bass, toms, subdrop boom (dark lows, hiss-free) +// velvet — chord pads + hums (smooth, static-free sustain) +// white — hats, clap, arp, sparkles, risers (crisp tops that WANT hiss) +// +// Same law as ever: each pair is noise + its inverted twin — flat EQ = +// bit-exact silence (--proof passes for all four materials). Short intro: +// the hook sings over bass + hats at bar 0, kick lands at bar 4. +// +// node pop/cutezen/bin/render-cutezenith.mjs → out/cutezenith.score.txt +// node pop/nullabye/c/run-c.mjs pop/cutezen/out/cutezenith.score.txt \ +// --out pop/cutezen/out/cutezenith.mp3 --master techno +import { mkdirSync, writeFileSync } from "node:fs"; +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const OUT = resolve(HERE, "..", "out"); +mkdirSync(OUT, { recursive: true }); + +const BPM = 160, BARS = 96; +const BEAT = 60 / BPM, BAR = BEAT * 4; +const DUR = BARS * BAR; // 144.000 s +const t = (bar, beat = 0) => bar * BAR + beat * BEAT; + +const N = { C2: 65.41, G2: 98, C3: 130.81, C4: 261.63, E4: 329.63, G4: 392, A4: 440, C5: 523.25, D5: 587.33, E5: 659.26, G5: 783.99, A5: 880, C6: 1046.5, D6: 1174.7, E6: 1318.5, G6: 1568, A6: 1760, C7: 2093, E7: 2637, G7: 3136 }; +const PENT = [N.C5, N.D5, N.E5, N.G5, N.A5]; + +// form — SHORT intro (4 bars, hook sings immediately over bass + hats) +const INTRO = [0, 4], GROOVE = [4, 16], HOOK = [16, 32], BREAK = [32, 38], + DROP = [38, 56], STILL = [56, 60], FINALE = [60, 88], OUTRO = [88, 96]; +const beatOn = (b) => + (b >= GROOVE[0] && b < BREAK[0]) || (b >= DROP[0] && b < STILL[0]) || + (b >= FINALE[0] && b < OUTRO[0] + 4); + +function band(events) { + const s = [...events].sort((a, b) => a.t0 - b.t0); + return s.map((e, i) => { + const cut = e.ring ? -1 : (s[i + 1] ? s[i + 1].t0 : -1); + return `${e.t0.toFixed(4)} ${e.f} ${e.fEnd ?? 0} ${e.db} ${e.q} ${e.atk} ${e.hold} ${e.rel} ${cut}`; + }); +} + +// ════ PINK — the singers ═══════════════════════════════════════════════ +const W = { db: 26, q: 110, atk: 0.02, rel: 0.12 }; +const hookA = [ + [0, N.E6, 0.28], [0.5, N.G6, 0.28], [1, N.A6, 0.28], [1.5, N.G6, 0.45], + [2.5, N.E6, 0.28], [3, N.D6, 0.28], [3.5, N.C6, 0.65], + [5, N.C6, 0.28], [5.5, N.D6, 0.28], [6, N.E6, 0.45], [7, N.G6, 0.85], +]; +const hookB = [ // the answer phrase — falls where A rises + [0, N.A6, 0.28], [0.5, N.G6, 0.28], [1, N.E6, 0.45], [2, N.D6, 0.28], + [2.5, N.E6, 0.28], [3, N.D6, 0.28], [3.5, N.C6, 0.9], + [5.5, N.G5, 0.28], [6, N.A5, 0.45], [7, N.C6, 0.85], +]; +const whistle = []; +// short intro — the hook sings at bar 0, once, naked +for (const [beat, f, hold] of hookA) whistle.push({ t0: t(INTRO[0], beat), f, hold, ...W }); +for (const start of [HOOK[0], HOOK[0] + 8, DROP[0], DROP[0] + 8, FINALE[0], FINALE[0] + 8, FINALE[0] + 16]) + for (const [beat, f, hold] of hookA) whistle.push({ t0: t(start, beat), f, hold, ...W }); +for (const start of [HOOK[0] + 4, HOOK[0] + 12, DROP[0] + 4, DROP[0] + 12, FINALE[0] + 4, FINALE[0] + 12, FINALE[0] + 20]) + for (const [beat, f, hold] of hookB) whistle.push({ t0: t(start, beat), f, hold, ...W }); +// break — one long exhale + the bend back in; still — a lone slow phrase +whistle.push({ t0: t(BREAK[0], 1), f: N.A5, hold: 4.5, db: 24, q: 110, atk: 1.2, rel: 1.8 }); +whistle.push({ t0: t(BREAK[1] - 2, 0), f: N.D5, fEnd: N.E5, hold: 2.6, db: 24, q: 110, atk: 0.8, rel: 0.8 }); +whistle.push({ t0: t(STILL[0], 1), f: N.E5, hold: 2.2, db: 23, q: 110, atk: 0.7, rel: 1.2 }); +whistle.push({ t0: t(STILL[0] + 2, 1), f: N.C5, hold: 2.6, db: 23, q: 110, atk: 0.7, rel: 1.6 }); +// outro — the hook's tail, slowing, home to C +whistle.push({ t0: t(OUTRO[0] + 2, 0), f: N.E6, hold: 0.5, ...W }); +whistle.push({ t0: t(OUTRO[0] + 2, 1.5), f: N.D6, hold: 0.6, ...W }); +whistle.push({ t0: t(OUTRO[0] + 3, 0), f: N.C6, hold: 2.8, db: 24, q: 110, atk: 0.4, rel: 2.5 }); + +// counter-whistle — an octave below, answering in the gaps (finale only +// gets thirds-ish pentatonic parallels for the lift) +const counter = []; +const answer = [[2, N.G5, 0.4], [2.5, N.A5, 0.4], [3, N.G5, 0.6], [6.5, N.E5, 0.4], [7, N.D5, 0.7]]; +for (const start of [HOOK[0] + 8, DROP[0], DROP[0] + 8]) + for (const [beat, f, hold] of answer) counter.push({ t0: t(start, beat), f, hold, db: 22, q: 110, atk: 0.03, rel: 0.15 }); +// parallel line under hookA — 4 pentatonic degrees down (stays in scale) +const PENT_LADDER = [N.C5, N.D5, N.E5, N.G5, N.A5, N.C6, N.D6, N.E6, N.G6, N.A6, N.C7]; +const pentDown = (f, steps) => { + const i = PENT_LADDER.findIndex((p) => Math.abs(p - f) < 1); + return i >= steps ? PENT_LADDER[i - steps] : f / 2; +}; +for (const start of [FINALE[0], FINALE[0] + 8, FINALE[0] + 16]) + for (const [beat, f, hold] of hookA) counter.push({ t0: t(start, beat), f: pentDown(f, 4), hold, db: 21, q: 110, atk: 0.03, rel: 0.15 }); + +const breath = [ + { t0: t(0), f: 250, fEnd: 430, db: 8, q: 1.9, atk: 2.2, hold: 1.2, rel: 2 }, + { t0: t(BREAK[0] + 1), f: 240, fEnd: 900, db: 9, q: 1.6, atk: BAR * 4, hold: 0.3, rel: 1 }, + { t0: t(STILL[0]), f: 430, fEnd: 240, db: 8, q: 1.9, atk: 1.8, hold: 1, rel: 2.6 }, // exhale into the still + { t0: t(FINALE[0] + 24), f: 240, fEnd: 700, db: 8, q: 1.6, atk: BAR * 3, hold: 0.3, rel: 1.5 }, +]; +const drone = [ + { t0: t(STILL[0]), f: N.C3, db: 20, q: 30, atk: 1.5, hold: BAR * 4 - 3, rel: 2 }, +]; + +// ════ BROWN — the lows ═════════════════════════════════════════════════ +const kick = []; +for (let b = 0; b < BARS; b++) { + if (!beatOn(b)) continue; + const db = b < GROOVE[0] + 2 ? 25 : 28; + for (let k = 0; k < 4; k++) + kick.push({ t0: t(b, k), f: 115, fEnd: 46, db, q: 4.5, atk: 0.004, hold: 0.035, rel: 0.14 }); +} +const sub = []; +for (let b = 0; b < BARS; b++) { + if (!beatOn(b)) continue; + for (let k = 0; k < 4; k++) + sub.push({ t0: t(b, k + 0.5), f: N.C2, db: 22, q: 14, atk: 0.01, hold: 0.14, rel: 0.12 }); +} +const bass = []; +for (let b = INTRO[0]; b < OUTRO[1] - 2; b++) { + if (b >= BREAK[0] + 2 && b < BREAK[1]) continue; + if (b >= STILL[0] && b < STILL[1]) continue; + for (let k = 0; k < 8; k++) { + let f = k % 2 === 0 ? N.C2 : N.C3; + if (b % 4 === 3 && k >= 6) f = N.G2; + const glide = b % 8 === 7 && k === 7; + bass.push({ t0: t(b, k * 0.5), f, fEnd: glide ? N.C3 : 0, db: 26, q: 18, atk: 0.006, hold: 0.1, rel: 0.07 }); + } +} +const toms = []; // pitch-drop fills into every section seam +for (const seam of [GROOVE[0], HOOK[0], BREAK[0], DROP[0], FINALE[0], OUTRO[0]]) { + for (let k = 0; k < 6; k++) { + const f = 220 - k * 22; + toms.push({ t0: t(seam - 1, 2 + k * (2 / 6)), f, fEnd: f * 0.55, db: 21, q: 6, atk: 0.004, hold: 0.03, rel: 0.11 }); + } +} +const boom = [ // subdrop at the two big landings + { t0: t(DROP[0]), f: 65, fEnd: 28, db: 26, q: 8, atk: 0.005, hold: 0.25, rel: 1.4, ring: true }, + { t0: t(FINALE[0]), f: 65, fEnd: 28, db: 26, q: 8, atk: 0.005, hold: 0.25, rel: 1.4, ring: true }, +]; + +// ════ VELVET — the sustain ═════════════════════════════════════════════ +const padProg = [[N.C4, N.G4], [N.A4 / 2 * 2, N.C4 * 2], [N.C4, N.A4], [N.G4 / 2 * 2, N.D5]]; +const padLow = [], padHigh = [], padFifth = []; +for (let b = GROOVE[0]; b < OUTRO[0]; b += 2) { + if (b >= STILL[0] && b < STILL[1]) continue; + const [f1, f2] = padProg[(b / 2) % 4 | 0]; + const db = b >= BREAK[0] && b < BREAK[1] ? 20 : 17; + padLow.push({ t0: t(b), f: f1, db, q: 60, atk: 0.9, hold: BAR * 2 - 1.6, rel: 0.9 }); + padHigh.push({ t0: t(b) + 0.02, f: f2, db: db - 2, q: 60, atk: 0.9, hold: BAR * 2 - 1.6, rel: 0.9 }); + if (b >= FINALE[0]) // third pad voice lifts the finale + padFifth.push({ t0: t(b) + 0.04, f: f2 * 1.5, db: db - 4, q: 60, atk: 0.9, hold: BAR * 2 - 1.6, rel: 0.9 }); +} +padLow.push({ t0: t(STILL[0]), f: N.E4, db: 18, q: 60, atk: 1.4, hold: BAR * 4 - 3, rel: 1.6 }); +padHigh.push({ t0: t(STILL[0]) + 0.02, f: N.G4, db: 16, q: 60, atk: 1.4, hold: BAR * 4 - 3, rel: 1.6 }); + +// ════ WHITE — the tops ═════════════════════════════════════════════════ +const hat = []; +for (let b = INTRO[0]; b < OUTRO[1] - 2; b++) { + if (b >= BREAK[0] + 2 && b < BREAK[1]) continue; + if (b >= STILL[0] && b < STILL[1]) continue; + const sixteenths = (b >= DROP[0] && b < STILL[0]) || b >= FINALE[0] + 8; + for (let k = 0; k < (sixteenths ? 16 : 8); k++) { + const off = sixteenths ? k / 4 : k / 2; + const accent = sixteenths ? k % 4 === 2 : k % 2 === 1; + hat.push({ t0: t(b, off), f: 8200, db: accent ? 11 : 8, q: 6, atk: 0.002, hold: 0.008, rel: accent ? 0.09 : 0.045 }); + } +} +const clap = []; +for (let b = GROOVE[0]; b < OUTRO[0]; b++) { + if (!beatOn(b)) continue; + for (const k of [1, 3]) + clap.push({ t0: t(b, k), f: 2300, db: 12, q: 2.2, atk: 0.003, hold: 0.04, rel: 0.09 }); +} +const arp = []; // glassy 16th pentatonic up-down, drops only +for (let b = DROP[0]; b < OUTRO[0]; b++) { + if (b >= STILL[0] && b < STILL[1]) continue; + if (b < FINALE[0] && b >= DROP[0] + 8 && b < DROP[0] + 12) continue; // breathe + for (let k = 0; k < 16; k++) { + const step = k < 8 ? k : 15 - k; // up then down + const f = PENT[step % 5] * (step >= 5 ? 2 : 1) * 2; // C6 register + arp.push({ t0: t(b, k / 4), f, db: 13, q: 90, atk: 0.004, hold: 0.03, rel: 0.06 }); + } +} +const sparkle = []; +for (const start of [DROP[0] + 2, DROP[0] + 6, FINALE[0] + 2, FINALE[0] + 6, FINALE[0] + 10, FINALE[0] + 14]) + for (const [beat, f] of [[0, N.C7], [1, N.G7], [2.5, N.E7]]) + sparkle.push({ t0: t(start, beat), f, db: 13, q: 90, atk: 0.01, hold: 0.06, rel: 0.4 }); +sparkle.push({ t0: t(OUTRO[1] - 3), f: N.C7, db: 12, q: 90, atk: 0.01, hold: 0.06, rel: 3, ring: true }); // last chime +const riser = [ // real white-noise risers into the landings + { t0: t(DROP[0] - 2), f: 400, fEnd: 3500, db: 11, q: 3, atk: BAR * 1.7, hold: 0.2, rel: 0.3 }, + { t0: t(FINALE[0] - 2), f: 400, fEnd: 4200, db: 11, q: 3, atk: BAR * 1.7, hold: 0.2, rel: 0.3 }, +]; + +// ════ score ════════════════════════════════════════════════════════════ +const L = [ + "sr 48000", + `dur ${DUR}`, + "detune 1.0012 0.9988", + "seed 48879 12648430", + "normpeak 0.88", + "fadein 0.004", + "fadeout 2", +]; +const push = (bandsArr) => { + for (const ev of bandsArr) { + const lines = band(ev); + if (lines.length) L.push(`band ${lines.length}`, ...lines); + } +}; +push([whistle, counter, breath, drone]); // group 0 — pink (default) +L.push("group 3735928559 195894762", "noisetype brown"); +push([kick, sub, bass, toms, boom]); +L.push("group 271828182 314159265", "noisetype velvet"); +push([padLow, padHigh, padFifth]); +L.push("group 161803398 141421356", "noisetype white"); +push([hat, clap, arp, sparkle, riser]); + +const scorePath = resolve(OUT, "cutezenith.score.txt"); +writeFileSync(scorePath, L.join("\n") + "\n"); +const n = [whistle, counter, breath, drone, kick, sub, bass, toms, boom, padLow, padHigh, padFifth, hat, clap, arp, sparkle, riser].reduce((s, a) => s + a.length, 0); +console.log(`✓ cutezenith · ${BPM} bpm · ${BARS} bars · ${DUR.toFixed(3)}s · ${n} events · 17 bands / 4 noise materials → ${scorePath}`); diff --git a/pop/nullabye/c/nullnoise b/pop/nullabye/c/nullnoise index 578b607f1a457d07fa14e76777c0a38fbf3587ac..e4cf32f4d38ec6ac2649e09fd039e64253a07516 100755 GIT binary patch delta 6378 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 HF)#oClps%4 delta 5581 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%0D#8Uw?W@5~IV7BDhwIq<3W@)HJz zDIh1lW?=}quQYj{vJ}_$fB(16V_=wa;@#v!$~GKguNN>fgq&oW%%-Am`m6pjsFnGc zS$x$d28Jn<85p*J637AuhAGSp3?jk|3=@j|p3>{iibES&sOMWsGNZsG%Gj#Z@$kVKKiz%b=+_5PKA znHeTBFfdGD*dRUe{r~^dQN*rDO?-te)*(IdDY{sI)WiqqVhvIgZzGA-gDrU?Iq@2j zAXsdMIMFnPX~ zCKHFsST-|ivx;sO(EiD^nayFYW4*PB2s&V3c+SAUz{kkI zkfQ_{ZF>XdFM#qv1s6zu3zW~s2vL6m$`^+6??CwqQ2qxfUl+<}QLcwrUX$6te;uVv%(EepK9zLHVbkpB4G@q}FIPOgwG)^%}R^ZAy4%-R=oz?Q8qZ`*H& zvo|GT*!HazDsxx8=5yivukAdat)@ then target-dB lines — loudness ride +// group — start a new parallel noise pair +// noisetype pink|white|brown|velvet — noise material for the CURRENT +// group (default pink). Each type is roughly RMS-matched to pink so +// peakDb means the same thing across groups: brown = leaky-integrated +// white (dark — carve lows from it), white = raw LCG (crisp tops), +// velvet = sparse ±1 impulses on a 16-sample grid (smooth, hiss-free +// pads). A flat chain still nulls to bit-exact zero for every type. // band then per-event lines: // t0 freq freqEnd(0=none) peakDb q atk hold rel cut(-1=none) // bands appear in chain order; events per band are time-sorted. @@ -72,6 +79,57 @@ static inline double pink_next(Pink *p) { return out; } +// ── noise generator: one of four materials per group ────────────────── +// Scales chosen so each type's RMS lands near pink's (~0.15), so a +// band's peakDb carves a similar loudness whatever it's carved from. +enum { NT_PINK = 0, NT_WHITE, NT_BROWN, NT_VELVET }; + +typedef struct { + int type; + Pink pink; // pink (also the LCG for the other types) + double brown; // leaky integrator state + int vPos; // velvet: sample index within the current grid cell + int vAt; // velvet: impulse offset within the cell + double vSign; // velvet: impulse polarity +} Gen; + +#define VELVET_CELL 16 + +static inline double lcg_next(Gen *g) { + g->pink.s = g->pink.s * 1664525u + 1013904223u; + return ((double)g->pink.s / 4294967296.0) * 2.0 - 1.0; +} + +static void gen_init(Gen *g, int type, uint32_t seed) { + memset(g, 0, sizeof *g); + g->type = type; + pink_init(&g->pink, seed); + g->vPos = VELVET_CELL; // force a fresh cell draw on first sample +} + +static inline double gen_next(Gen *g) { + switch (g->type) { + case NT_WHITE: + return lcg_next(g) * 0.26; + case NT_BROWN: + g->brown = g->brown * 0.998 + lcg_next(g) * 0.02; + return g->brown; + case NT_VELVET: { + if (g->vPos >= VELVET_CELL) { + double r = lcg_next(g); // one draw: position + sign + g->vAt = (int)((r * 0.5 + 0.5) * VELVET_CELL) % VELVET_CELL; + g->vSign = (g->pink.s & 1u) ? 0.6 : -0.6; + g->vPos = 0; + } + double v = (g->vPos == g->vAt) ? g->vSign : 0.0; + g->vPos++; + return v; + } + default: + return pink_next(&g->pink); + } +} + // envelope in dB-space: linear attack / hold / release, optional cut fade static inline double env_of(const Event *e, double time) { double dt = time - e->t0; @@ -108,6 +166,7 @@ int main(int argc, char **argv) { // first already boosts without the serial dB-addition blowup. enum { MAXG = 8 }; uint32_t gseed[MAXG][2] = { { 0xBEEF, 0xC0FFEE } }; + int gtype[MAXG] = { NT_PINK }; int nGroups = 1, curGroup = 0; double normpeak = 0.88, fadein = 0.004, fadeout = 1.6; double rideLen = 0; int rideN = 0; double *rideTarget = NULL; @@ -122,8 +181,18 @@ int main(int argc, char **argv) { else if (!strcmp(key, "group")) { if (nGroups >= MAXG) die("too many groups"); if (fscanf(f, "%u %u", &gseed[nGroups][0], &gseed[nGroups][1]) != 2) die("bad group"); + gtype[nGroups] = NT_PINK; curGroup = nGroups++; } + else if (!strcmp(key, "noisetype")) { + char nt[16]; + if (fscanf(f, "%15s", nt) != 1) die("bad noisetype"); + if (!strcmp(nt, "pink")) gtype[curGroup] = NT_PINK; + else if (!strcmp(nt, "white")) gtype[curGroup] = NT_WHITE; + else if (!strcmp(nt, "brown")) gtype[curGroup] = NT_BROWN; + else if (!strcmp(nt, "velvet")) gtype[curGroup] = NT_VELVET; + else die("unknown noisetype"); + } else if (!strcmp(key, "normpeak")) { if (fscanf(f, "%lf", &normpeak) != 1) die("bad normpeak"); } else if (!strcmp(key, "fadein")) { if (fscanf(f, "%lf", &fadein) != 1) die("bad fadein"); } else if (!strcmp(key, "fadeout")) { if (fscanf(f, "%lf", &fadeout) != 1) die("bad fadeout"); } @@ -168,8 +237,8 @@ int main(int argc, char **argv) { // ── render: per channel, per group: EQ'd copy minus the original; // group residues sum in parallel ────────────────────────────────── for (int ch = 0; ch < 2; ch++) { - Pink pinks[MAXG]; - for (int g = 0; g < nGroups; g++) pink_init(&pinks[g], gseed[g][ch]); + Gen gens[MAXG]; + for (int g = 0; g < nGroups; g++) gen_init(&gens[g], gtype[g], gseed[g][ch]); for (int bi = 0; bi < nBands; bi++) { bands[bi].cursor = 0; bands[bi].z1 = 0; bands[bi].z2 = 0; } for (long blockStart = 0; blockStart < ns; blockStart += BLOCK) { @@ -177,7 +246,7 @@ int main(int argc, char **argv) { double time = (double)blockStart / sr; memset(acc, 0, sizeof(float) * n); for (int g = 0; g < nGroups; g++) { - for (int i = 0; i < n; i++) wet[i] = dry[i] = (float)pink_next(&pinks[g]); + for (int i = 0; i < n; i++) wet[i] = dry[i] = (float)gen_next(&gens[g]); for (int bi = 0; bi < nBands; bi++) { Band *b = &bands[bi];