CoProducer Measure a mix
A brass fader cap pushed up on a gold mixing console on black marble, a glowing gold waveform floating above it and almost touching a thin cyan line of light across the top of the frame, studio monitors with soft magenta light behind

Delivery · 27 October 2026 · 8 min

Should you normalize on export? Turn it off before mastering

NoDAW Labs · Lacquer Vol. 06

Turn normalize off when you export a mix for mastering. The normalize option rescales the finished bounce, usually so the loudest sample hits 0 dBFS or the file lands on a loudness target. That throws away the headroom you left, can push the true peak over full scale, and gives every version of the song its own gain, so the main mix, the instrumental and the stems stop matching.

You did everything right. Mix bus clean, no limiter, peaks sitting around −6 dBFS because that’s what the mastering engineer asked for. You bounce, open the file, and the waveform is slammed against the top of the window. Peak: 0.0.

Nothing in the mix changed. Somewhere in the export window there’s a checkbox that says normalize, and it’s ticked. Maybe you ticked it once for a demo, and the DAW remembered. Maybe it was on in a preset. Either way, it quietly undid the one thing you were asked to do.

What the normalize box actually does

Normalize waits until the render is finished, measures it, and applies one gain change to the whole file. Peak normalize moves the loudest sample to a target, usually 0 dBFS or a hair under. Some DAWs also offer a loudness version that moves the whole file to a LUFS target. It isn’t compression and it doesn’t change the shape of anything. It’s a fader move you didn’t make, decided by whichever moment of the song happened to be loudest.

That sounds harmless, and on one file sometimes it is. The trouble comes from three places: the headroom is gone, the peaks between the samples can end up over full scale, and every file you bounce gets a different fader move.

It’s also not the same thing as streaming normalization. Platforms turn songs up or down at playback and leave your file alone, which is covered in why streaming platforms turn loud masters down. Export normalize changes the file itself.

We bounced one song three ways

To see it in numbers, we built a four-bar song at 120 BPM in make_versions.py: kick, snare, hats, a bass and chords, plus a vocal that comes in on bar 2. It renders a main mix, an instrumental with the vocal muted, and an a cappella, all through the same mix-bus gain, with the main mix peaking at −6 dBFS. Then it runs the main mix and the instrumental through both kinds of normalize:

$ python3 make_versions.py
main         peak-normalize gain  +6.00 dB   -14 LUFS gain  +6.42 dB (1 sample past full scale, clipped in 24-bit)
instrumental peak-normalize gain  +6.71 dB   -14 LUFS gain  +9.03 dB (82 samples past full scale, clipped in 24-bit)

Here’s the script that measures them: sample peak, true peak by 4x oversampling, and integrated loudness with a BS.1770 meter. It also does a null test, which we’ll get to:

#!/usr/bin/env python3
"""Peak, true peak and loudness for one or more bounces, plus an optional null test.
  python3 export_check.py a.wav b.wav ...           sample peak, 4x true peak, integrated LUFS
  python3 export_check.py --null a.wav b.wav 0 2    residual of a - b between 0 and 2 seconds
True peak is estimated by 4x oversampling (polyphase FIR), as in ITU-R BS.1770; LUFS is BS.1770
K-weighting with the -70 LUFS absolute and -10 LU relative gates."""
import sys, numpy as np, soundfile as sf
from scipy.signal import lfilter, resample_poly

db = lambda v: 20 * np.log10(max(v, 1e-12))

def k_weight(x, sr):                                     # BS.1770 pre-filter + RLB high-pass at any rate
    K = np.tan(np.pi * 1681.974450955532 / sr); Vh = 10 ** (3.99984385397 / 20); Vb = Vh ** 0.4996667741545416
    Q = 0.7071752369554196; a0 = 1 + K / Q + K * K
    x = lfilter([(Vh + Vb * K / Q + K * K) / a0, 2 * (K * K - Vh) / a0, (Vh - Vb * K / Q + K * K) / a0],
                [1, 2 * (K * K - 1) / a0, (1 - K / Q + K * K) / a0], x, axis=0)
    K = np.tan(np.pi * 38.13547087602444 / sr); Q = 0.5003270373238773; a0 = 1 + K / Q + K * K
    return lfilter([1, -2, 1], [1, 2 * (K * K - 1) / a0, (1 - K / Q + K * K) / a0], x, axis=0)

