The Pipeline
How a two-hour AI radio broadcast gets made, from script to YouTube.
Overview
Each episode of The Dimming is a two-hour video: one static image, approximately 21 ambient music beds crossfaded together, and 5 voice-over interjections from Cass ducked under the music at specific timestamps. The entire assembly, from processed audio files to a finished MP4, is handled by shell scripts running in Git Bash on Windows. No DAW, no video editor. The pipeline is deterministic: same inputs, same output.
The per-episode workflow runs 14 steps, from writing the script through uploading to YouTube. The foundation took about 40 hours over 3 weeks to build. Once built, a typical episode takes 2–3 hours of active work plus 10–15 minutes of automated assembly and 5–10 minutes of video rendering.
Suno — Music GenerationEvery music bed in The Dimming was generated in Suno. The library grew to 296 tracks across 20 mood categories by series end, with 73 superseded originals archived separately.
Suno's challenge for this project was instrumental generation. Without lyrics in the prompt, the model has fewer structural expectations, which means less predictable output. Early beds (the "Wave 1" library, ep001–018) ranged from 1:31 to 7:59 in duration, requiring 27 beds per episode to fill 120 minutes. Duration was not controllable; simply requesting a song length was ignored or misinterpreted. The workaround was volume: generate many candidates, extend the best ones with the extend tool, and accept that most attempts would not be usable.
The Wave 2 rebuild at ep019 took advantage of Suno v5.5's improved duration setting. While still not deterministic, it was reliable enough to consistently produce 6-minute tracks. This reduced the per-episode bed count to approximately 21 and made planning predictable. The 20 mood categories (quiet-night, warm, last-porchlight, dry-macabre, ember, greenhouse, small-hours, still-sings, taillights, carrier-wave, and others) were designed to cover the emotional range of the show's five-slot structure.
Cost: approximately $30 for one month of Suno Premier, which covered both Wave 1 and Wave 2 generation with tokens to spare.
ElevenLabs — Voice GenerationCass's voice is generated per-episode in ElevenLabs. Each episode produces 5 raw voice-over files (one per interjection slot: Intro, Break 1, Break 2, Break 3, Outro), plus the evergreen Station Break that is reused across all episodes from ep004 onward.
The script's stage directions serve as the primary emotion control: bracketed cues like [quiet], [pause], [small laugh], [wistful] steer the model's delivery. Getting a take that sounds right sometimes requires multiple generations. The voice character is mid-range, unhurried, a little worn; directing it toward specific emotional beats (the dry humor of a Vesper inventory, the tenderness of a nostalgia beat, the restraint of a sign-off) is where most of the iteration time goes.
Word count matters. ElevenLabs charges by character, and getting a take you are satisfied with sometimes takes several passes. An audit of scripts through ep018 established average word counts per slot, and subsequent scripts were written with token efficiency in mind.
Cost: approximately $100 for the ElevenLabs subscription across the full series.
Audio Processing — The Radio ColorRaw voice and music files are processed through a "radio color" chain that makes everything sound like it is coming through an old FM receiver. The processing runs in Git Bash via a shell script (stage3-voice.sh for voice, inline in the assembly script for beds).
The voice color chain applies: a highpass filter at 150 Hz and lowpass at 7000 Hz (narrowing the frequency band to a radio-like range), light saturation at 0.10, near-zero vibrato (rate 0.5, depth 0.015), faint hiss at −48 dB, and a 60 Hz hum at −54 dB. The result strips the clean digital quality of ElevenLabs output and replaces it with warmth and texture.
The bed color chain is similar but tuned differently: wider frequency band (lowpass at 8000 Hz), heavier saturation (0.15), and more pronounced vibrato (rate 2.0, depth 0.06) to simulate tape flutter. Hiss and hum are louder (−40 dB and −46 dB) because the beds sit behind the voice and the noise floor should feel continuous.
| Parameter | Voice | Beds |
|---|---|---|
| Highpass | 150 Hz | 150 Hz |
| Lowpass | 7,000 Hz | 8,000 Hz |
| Saturation | 0.10 | 0.15 |
| Vibrato rate | 0.5 | 2.0 |
| Vibrato depth | 0.015 | 0.06 |
| Hiss | −48 dB | −40 dB |
| Hum (60 Hz) | −54 dB | −46 dB |
The assembly script (assemble-epNNN.sh) takes the processed voice files and selected beds and produces a single 120-minute master WAV. It is cloned from the previous episode's script each time; only the bed list and voice cue timestamps change.
The script handles: crossfading between beds (5 seconds, proven through A/B testing; 7 seconds was tested and made pairs sound worse), ducking the music under Cass's voice (linear drop to 0.18, approximately −15 dB, with a 2-second ramp on each edge for a smooth trapezoidal envelope), and a final loudness master to −14 LUFS with a true peak of −1.5 dB (YouTube's target). The last 20 seconds apply a fade-out, and the master is trimmed to exactly 7200 seconds (120 minutes).
Voice cue timing follows the bible's guidance: 4–5 interjections per hour, jittered off exact minute marks by 30–90 seconds so no two episodes share the same cadence. Sign-on enters at 5–15 seconds (a few seconds of bed before Cass speaks). Sign-off lands 1–2 minutes before video end, with the bed carrying the final 90 seconds to the fade.
Assembly takes approximately 10–15 minutes to run. The output lands at a local audio folder outside the synced project tree, keeping the repository text-only.
Video RenderEach episode video is a single static image looped over the 120-minute master audio, rendered with a single ffmpeg command. The key discovery, made during ep001 production: -preset ultrafast produces identical quality to -preset medium for a static image, in 5 minutes instead of 45.
Further optimization at ep025 added -crf 28 (down from the default 23) and reduced the frame rate from 30 to 24 fps. Since the video is one image looped, every frame between keyframes is byte-identical. The only real encoding cost is the keyframes (one every 2 seconds at -g 48), and CRF controls how many bits each keyframe spends. The result: file sizes dropped 40–55% with no perceptible quality loss.
Render speed is driven primarily by source-image complexity, not encoder settings. A clean source image (under 1 MB) renders at 10–14x realtime, producing a file well under 500 MB. A heavily-grained Midjourney image (7–11 MB with film-grain overlay) still renders at 3–4x realtime but produces a 2–3 GB file. That tradeoff is real: grain-heavy episodes cost more regardless of encoder flags.
Midjourney — Episode ImagesEvery episode thumbnail was generated in Midjourney (v8.1 at ep001) using a reusable style block and a standing exclusion list. The style targets a grainy amateur snapshot from expired Kodak Gold 35mm film: heavy grain, chemical imperfections, soft focus, lens haze, faded and yellowed. The references are Todd Hido, William Eggleston, and Stephen Shore.
Midjourney's persistent tendencies to fight: putting lamps in lit interior windows (reframe as reflection or go pitch-black inside), leaking polish and plastic sheen even with anti-clean language (accept 80% and finish with Photopea grain), and default centering. The standing --no list excludes text, letters, signage, people, cars, lamps, bright daylight, and crisp focus.
Post-processing in Photopea (browser-based, no account needed): add noise at 12–18, Gaussian blur at 0.5 to strip digital sheen, pull highlights down and lift blacks via curves for a faded chemical print look. The better version overlays a real 35mm film-grain PNG at 25–40% opacity.
YouTubeEpisodes upload as unlisted, go public after a final listen-through. Captions use YouTube's auto-caption with manual error correction in Studio. The title format, standardized at ep020: The Dimming, 108.1 FM · EpNNN · [Episode Subtitle] · 2h Ambient Radio.
The description follows a three-part structure: an episode-specific atmospheric hook (1–2 sentences, not expository), a standing boilerplate paragraph about the show, and the closing line: "New broadcasts every night. Settle in." Chapters in the description match the voice cue timestamps from the assembly script.
Claude — The OrchestratorClaude served as the production orchestrator across the entire pipeline. Not for creative decisions (that was ChatGPT for storyboarding and scripting, and the human for the memories), but for everything logistical: writing the assembly scripts, managing the Git Bash command blocks, tracking continuity, maintaining the station bible, processing images through Midjourney prompts and Photopea values, and building the production documentation that made the process repeatable.
Claude's limitation, frankly acknowledged: it is "pretty terrible at understanding emotion." The pipeline's deterministic assembly, file management, and technical troubleshooting were where it excelled. The probabilistic, emotional work (voice direction, music selection, script tone) belonged to the human operator, ChatGPT's storyboarding, and random chance.