CoProducer Measure a mix
A gold-framed peak meter on black marble, a cyan waveform with space above it beside a magenta waveform pressed flat against a gold ceiling line

Delivery · 15 October 2026 · 8 min

How much headroom to leave for mastering (and why −6 dB isn’t the point)

NoDAW Labs · Lacquer Vol. 05

Leave enough headroom that nothing clips, and send it with no limiter or clipper on the mix bus. Peaks somewhere around −6 to −3 dBFS in a 24-bit or 32-bit float file is a polite convention, not a rule: in 24-bit there’s no audible cost to leaving more, but a clipped fixed-point bounce can’t be undone.

The email says “please leave −6 dB of headroom.” So you pull the master fader until the meter peaks at −6 and export. Or, more often, the mix already sounds great with the limiter on the bus, so you leave it there and just turn the output down to −6. Both feel like you followed the instructions. Only one of them actually did.

The number is the least important part of that request. What the mastering engineer is really asking for is a file they can still make decisions on.

What headroom actually protects

What headroom does not do is change the quality of a 24-bit file. The engineer is going to set the gain anyway. A 24-bit file has a theoretical noise floor around −144 dBFS, so leaving 6 dB, 10 dB or even 20 dB of headroom costs nothing you’ll ever hear. The difference between peaking at −3 and peaking at −8 is just a gain knob.

One mix, four exports

We summed a synthetic hot mix (kick, snare, bass, pad, hats) so its float peak landed at +4.5 dBFS, which is common when a mix bus is running hot and nobody’s looking. Then we exported it four ways and ran each file through the check script below. These are synthetic test signals, but the failure modes are the real ones.

ExportSample peakTrue peakIntegratedCrestClipped runsCheck says
32-bit float, overs kept+4.5 dBFS+4.7 dBTP−11.7 LUFS17.0 dB—Recoverable; say so
24-bit, overs clipped+0.0 dBFS+2.7 dBTP−11.7 LUFS12.5 dB5Clipped
24-bit, limiter on the bus (−0.3 ceiling)−0.3 dBFS+1.1 dBTP−12.4 LUFS12.6 dB0Limiter on the bus?
24-bit, turned down 10 dB first−5.5 dBFS−5.3 dBTP−21.7 LUFS17.0 dB0OK to send

Then we did what a mastering engineer would do first: turn each file down 10 dB and compare it against the clean, unclipped original.

ExportError vs the clean originalWhat that means
32-bit float, overs kept−151.9 dBNothing lost. Float only rounds.
24-bit, turned down 10 dB first−121.2 dB24-bit rounding, far below anything audible
24-bit, overs clipped−33.4 dBFlat-topped transients, permanently. At the worst moment the error is only 8 dB below the peak itself.

Three things jump out. The clipped file reads the same −11.7 LUFS as the float one, so a loudness meter won’t warn you. Its crest factor dropped from 17.0 to 12.5 dB: that’s the punch of the kick and snare, gone. And the file that peaked at −5.5 is the cleanest fixed-point export of the lot, even though it’s 10 dB “quieter.”

The limiter file needs its own sentence. It shows zero clipped runs, so a quick clip check passes it. But its crest dropped 4.4 dB, nearly as much as the clipped file, and the peaks keep landing on the same ceiling, about 9% of all 50 ms blocks, against roughly 1% in the untouched exports. That pattern is what the script flags. Also notice that a sample-peak limiter set to −0.3 still let a +1.1 dBTP true peak through. True peak is what codecs clip goes into why.

The send-to-mastering checklist

  1. Take the limiter, maximizer or clipper off the mix bus. If the mix depends on how it sounds through it, send two files: the clean premaster, and the limited version labeled as a vibe reference. A clipper on the 808 or drum bus is a mix choice and can stay; on the whole mix it’s a mastering choice.
  2. Peak somewhere around −6 to −3 dBFS. That’s the convention most engineers ask for. Anything under 0 in 24-bit is technically fine; the range just leaves room for inter-sample peaks and stray overs.
  3. Make headroom at the source, not after a bus processor. Pull the mix bus input trim, or lower the faders or a VCA. Pulling the master fader works in a float DAW only if no plugin after it clips internally.
  4. Export 24-bit or 32-bit float at the session’s sample rate. No dither unless you’re going to 16-bit, no normalize box ticked, no MP3 preview as the master. Sample rate and bit depth are delivery hygiene has the details.
  5. If you send float with overs, say so in the email. It’s recoverable, but only if the next person turns it down before anything fixed-point touches it.
  6. Leave the reverb tail and a breath of room at the end. Don’t trim it tight; the engineer will set fades.

A check you can run before you hit send

This reads any file ffmpeg can open and prints its format, sample peak, true peak, integrated loudness and crest, counts runs of three or more samples stuck at full scale (per channel, so stereo interleaving can’t hide them), and measures how often the peaks land on one ceiling, which is a hint that a limiter or clipper is on the bus. That last one is a heuristic, not a verdict. It needs Python 3, numpy and ffmpeg.