def lufs(x, sr):
    y = k_weight(x.reshape(len(x), -1), sr); blk, hop = int(0.4 * sr), int(0.1 * sr)
    z = np.array([(y[i:i + blk] ** 2).mean(axis=0).sum() for i in range(0, len(y) - blk + 1, hop)])
    lk = -0.691 + 10 * np.log10(z + 1e-20)
    z = z[lk > -70]; rel = -0.691 + 10 * np.log10(z.mean()) - 10
    return -0.691 + 10 * np.log10(z[-0.691 + 10 * np.log10(z) > rel].mean())

def true_peak(x):
    return np.abs(resample_poly(x.reshape(len(x), -1), 4, 1, axis=0)).max()

def main():
    if sys.argv[1] == "--null":
        (a, sr), (b, _) = sf.read(sys.argv[2]), sf.read(sys.argv[3])
        i, j = int(float(sys.argv[4]) * sr), int(float(sys.argv[5]) * sr)
        r = a[i:j] - b[i:j]; rms = np.sqrt((r ** 2).mean())
        print(f"null {sys.argv[2]} - {sys.argv[3]}, {sys.argv[4]}-{sys.argv[5]} s: " +
              ("digital silence - identical" if rms == 0 else f"residual RMS {db(rms):.1f} dBFS, peak {db(np.abs(r).max()):.1f} dBFS"))
    else:
        print(f"{'file':24s} {'peak':>9s} {'true peak':>10s} {'LUFS-I':>7s}  full-scale samples")
        for f in sys.argv[1:]:
            x, sr = sf.read(f)
            print(f"{f:24s} {db(np.abs(x).max()):+6.2f} dB {db(true_peak(x)):+7.2f} dB {lufs(x, sr):7.2f}  "
                  f"{int((np.abs(x) >= 1 - 2 ** -22).sum())}")

if __name__ == "__main__":
    main()
$ python3 export_check.py main.wav instrumental.wav main_norm0.wav instrumental_norm0.wav main_norm14.wav instrumental_norm14.wav
file                          peak  true peak  LUFS-I  full-scale samples
main.wav                  -6.00 dB   -5.99 dB  -20.42  0
instrumental.wav          -6.71 dB   -6.33 dB  -23.03  0
main_norm0.wav            -0.00 dB   +0.01 dB  -14.42  1
instrumental_norm0.wav    -0.00 dB   +0.38 dB  -16.32  1
main_norm14.wav           -0.00 dB   +0.25 dB  -14.00  1
instrumental_norm14.wav   +0.00 dB   +1.38 dB  -14.01  82

Look at the gains first. To reach 0 dBFS, the main mix needed +6.00 dB and the instrumental +6.71 dB. Without the vocal on top, the instrumental’s loudest moment is quieter, so normalize pushes it harder. To reach −14 LUFS the gap is bigger: +6.42 dB against +9.03 dB, 2.61 dB apart.

Now the true-peak column. Peak normalize put both files’ loudest sample at 0 dBFS, but the waveform between samples kept going: the instrumental reads +0.38 dB true peak, and the main mix sits right at full scale. The loudness version is worse, because −14 LUFS needed more gain than the headroom allowed. Those samples got clipped flat when the 24-bit file was written: one in the main mix, 82 in the instrumental, which still reads +1.38 dB true peak. A float export can carry overs without clipping, but whatever opens the file next might not, and lossy encoders are where they hurt, as in true peak is what codecs clip.

Versions that don’t line up anymore

Bar 1 has no vocal, so the main mix and the instrumental should be identical there. A null test subtracts one file from the other. If they match, what’s left is digital silence:

