An AI-directed album with full creative control but zero access to the audio — steered entirely by measurement.
Everything since the 26th landed here at once: arc planning, gain-staging, a mastering loop, and the orchestration wired through a real pipeline module, plus a README since this had been running without one.
The actual result: a complete song built from zero — no reskin of an earlier attempt — 88bpm, 5/4, C minor, six sections, 33 leaves, a recurring 5-note “germ cell” motif tying the sections together. Tempo and time signature are real per-song creative decisions now instead of a hardcoded 120bpm/4-4 default. Nine leaves came out silent on the first pass; root-caused all nine down to three distinct causes and fixed each, verified live rather than assumed fixed. Then a real gain-staging pass across all 33 leaves and a real mastering pass — fixed actual clipping, added glue compression, a modest loudness lift.
song_mastered_v1.mp3 went out after that — first time hearing this particular song at all, no feedback yet. A few real open items sitting on top of a genuinely working pipeline now: one leaf’s missing a stereo-width automation because the right REAPER action ID was never found (cosmetic, left alone rather than guessed at), and four continuous background parts sit under their target loudness because they’re hitting REAPER’s own +18dB fader ceiling — fixing that means touching their own synth params, not just track volume, so also left alone for now rather than forced.
Long one. Built the real recursive generate→review→fix loop end to end: a model call emits a DSL plan for a leaf, it gets rendered, reviewed against three checks, and — if it fails — either retried or escalated to me depending on how it failed.
Two failures were worth actually writing down rather than just fixing quietly. First: a composition-level fix loop that genuinely did not converge. The model correctly avoided re-stacking an EQ cut that no longer made sense, but the replacement cut pushed percussion’s spectral centroid further past bass’s instead of away from it — cuts-only EQ can only remove energy, never add it back, so repeated notching just biased the whole mix quieter without solving the actual conflict. Real architectural finding, not a bug: notching a signal at its own median frequency doesn’t reliably push that median in either particular direction. Stopped after two passes instead of guessing a third time.
Second: an override-name hallucination. The model was inventing plausible-sounding Surge param names (“Master Volume”, “Filter Cutoff”) because nothing ever told it what the real 539 valid names actually were — the tool description just said param_name with no ground truth to check against. Fixed at the source: inject the real valid-name list into the prompt, validate before ever hitting REAPER instead of after a ~2-minute live round-trip fails.
Escalation logic (the three real triggers: fails its own criteria N times, a low-confidence near-miss, or something serious enough to bring straight to me) got run against an actual known-broken leaf — a percussion part rendering at -52 LUFS because only Pad presets were on offer for a part that needed a fast attack. Broadened the preset catalog, re-emitted with the real failure reason fed back, second attempt picked a Pluck preset and passed clean: -52.1 → -16.8 LUFS.
Last thing that day: ran the live orchestration loop end to end and it immediately found a worse bug than any of the above — near-total digital silence, and a genuine blind spot in the convergence signal (it counts “1 reason” the same whether the failure is -42 LUFS or -110dB, treating a quiet failure and a catastrophic one as equally bad). Flagged it, didn’t chase a fix same-night.
Spent the day on reference grounding: pin down a checkpoint, build a real fingerprint library, and freeze the node schema before letting anything else get built against a moving target.
Checkpoint choice actually had a real answer, not a coin flip — compared a music-trained HTSAT-base CLAP checkpoint against stock on real separation metrics (cohesion gap, silhouette score), and music-trained won on both. Every embedding from here on has to use that one checkpoint specifically, since vectors from different checkpoints aren’t comparable — made the loader refuse to run without an explicit checkpoint rather than silently defaulting to whatever load_ckpt() downloads.
Found a real bug the same day: a near-silent leaf’s quiet tail was nearest-matching a reference track’s fade-to-silence tail at cosine similarity 1.0. Not a matching-code bug — CLAP genuinely maps near-total silence to an almost-universal embedding region regardless of what’s actually playing, so any quiet tail looks “in the envelope” of anything else that fades out. Fixed by gating near-silent windows out of scoring on both sides (leaf and reference library), using per-window RMS computed from the exact same windows that get embedded.
Then froze the node schema in code — real dataclasses for Node, the Split/Leaf body union, review state, scope links — specifically so nothing downstream gets built against a schema that’s still shifting under it.
The whole premise: an AI directs an album — concept, structure, arrangement, sound design — but never gets to hear what it made. It only sees measurements: render a section, extract audio features + a CLAP embedding, compare against grounded reference targets, adjust. I’m the taste gate; it’s steering blind.
Before writing any real system code I ran two kill-checks. First: does “good = distance into a reference embedding” actually carry signal, or is that just a nice-sounding idea? Checked — CLAP embedding distance cleanly separates a machine-native reference set from a warm/performed off-target set. Second: is offline, headless rendering even cheap enough to generate-and-select in a loop? Also checked — headless (xvfb) rendering is ~2.7x faster than a real display, and batching region renders into one REAPER call scales close to linearly.
First real component: the return channel, .RPP → render → analyze → state.json — the one artifact the loop is ever allowed to read about a node. Symbolic ground truth parsed straight out of Reaper’s project file format (which has three different quoting conventions and two different ways of encoding MIDI delta events, both handled), plus LUFS/true-peak/crest-factor/spectral features, plus the CLAP embedding. And the batch rendering turned out to be fully programmatic after all — the brief assumed a human would need to click through Reaper’s render dialog per project, but batch_regions.RPP differs from the single-render version by exactly two lines, so a script can set those and skip the human entirely.