Normalize audio levels

View Markdown

“Airstation” (a podcast network like Gimlet) publishes shows from dozens of creators. Each submits audio recorded on different equipment at different levels. Listeners shouldn’t have to adjust volume between episodes.

LUFS (Loudness Units Full Scale) normalization measures perceived loudness rather than peak amplitude, giving consistent results across content.

API

ittybit audio \
  -i https://airstation-app.com/submissions/show-14-ep-8.wav \
  --normalize lufs \
  --target_lufs -16 \
  --format mp3 \
  --quality high
const task = {
  input: 'https://airstation-app.com/submissions/show-14-ep-8.wav',
  kind: 'audio',
  options: {
    normalize: 'lufs',
    target_lufs: -16,
    format: 'mp3',
    quality: 'high',
  },
};

const res = await fetch('https://api.ittybit.com/jobs', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.ITTYBIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(task),
});
const data = await res.json();
import requests

task = {
    "input": "https://airstation-app.com/submissions/show-14-ep-8.wav",
    "kind": "audio",
    "options": {
        "normalize": "lufs",
        "target_lufs": -16,
        "format": "mp3",
        "quality": "high",
    },
}

res = requests.post(
    "https://api.ittybit.com/jobs",
    headers={"Authorization": f"Bearer {api_key}"},
    json=task,
)
data = res.json()
TASK='{
  "input": "https://airstation-app.com/submissions/show-14-ep-8.wav",
  "kind": "audio",
  "options": {
    "normalize": "lufs",
    "target_lufs": -16,
    "format": "mp3",
    "quality": "high"
  }
}'

curl -X POST https://api.ittybit.com/jobs \
  -H "Authorization: Bearer $ITTYBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$TASK"

CLI

ittybit audio \
  -i show-14-ep-8.wav \
  -o show-14-ep-8.mp3 \
  --normalize lufs \
  --target_lufs -16 \
  --quality high
PlatformTarget LUFSNotes
Podcasts (Apple, Spotify)-16Industry standard for spoken word
Music streaming (Spotify, Apple Music)-14Spotify normalizes to -14 by default
Broadcast (TV, radio)-23EBU R 128 standard
YouTube-14Matches their internal normalization
Audiobooks (ACX)-18 to -23ACX requires -18 to -23 LUFS range

If unsure, use -16 for speech and -14 for music. These match what major platforms normalize to internally — sending audio at the target level avoids double-normalization artifacts.

True peak limiting

Normalization can push peaks above 0 dBTP, causing clipping on some devices. Set a true peak ceiling:

ittybit audio \
  -i show-14-ep-8.wav \
  -o show-14-ep-8.mp3 \
  --normalize lufs \
  --target_lufs -16 \
  --true_peak -1

A ceiling of -1 dBTP is safe for all codecs and playback systems. Apple Podcasts recommends -1 dBTP.

Batch normalize a catalog

Normalize all episodes in a series to the same level:

for file in episodes/*.wav; do
  ittybit audio \
    -i "$file" \
    -o "normalized/$(basename "${file%.wav}.mp3")" \
    --normalize lufs \
    --target_lufs -16 \
    --true_peak -1 \
    --quality high
done

Peak vs LUFS normalization

MethodWhat it measuresDownside
PeakMaximum sample amplitudeA quiet track with one loud transient still sounds quiet
RMSAverage amplitudeDoesn’t account for human loudness perception
LUFSPerceived loudness (frequency-weighted)Slightly slower to compute

LUFS is the right choice for consistent loudness. Peak normalization is only useful when you need to maximize headroom before further processing.