Chapter 13 — Case Study: Twelve Songs, One Script
Everything in this book so far has been machinery. This chapter is the record. We’re going to walk through the actual program that produced Sign-Off — a twelve-track album of nostalgic 1983-flavored synth music, rendered start to finish by one command:
python3 generate_album_sign_off.py
# 01 Test Pattern.wav: 2.9 min
# 02 Sodium Lights.wav: 2.8 min
# ...
# 12 Sign-Off.wav: 3.4 min
# Album complete: Sign-Off
The interesting question isn’t whether that works — you’ve now seen every piece it’s built from. The question is a design question: when one song becomes twelve, what stays code and what becomes data? Get that split right and an album is twelve entries in a list. Get it wrong and it’s twelve copied-and-diverged scripts you’ll never dare touch again.
The split: an engine and twelve specs
The script has two halves. The engine — the Synth voices from
Chapter 7, the drum kits from Chapter 6, a TrackWriter class that knows
the shared song form — is written once, about four hundred lines. The
album is a list of twelve dictionaries, one per song. Here’s track 6,
complete:
dict(num=6, title="Drive Home, 1983",
root=1, mode="aeo", bpm=88, # key of C#, minor-ish mode, tempo
kit="TR-808", beat_style="pulse", # which drum machine, which beat
arp=True, lead="lead", # arpeggio on; melodic lead voice
long=True, # extended form (three B sections)
wobble=0.9, # tape-wow depth (Chapter 7)
prog_a=[0, 6, 5, 4], # verse chord progression
prog_b=[3, 6, 0, 4], # bridge progression
hook=[(0,0,2,4), (0,2,1,4), (0,3,1,5), ...], # the melody itself
hook_b=[(0,0,3,9), (0,3,1,8), ...]), # the bridge melody
Read it like a session sheet: everything a producer would say out loud about a song — key, tempo, drum machine, mood, the tune — and nothing else. Every number here is a musical decision. Everything mechanical (how a pad voices a chord, how the drum pattern breathes, how sections sequence) lives in the engine, decided once for the whole record.
That’s the design rule this chapter exists to teach: the spec should read like intent. When you’re deciding whether something belongs in the spec or the engine, ask whether two songs would ever want different values. Tempo? Obviously per-song. The rule that A-sections escalate their instrumentation? That’s the album’s identity — engine.
Writing melodies as data
The hook is the actual melody, in the (bar, beat, length, scale_degree)
tuples of Chapter 8. One decision made these humanly writable: the pitch
numbers are scale degrees, not notes — 0 is “the home note of the
key,” 4 is “the fifth step up the scale,” and the engine translates
through the song’s key and mode:
MODES = {"ion": [0, 2, 4, 5, 7, 9, 11], # major
"dor": [0, 2, 3, 5, 7, 9, 10], # minor with a hopeful lift
"aeo": [0, 2, 3, 5, 7, 8, 10]} # natural minor: melancholy
def deg(self, degree, base_oct=5):
"""Scale degree -> MIDI note, in this song's key and mode."""
iv = MODES[self.s["mode"]]
return 12 * (base_oct + degree // 7) + self.s["root"] + iv[degree % 7]
(// is whole-number division — every seven degrees climbs an octave;
% wraps within the scale.) Two consequences. Melodies are portable:
the same hook data works in any key, so hooks can be sketched without
committing to a key. And melodies are safe: a scale degree cannot be
out of key, so every experiment stays musical. The chord progressions
(prog_a=[0, 6, 5, 4]) are scale degrees too — the engine builds each
chord by stacking alternate scale steps on the degree.
Where did the hooks come from? A keyboard and a notebook, honestly — I hummed, found it on keys, and transcribed into tuples. The engine doesn’t compose. It performs. (Whether a machine should compose is Chapter 14’s question.)
The form, escalating
Chapter 8 promised the named-form template; here it is earning an album:
sections = ["intro", "A", "A2", "breath", "B", "A3",
"breath", "B2", "A4", "outro"]
if spec["long"]: # three songs stretch out
sections = ["intro", "A", "A2", "breath", "B", "A3", "breath",
"B2", "A4", "breath", "B3", "A5", "outro"]
Every song on the record walks one of these two shapes. A sections carry
the hook at escalating intensity — level 1 is pads, bass, and melody;
level 2 adds the arpeggio and percussion; level 3 doubles the melody with
the FM bell an octave up, the record’s “fullest” gesture. B sections
shift to the second progression, where the melody either rests or answers
(hook_b). breath is two bars of pads alone.
Twelve songs on two skeletons could feel like a template showing through. It doesn’t, and it’s worth being precise about why: the skeleton is shared but every surface differs — key, mode, tempo (56 to 88 BPM), drum machine (five different real boxes), beat style, wobble depth, and above all the melodies. That’s the same reason a band’s album coheres: the same people, habits, and instincts applied to twelve different songs. The form template is the band’s habits, formalized.
The specs also encode the album’s dramaturgy. Track 3 (“Rabbit Ears”)
and track 7 (“Static Bloom”) have kit=None — no drums at all, pure
interludes. Their lead="bell" swaps the melodic voice for the FM bell.
“Vertical Hold” runs wobble=2.2, the queasiest tape on the record,
exactly where an album needs its unease. The final track is the slowest,
in the saddest mode, with hook_b sinking instead of rising — a sign-off
in the data itself.
Production details that make it an album
Per-song seeds. Each song’s random generator starts at 500 +
track_number — so every song has its own reproducible performance, and
re-rendering track 9 alone never changes track 4.
Loudness matching. Chapter 12’s RMS-target normalization runs on every track with the same target. Twelve songs, one perceived level — the thing that makes a folder of WAVs feel like a record on shuffle.
One shared room. Every track sends to the same damped-studio impulse response at the same relative level. Same band, same room, all night.
The bounded batch loop. The engine over-allocates each song’s audio buffers from a generous bar estimate, builds the song, then trims to the bars actually used. Boring — and the reason adding a thirteenth track is appending a dict, rendering, and walking away.
The whole album renders in a few minutes. The writing took the time writing takes: evenings of hook-sketching, kit auditions (Chapter 6’s harness, once per song), wobble tastings, listening in the car. The pipeline never made the record fast. It made every decision cheap to revisit, which is a different and better gift.
What I’d tell you to steal
If you build one album engine, steal these four choices in order: specs that read like intent; melodies as scale degrees, not notes; a named form with an intensity dial per section; per-song seeds under one loudness target. Those four are the difference between “a script that made twelve WAVs” and “a record.”
What’s deliberately not here is a machine that writes the tunes. That absence is a choice, and the next chapter is about making it honestly — because there’s plenty of AI in this studio’s story, just not where people assume.