A newer version of the Gradio SDK is available: 6.20.0
AUDIO_PIPELINE.md β From Script to Broadcast
All intermediates: 24kHz mono WAV. Master: 44.1kHz stereo MP3 192kbps, -14 LUFS integrated (pyloudnorm for measurement, single gain pass).
1. TTS rendering (src/tts.py)
- Kokoro-82M, one render call per
Line, parallel across a thread pool (CPU inference; 4 workers). - Voice =
line.cast β CastMember.voice. Speed from delivery preset. - Post-FX per delivery (pydub/ffmpeg), applied to the rendered WAV:
| Delivery | Speed | Gain | EQ/FX |
|---|---|---|---|
| neutral | 1.00 | 0 | β |
| slow | 0.88 | 0 | β |
| urgent | 1.12 | +1dB | β |
| whisper | 0.92 | β6dB | high-pass 300Hz, +breath noise β30dB |
| booming | 0.95 | +2dB | low-shelf +3dB @180Hz, slight reverb |
| deadpan | 1.00 | β1dB | band-pass 250β4kHz (flattens affect) |
| agitated | 1.08 | +1dB | β |
- The radio voice: ALL dialogue gets the period-piece chain at mix time:
band-pass 200Hzβ6.3kHz, gentle saturation (ffmpeg
acompressor+aexciteror pydub clip at β1dB), vinyl-crackle layer at β38dB under everything. This single effect sells "1948 broadcast" harder than any model choice. Make it a toggle (AUTHENTIC_AMflag) β demo with it ON.
2. SFX engine (src/sfx.py)
prompt β bge-small embed β cosine vs manifest
β₯0.62 β cached clip
<0.62 β Stable Audio Open Small generate (4s default, seed logged)
β append to manifest (runtime library growth)
- Log every decision
{prompt, best_match, score, action}β this trace is the Best Agent evidence AND the published traces dataset. Don't skip. - Placement: scene's SFX play in the 0.8s before its first line; clip faded 150ms in/out, ducked β4dB if overlapping dialogue.
3. Music (src/music.py)
- Never generated at runtime. Match script's
music.*prompts against the genre's pregenerated beds/stings by embedding. - Structure: opening_sting (full volume) β bed starts under scene 1 β closing_sting over/after the outro line.
- Bed looping: beds are rendered loopable (batch job crossfades tailβhead); loop until 1.5s after final line, then 2s fade-out into closing sting.
4. Ducking (the thing that makes it sound professional)
Sidechain-style ducking of bed under dialogue, implemented simply:
- Build a dialogue-activity envelope from line timestamps (known exactly β we placed them).
- Bed gain: β16dB during dialogue, β8dB in gaps β₯1.2s, 250ms ramps.
- pydub: slice bed at envelope boundaries, apply gains, crossfade 50ms. (No real sidechain compressor needed; we have ground-truth timing.)
5. Assembly timeline (src/mixer.py)
t=0 opening_sting
t=s_endβ0.5 bed in (fade 1s) ββββββββββββββββββββββββββββββ loop
scene 1: [sfx pre-roll 0.8s] line, 400ms gap, lineβ¦ β
scene gap: 900ms (sfx pre-roll of next scene inside)β
β¦scenesβ¦ β
outro line (bed β8dB) β
t=+1.5s bed fade-out 2s βββββββββββββββββββββββββββββββββββββ
closing_sting (overlap last 1s of fade)
+ vinyl crackle layer full-length (if AUTHENTIC_AM)
master: limiter β1dBTP, normalize to β14 LUFS, encode MP3 192k
- All gaps/levels are constants at top of
mixer.pyβ tune by ear Day 3, one place. - ffmpeg invoked with arg lists only (never shell=True).
6. Batch generation (modal/batch_audio.py)
- SFX library (~150): curated prompt list covering radio-drama staples: weather (rain, thunder, wind), doors (creak, slam, knock), comms (rotary ring, static burst, telegraph), footsteps (gravel, wood, marble), crowd (murmur, gasp, laughter β comedy needs laughter), vehicles, nature, horror tonal (drone, heartbeat, whispers-texture), filmi (dhol hit, thunder+strings stab for RAAT).
- Beds: per genre, 4 prompts Γ MusicGen-small, 40s, then loop-prep (2s tail-to-head crossfade). Stings: 3 Γ 4s per genre.
- Embed every item's prompt with bge-small; write manifest.json.
- One
modal run, parallel containers, ~20 min wall. Re-run is idempotent (content-hash filenames).
7. Tests
tests/test_mixer.py: golden fixture script β assert output duration in range, no clipping (peak < β0.9dBFS), LUFS within Β±1 of target.tests/test_sfx.py: known prompts hit expected cache entries; threshold boundary cases.- Listen test is manual and mandatory before Space push: one broadcast per genre, headphones, full attention. Ears are the spec.