$ python3 export_check.py --null main.wav instrumental.wav 0 2
null main.wav - instrumental.wav, 0-2 s: digital silence - identical
$ python3 export_check.py --null main_norm0.wav instrumental_norm0.wav 0 2
null main_norm0.wav - instrumental_norm0.wav, 0-2 s: residual RMS -39.2 dBFS, peak -22.1 dBFS
$ python3 export_check.py --null main_norm14.wav instrumental_norm14.wav 0 2
null main_norm14.wav - instrumental_norm14.wav, 0-2 s: residual RMS -26.5 dBFS, peak -11.7 dBFS

Before normalizing, the intro nulls perfectly. After peak normalize, the leftover is −39.2 dBFS RMS. After loudness normalize it’s −26.5 dBFS, with peaks at −11.7. That’s the beat at two different levels, all the way through the song.

This matters more than it looks. A mastering engineer usually runs the main, the instrumental, the clean and the TV mix through the same chain at the same settings, so they come back matching. Feed that chain files at different levels and the limiter works harder on some than others. The instrumental comes back louder and flatter than the beat under the vocal, and nobody can tell why. The same goes for stems that are supposed to add back up to the mix, covered in exporting stems for a mixer, and for a clean version that’s meant to match the explicit one, as in making a clean version.

The headroom was the point

Mastering engineers ask for room below 0 dBFS so the first thing in their chain, often an EQ that adds a few dB somewhere, isn’t starting already at the ceiling. Peak normalize fills that room on purpose. How much to leave, and why the exact number matters less than you’d think, is in how much headroom to leave for mastering. The same reasoning is behind turning the mix down before you limit: gain staging is a decision, not something a checkbox does after the fact.

Export settings that keep versions honest

  1. Turn normalize off everywhere. Check the main export dialog, every saved preset, the stem or multitrack export, and bounce-in-place. Some DAWs keep a separate setting for each.
  2. One mix-bus gain for every version. Main, instrumental, a cappella, clean and stems all come from the same session with the same master fader. Mute parts. Don’t rebalance.
  3. Export 24-bit or 32-bit float at the session’s sample rate. No limiter on the bus unless the engineer asked for your print through it.
  4. Null the intro. Pull the main and the instrumental into a session, flip one’s polarity and play a section where the vocal is silent. It should disappear. If it doesn’t, something changed the gain.
  5. Read the numbers. Peak and true peak should be where you left them, not at zero.

For that last step you don’t need a script. Bounce the same mix once with normalize on and once with it off and drop both into CoProducer. It reads LUFS, true peak and clips for each file, so the normalized bounce gives itself away: true peak at or over zero, and several dB more loudness than the mix you actually left. In our test that was 6.00 dB, the whole of the headroom.

When normalize is fine

A rough mix for your phone, a reference for the car, a quick bounce for a friend: if nobody will process the file further, normalizing it is just a volume knob. The rule is narrower than “never.” Don’t normalize anything that’s headed into mastering, anything that has to match another file, or anything that’s already been through a limiter, where it will raise the ceiling and the true peak with it.

Genre notes

Should I normalize my mix before sending it to mastering?
No. Normalize moves the loudest peak to 0 dBFS or the file to a loudness target, which removes the headroom the engineer asked for and changes the gain of each version differently. Turn it off and export at the level you mixed.
Does normalizing reduce audio quality?
A gain change on its own is clean in a 24-bit or float file. The trouble is where it puts the peaks: in our test, peak-normalized files read up to +0.38 dB true peak, and loudness-normalized ones clipped in 24-bit.
Why is my instrumental louder than my main mix?
Usually because each file was normalized on its own. Without the vocal, the instrumental’s peaks are lower, so normalize turns it up more. In our test that was 0.71 dB more at peak and 2.61 dB more to reach −14 LUFS.
Is export normalize the same as Spotify normalization?
No. Streaming platforms adjust the volume at playback and leave your file untouched. Export normalize rewrites the file before anyone else hears it.

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.