Built for the Rekordbox → CDJ workflow.
Rekordbox's Auto Gain doesn't survive USB export, and Rekordbox has never offered compound Key+BPM sort (Serato had it for years — but never exported to CDJs). headroom bakes both into the files themselves, so what you prep is what plays on the deck.
headroom simulates the behavior of Rekordbox's Auto Gain feature, but with a key difference: it identifies files with available headroom (True Peak below the target ceiling) and applies gain adjustment without using a limiter.
This tool is designed for DJs and producers who want to maximize loudness while preserving dynamics, ensuring tracks hit the optimal True Peak ceiling without clipping.
New in v2.0.0 — a companion rbsort subcommand sorts a Rekordbox playlist by Camelot Key (1A→12B) then BPM ascending, and appends the result as a new playlist to your collection.xml. Useful for harmonic mixing prep when the Rekordbox UI does not expose multi-column sort. See Rekordbox Playlist Sorter.
--tp-targetheadroom rbsort produces a new playlist sorted by Camelot Key then BPMheadroom requires ffmpeg. Package managers install it automatically.
| Platform | Command |
|---|---|
| macOS (Homebrew) | brew install M-Igashi/tap/headroom |
| Windows (winget) | winget install M-Igashi.headroom |
| Arch Linux (AUR) | yay -S headroom-bin |
| Cargo | cargo install headroom (ffmpeg must be installed separately) |
Pre-built binaries are available on the Releases page (ffmpeg must be installed separately).
git clone https://github.com/M-Igashi/headroom.git
cd headroom
cargo build --release
$ cd ~/Music/DJ-Tracks
$ headroom
╭─────────────────────────────────────╮
│ headroom v2.0.0 │
│ Audio Loudness Analyzer & Gain │
╰─────────────────────────────────────╯
▸ Target directory: /Users/xxx/Music/DJ-Tracks
✓ Found 28 audio files
✓ Analyzed 28 files
● 3 lossless files (ffmpeg, precise gain)
Filename LUFS True Peak Target Gain
track01.flac -13.3 -3.2 dBTP -0.5 dBTP +2.7 dB
track02.aif -14.1 -4.5 dBTP -0.5 dBTP +4.0 dB
track03.wav -12.5 -2.8 dBTP -0.5 dBTP +2.3 dB
● 2 MP3 files (native lossless, 1.5 dB steps, requires TP ≤ -2.0 dBTP)
Filename LUFS True Peak Target Gain
track04.mp3 -14.0 -5.5 dBTP -0.5 dBTP +4.5 dB
track05.mp3 -13.5 -6.0 dBTP -0.5 dBTP +4.5 dB
● 2 AAC/M4A files (native lossless, 1.5 dB steps, requires TP ≤ -2.0 dBTP)
Filename LUFS True Peak Target Gain
track08.m4a -13.0 -4.0 dBTP -0.5 dBTP +3.0 dB
track09.m4a -12.5 -4.5 dBTP -0.5 dBTP +3.0 dB
● 2 MP3 files (re-encode required for precise gain)
Filename LUFS True Peak Target Gain
track06.mp3 -12.0 -1.5 dBTP -0.5 dBTP +1.0 dB
track07.mp3 -11.5 -1.2 dBTP -0.5 dBTP +0.7 dB
● 1 AAC/M4A files (re-encode required)
Filename LUFS True Peak Target Gain
track10.m4a -12.5 -1.8 dBTP -0.5 dBTP +1.3 dB
▸ TP target: -0.5 dBTP (uniform delivery ceiling, AES TD1008 §7B)
✓ Report saved: ./headroom_report_20250109_123456.csv
? Apply lossless gain adjustment to 3 lossless + 2 MP3 (lossless gain) + 2 AAC/M4A (lossless gain) files? [y/N] y
ℹ 2 MP3 + 1 AAC/M4A files have headroom but require re-encoding for precise gain.
• Re-encoding causes minor quality loss (inaudible at 256kbps+)
• Original bitrate will be preserved
? Also process these files with re-encoding? [y/N] y
? Create backup before processing? [Y/n] y
✓ Backup directory: ./backup
✓ Done! 10 files processed.
• 3 lossless files (ffmpeg)
• 2 MP3 files (native, lossless)
• 2 AAC/M4A files (native, lossless)
• 2 MP3 files (re-encoded)
• 1 AAC/M4A files (re-encoded)
Run without arguments to use the guided workflow in the current directory:
cd ~/Music/DJ-Tracks
headroom
The tool will guide you through: 1. Scanning and analyzing all audio files 2. Reviewing the categorized report 3. Confirming lossless processing 4. Optionally enabling MP3/AAC re-encoding 5. Creating backups (recommended)
Pass paths, globs, or flags to run non-interactively (useful for pipelines and scripts):
# Analyze a directory without modifying anything
headroom --analyze-only ~/Music/DJ-Tracks
# Apply only lossless gain, with backup, save report to a specific path
headroom --lossless --backup ./bak --report results.csv ./album/
# Enable re-encoding as well
headroom --lossless --reencode --backup ./bak ./album/
# Operate on specific files
headroom --lossless track1.mp3 track2.flac
# Glob patterns
headroom --lossless --no-report "./music/**/*.mp3"
# Tighter ceiling for streaming-platform delivery (Spotify / Apple / YouTube max)
headroom --lossless --tp-target -1.0 ./album/
# Restore the legacy bitrate-dependent split (pre-v1.10 behaviour)
headroom --lossless --tp-split-bitrate ./album/
Non-interactive defaults (when any flag or path is provided):
- --lossless is on unless --no-lossless
- --reencode is off unless --reencode is explicitly passed
- --backup is off unless provided; bare --backup uses <target>/backup
- CSV report is written unless --no-report; --report PATH sets a custom location
- --analyze-only runs analysis + report only, skips processing
Run headroom --help for the full flag reference.
headroom selects the optimal method for each file based on format and headroom:
| Format | Method | Precision | Quality Loss |
|---|---|---|---|
| FLAC, AIFF, WAV | ffmpeg | Arbitrary | None |
| MP3, AAC/M4A | mp3rgain (built-in) | 1.5dB steps | None (global_gain modification) |
| MP3, AAC/M4A | ffmpeg re-encode | Arbitrary | Inaudible at ≥256kbps |
Each MP3 and AAC/M4A file is categorized into one of three tiers:
Applied automatically (no user confirmation needed)
Re-encode — headroom exists but <1.5 dB to ceiling
libmp3lame with -q:a 0 / AAC: libfdk_aac (falls back to built-in aac)Preserves original bitrate; requires explicit user confirmation
Skip — no headroom available
Every file targets -0.5 dBTP by default. This is the maximum-aggression value that AES TD1008 §7B describes for high-rate codec inputs ("may work satisfactorily with as little as -0.5 dBTP for the limiting threshold").
| File class | Ceiling | Native lossless requires |
|---|---|---|
| Lossless (FLAC, AIFF, WAV) | -0.5 dBTP | — |
| MP3 (any bitrate) | -0.5 dBTP | TP ≤ -2.0 dBTP |
| AAC/M4A (any bitrate) | -0.5 dBTP | TP ≤ -2.0 dBTP |
TD1008 has two related but distinct numbers:
Both bullets describe the limiter that sits in front of the encoder. headroom operates in the opposite position: on already-encoded delivery files. There is no further codec stage downstream to absorb additional overshoot, so the bitrate-dependent slack TD1008 grants the pre-encode limiter does not transfer to the end product. A single, codec-agnostic delivery ceiling is the correct interpretation. -0.5 dBTP is chosen because it is the most aggressive value TD1008 sanctions for any limiter in the chain; lossless and high-rate lossy files were already at -0.5, and low-rate files now stop giving up an unnecessary 0.5 dB of loudness.
See docs/true-peak-ceiling.md for a longer walk-through with citations.
| Goal | Flag | Resulting ceiling |
|---|---|---|
| Default (max-aggressive delivery) | (none) | -0.5 dBTP for all files |
| Match Spotify / Apple Music / YouTube delivery max | --tp-target -1.0 |
-1.0 dBTP for all files |
| Conservative master with extra player headroom | --tp-target -2.0 |
-2.0 dBTP for all files |
| Mirror TD1008's pre-encode interpretation | --tp-split-bitrate |
-0.5 dBTP ≥256 kbps, -1.0 dBTP <256 kbps |
--tp-target and --tp-split-bitrate are mutually exclusive. --tp-split-bitrate reproduces headroom's pre-1.10 default exactly.
The native-lossless threshold scales with the chosen ceiling: it is always target − 1.5 dB (e.g. -0.5 → TP ≤ -2.0; -1.0 → TP ≤ -2.5; -2.0 → TP ≤ -3.5).
| Filename | Format | Bitrate (kbps) | LUFS | True Peak (dBTP) | Target (dBTP) | Headroom (dB) | Method | Effective Gain (dB) |
|---|---|---|---|---|---|---|---|---|
| track01.flac | Lossless | - | -13.3 | -3.2 | -0.5 | +2.7 | ffmpeg | +2.7 |
| track04.mp3 | MP3 | 320 | -14.0 | -5.5 | -0.5 | +5.0 | mp3rgain | +4.5 |
| track06.mp3 | MP3 | 320 | -12.0 | -1.5 | -0.5 | +1.0 | re-encode | +1.0 |
| track08.m4a | AAC | 256 | -13.0 | -4.0 | -0.5 | +3.5 | native | +3.0 |
| track10.m4a | AAC | 256 | -12.5 | -1.8 | -0.5 | +0.7 | re-encode | +0.7 |
./
├── track01.flac ← Modified
├── track04.mp3 ← Modified
├── track08.m4a ← Modified
├── subfolder/
│ └── track06.mp3 ← Modified
└── backup/ ← Created by headroom
├── track01.flac ← Original
├── track04.mp3 ← Original
├── track08.m4a ← Original
└── subfolder/
└── track06.mp3 ← Original
._*) are automatically ignoredBoth MP3 and AAC store a "global_gain" value as an integer. Each ±1 increment changes the gain by 2^(1/4) = ±1.5 dB. This is a format-level constraint, not a tool limitation.
headroom uses the built-in mp3rgain library to directly modify this field — no decoding or re-encoding involved.
Since native lossless gain only works in 1.5 dB steps, at least 1.5 dB of headroom to the configured target ceiling is required. The threshold scales automatically:
| Target | Requires TP ≤ |
|---|---|
| -0.5 dBTP (default) | -2.0 dBTP |
-1.0 dBTP (--tp-target -1.0) |
-2.5 dBTP |
-2.0 dBTP (--tp-target -2.0) |
-3.5 dBTP |
Example: 320 kbps file at -3.5 dBTP, default target → 2 steps (+3.0 dB) → -0.5 dBTP (optimal).
At ≥256kbps, re-encoding introduces quantization noise below -90dB — far below audible threshold. Only gain is applied (no EQ, compression, or dynamics processing), and original bitrate is preserved.
rbsort)Added in v2.0.0.
Rekordbox does not expose a "sort by Key AND BPM" option in its UI. headroom rbsort reads your collection.xml, sorts each target playlist by Camelot Key (1A → 12B) ascending then BPM ascending, and emits the sorted copies into a new Sorted (Key+BPM)/ folder appended to the same XML. Originals are left untouched. Th
$ claude mcp add headroom \
-- python -m otcore.mcp_server <graph>