#!/usr/bin/env python3
"""premaster_check.py: is this bounce safe to send to mastering?
Usage: python3 premaster_check.py mix.wav [more.wav ...]   (needs ffmpeg + numpy)
Reports format, sample peak, true peak, integrated LUFS, clipped runs and how much
of the file sits pinned near its own ceiling (a hint that a limiter or clipper is on)."""
import sys, re, json, subprocess, numpy as np

def probe(p):
    j = json.loads(subprocess.run(["ffprobe", "-v", "error", "-select_streams", "a:0", "-show_entries",
        "stream=codec_name,sample_rate,channels,bits_per_raw_sample,bits_per_sample", "-of", "json", p],
        capture_output=True, text=True, check=True).stdout)["streams"][0]
    return j

def pcm(p):
    raw = subprocess.run(["ffmpeg", "-v", "error", "-i", p, "-f", "f32le", "-"], capture_output=True, check=True).stdout
    return np.frombuffer(raw, np.float32)

def loud(p):
    err = subprocess.run(["ffmpeg", "-nostats", "-i", p, "-af", "ebur128=peak=true", "-f", "null", "-"],
                         capture_output=True, text=True).stderr
    I = float(re.findall(r"I:\s+(-?[\d.]+) LUFS", err)[-1]); tp = float(re.findall(r"Peak:\s+(-?[\d.]+) dBFS", err)[-1])
    return I, tp

def runs(x, thr, minlen=3):
    hot = np.abs(x) >= thr
    if not hot.any(): return 0
    d = np.diff(np.concatenate([[0], hot.astype(np.int8), [0]]))
    return int(np.sum((np.where(d == -1)[0] - np.where(d == 1)[0]) >= minlen))

for p in sys.argv[1:]:
    s = probe(p); x = pcm(p); pk = np.max(np.abs(x)); I, tp = loud(p)
    fmt = s["codec_name"]; floaty = fmt.startswith("pcm_f")
    ch = int(s["channels"])
    clip_runs = 0 if floaty else sum(runs(x[c::ch], 0.9999) for c in range(ch))   # 3+ samples in a row at full scale, per channel
    blk = int(int(s["sample_rate"]) * 0.05) * ch
    bp = np.abs(x[:len(x) // blk * blk]).reshape(-1, blk).max(1)
    pinned = 100 * np.mean(bp >= pk * 10 ** (-0.5 / 20))   # 50 ms blocks peaking within 0.5 dB of the max
    crest = 20 * np.log10(pk / np.sqrt(np.mean(x.astype(np.float64) ** 2)))
    notes = []
    if floaty and pk > 1: notes.append("overs kept in float: recoverable, but say so when you send it")
    if not floaty and clip_runs: notes.append("CLIPPED: flat tops baked into a fixed-point file")
    if pinned > 5 and not clip_runs: notes.append("peaks keep landing on one ceiling: limiter or clipper on the mix bus?")
    if pk < 10 ** (-24 / 20): notes.append("very quiet: fine in 24-bit/float, just check it's the right file")
    print(f"\n{p}\n  format {fmt}, {s['sample_rate']} Hz, {s['channels']} ch")
    print(f"  sample peak {20*np.log10(pk):+.1f} dBFS | true peak {tp:+.1f} dBTP | {I:.1f} LUFS | crest {crest:.1f} dB")
    print(f"  clipped runs (3+ samples at full scale) {clip_runs} | 50 ms blocks at the ceiling {pinned:.1f}%")
    print("  " + ("; ".join(notes) if notes else "OK to send"))

On the four exports above it printed (trimmed to the last line of each):

mix_float.wav        overs kept in float: recoverable, but say so when you send it
mix_24_clipped.wav   CLIPPED: flat tops baked into a fixed-point file
mix_24_limited.wav   peaks keep landing on one ceiling: limiter or clipper on the mix bus?
mix_24_headroom.wav  OK to send

If you’d rather not run Python, CoProducer’s analyze page reads clipped samples, true peak and crest for a bounce right in the browser, without uploading it anywhere. Drop the premaster there before it goes out. A zero in the clipped samples row plus a crest that looks like your mix (not like a master) is a good sign.

Is −6 dB of headroom required for mastering?
No. It’s a convention. What matters is that nothing clips and there’s no limiter on the mix bus. In a 24-bit file, peaking anywhere from about −10 to −3 dBFS gives the engineer the same quality to work with; they set the gain either way.
Should I normalize my mix before sending it to mastering?
No. Normalizing just moves the peak to a fixed level, often right up to 0 dBFS, which removes the safety margin you were asked for. Leave the gain where your headroom puts it.
Can I send a 32-bit float file that peaks over 0 dBFS?
Yes, the overs are still in the file and turning it down recovers them exactly. Tell the engineer, though, because any fixed-point step before that turn-down will clip it, and some tools convert on import.
Should I send my mix with or without the limiter?
Without, as the main file. If the limiter is part of the sound you’ve been living with, include the limited bounce as a labeled reference so the engineer knows what you liked.

A free one-pager

The checklist we run before every release.

Loudness, true peak, mono, tails, export settings and the metadata basics, on one page. Leave your email and it opens straight away, or read it here first. No email needed for that.