---
name: "audio_mixing"
description: "Mix dialogue and music for spoken-word content: EQ, de-essing, compression, music-under-dialogue balance, and loudness normalization to platform delivery targets via ffmpeg. Use when a podcast episode, voiceover, video dialogue, or demo narration needs a professional mix."
---

# Audio Mixing

## Purpose
Take raw dialogue + music and deliver a platform-ready mix: clean intelligible speech, balanced music bed, integrated loudness at the delivery target.

## Workflow
1. **Pick the target.** From `references/loudness-targets.md`, match the delivery destination (podcast, YouTube, socials). If none is stated, use podcast stereo (-16 LUFS / -1.0 dBTP).
2. **Dialogue chain, in order** (EQ before compression; loudnorm last):
   - Trim head/tail and dead air; light noise reduction only if the room needs it.
   - EQ: high-pass 80–100 Hz, cut mud/boxiness, add presence — see `references/dialogue-eq.md` for the full cheat sheet.
   - De-ess sibilance (narrow cut or dynamic de-esser at 5–8 kHz); fix harshness before any compression makes it worse.
   - Compress: start 3:1, threshold for 3–6 dB gain reduction, attack 1–5 ms, release 10–100 ms, soft knee, makeup gain = gain reduction. See `references/compression.md`.
3. **Music under dialogue.** Set the bed 18–24 dB below the dialogue's RMS (measure both with `ebur128`). Options: (a) static fader offset — simplest and transparent; (b) sidechain compression keyed from dialogue for a pump-free duck of 2–4 dB; (c) hand-drawn volume envelope — most transparent for long swells. Never duck the whole music track with the dialogue fader; pull the bed's midrange down a few dB if speech still masks.
4. **Check internal dynamics.** Measure LRA with ebur128. If LRA > ~5–6 LU, loudnorm's constant-gain mode won't fix quiet-vs-loud passages — apply `speechnorm` (or manual clip gain) *before* loudnorm.
5. **Normalize.** Run the two-pass loudnorm CLI: `bin/normalize_audio.py -i mixed.wav -o final.wav --target -16 --true-peak -1.0`. The CLI verifies the output with ebur128 and exits non-zero if |measured − target| > 1.0 LU (EBU tolerance) or true peak exceeds the ceiling.
6. **Verify by ear.** Listen on speakers (not only headphones) for pumping, breath artifacts, residual sibilance, and music masking speech. Re-run EQ/comp if anything changed the balance.

```bash
# Quick chain in ffmpeg (adjust gains to the source):
ffmpeg -i voice.wav -af "highpass=f=90, \
  deesser=i=0.5:m=0.5:f=0.5:s=o, \
  acompressor=threshold=-20dB:ratio=3:attack=3:release=100:makeup=6dB" \
  voice_mixed.wav
```

## Output Contract
- Final file: correct container/bitrate for the destination, integrated loudness within ±1 LU of target, true peak at or below the target ceiling.
- CLI exit code 0 = verified master; non-zero = delivery blocked, fix and re-run.
- A short delivery note: target platform, measured I/LRA/TP, any sections that needed manual gain rides.

## Operating Rules
1. Loudnorm is the LAST process — never normalize then EQ/compress.
2. Cut before boosting; solve boxiness/mud with subtractive EQ first.
3. Never invent loudness numbers: every delivered file is measured with ebur128, never eyeballed.
4. Compress for consistency (3–6 dB GR), not loudness — loudness comes from normalization.
5. If the source has clipping (0 dBFS samples), declip or re-record; limiters can't restore clipped peaks.
6. Mono podcasts deliver at -19 LUFS, not -16; check the table when the destination is mono-only.
