
Delivery · 20 October 2026 · 8 min
Sending stems to a mixer? Export them so they line up and add back up
Stems a mixer can use all start at the same point, run the same length, share the session’s sample rate, and add back up to your rough mix when every fader sits at zero. Export every track from the same start to the same end, keep send effects on their own stems, switch off the mix-bus chain, and null the stems against your bounce before you send the link.
You send the link, feel good about it, and the first reply isn’t about the mix. The vocal comes in two bars early. The 808 sounds a little sharp. Where are the adlibs? And the rough you sent has a glue the mixer can’t find on any of the stems. Day one of a paid mix gets spent on email.
Every one of those is an export setting, and you can catch all of them in about thirty seconds before the files leave your machine.
What “lined up” actually means
- Same start. Every stem begins at the same moment: bar 1, or one shared point before it if there’s a count-in or a pickup. Not at the first note on that track.
- Same length. Every stem runs to the same end, past the last reverb tail. Drop them all at zero in any DAW and they play in time with no nudging.
- Same format. One sample rate (the session’s), one bit depth, WAV or AIFF. Nothing lossy.
- They add back up. With every fader at zero, the stems summed together should be your rough mix. That’s the one people skip.
The last check is a null test. Flip the polarity of your rough bounce and play it against all the stems at once. If what’s left is silence, the stems are the mix, and the mixer starts exactly where you left off. If what’s left is loud, something on the rough never made it into the stems.
We broke a session on purpose
To put numbers on it we built a small synthetic session: eight bars at 140 BPM, 48 kHz, five stems (drums, 808, vocal, keys, adlibs). The vocal doesn’t start until bar 3 and the adlibs until bar 5. We exported it cleanly once. Then we exported it the way a rushed export goes: the vocal as region-only, the 808 at 44.1 kHz with its fader pushed 12 dB, the adlib track still muted, and a soft-clip glue on the mix bus that only the rough went through.
| What went wrong | What the mixer gets | The fix |
|---|---|---|
| Vocal exported from its first note | A 10.256 s file instead of 13.714 s. Dropped at zero, it plays 3.459 s early: two bars plus the 30 ms pickup | Export every stem from the same start bar, even when the track is silent there |
| 808 bounced at 44.1 kHz in a 48 kHz session | Either the DAW converts it on import, or it plays about 8.8% fast and 1.47 semitones sharp (48,000 ÷ 44,100) | Export everything at the session rate. Let the mixer decide on any conversion |
| 808 fader pushed 12 dB, 24-bit fixed | The peak went from −11.4 dBFS to over full scale, so 18,488 samples were flattened at 0 dBFS. That clipping is printed, and the mixer can’t undo it | Leave headroom on the stem, or export 32-bit float, which keeps overs intact |
| Adlib track muted | A file of digital silence with the right name | Unmute and unsolo everything, and check no file is empty |
| Glue on the mix bus, rough only | The stems summed back up miss the rough by a residual only 9.9 dB under the mix. That’s a different record | Bypass the bus chain for the stems and the rough, or send a second rough with it on and tell them what the chain is |
The clean export, by contrast, nulled to −120.4 dB under the mix, which is just rounding, with zero samples of lag. That’s what a stem folder should look like.
Check the folder before you send it
This reads a folder of stems and, optionally, your rough bounce. It prints each stem’s rate, format, length, peak and where its first audio starts, then lists anything a mixer would email you about. With a rough it lines every stem up at zero, adds them together and runs the null test:
#!/usr/bin/env python3
"""Check a folder of exported stems before they go to a mixer.
usage: python3 stem_check.py STEMS_DIR [MIX.wav] (pip install numpy soundfile)"""
import sys, pathlib
import numpy as np, soundfile as sf
def db(v): return 20 * np.log10(max(float(v), 1e-12))
folder = pathlib.Path(sys.argv[1]); mix_path = sys.argv[2] if len(sys.argv) > 2 else None
files = sorted(p for p in folder.iterdir() if p.suffix.lower() in (".wav", ".aif", ".aiff", ".flac"))
problems, stems = [], {}
print(f"{'stem':<11}{'rate':>7}{'format':>8}{'length s':>10}{'peak dBFS':>11}{'first audio':>13}")
for p in files:
i = sf.info(p); x, sr = sf.read(p, dtype="float64", always_2d=True); stems[p.name] = (x, sr)
peak = np.abs(x).max(); loud = np.flatnonzero(np.abs(x).max(axis=1) > 1e-4) # above -80 dBFS
first = f"{loud[0] / sr:.2f} s" if loud.size else "silent"
print(f"{p.stem:<11}{sr:>7}{i.subtype:>8}{i.frames / sr:>10.3f}{(f"{db(peak):.1f}" if peak else "-inf"):>11}{first:>13}")
if not loud.size: problems.append(f"{p.name}: silent (muted when you exported?)")
clipped = int((np.abs(x) >= 0.99999).sum())
if clipped and i.subtype != "FLOAT": problems.append(f"{p.name}: {clipped} samples at full scale, clipped on export")
rates = {sr for _, sr in stems.values()}; lengths = {len(x) for x, _ in stems.values()}
if len(rates) > 1: problems.append(f"mixed sample rates {sorted(rates)}: re-export everything at the session rate")
if len(lengths) > 1:
longest = max(len(x) / sr for x, sr in stems.values())
for name, (x, sr) in stems.items():
if longest - len(x) / sr > 0.001:
problems.append(f"{name}: {longest - len(x) / sr:.3f} s shorter than the longest stem")
if mix_path and len(rates) == 1:
mix, _ = sf.read(mix_path, dtype="float64", always_2d=True)
total = np.zeros_like(mix)
for x, sr in stems.values(): # line every stem up at zero, the way a mixer imports them
n = min(len(x), len(total)); total[:n, :x.shape[1]] += x[:n]
a, b = mix.mean(axis=1), total.mean(axis=1) # lag between the mix and the stems added back up
n = 1 << int(np.ceil(np.log2(len(a) * 2)))
xc = np.fft.irfft(np.fft.rfft(a, n) * np.conj(np.fft.rfft(b, n)), n)
lag = int(np.argmax(xc)); lag = lag - n if lag > n // 2 else lag
resid = db(np.sqrt(np.mean((mix - total) ** 2)) / np.sqrt(np.mean(mix ** 2)))
print(f"\nnull vs {pathlib.Path(mix_path).name}: residual {resid:.1f} dB under the mix, best lag {lag} samples")
if resid > -40: problems.append("stems don't add back up to the mix (bus processing, missing FX return, moved stem?)")
print("\n" + ("\n".join("- " + s for s in problems) if problems else "ok: same rate, same length, nulls against the mix"))
On the clean export:
$ python3 stem_check.py clean mix_clean.wav
stem rate format length s peak dBFS first audio
01_drums 48000 PCM_24 13.714 -1.6 0.00 s
02_808 48000 PCM_24 13.714 -11.4 0.00 s
03_vocal 48000 PCM_24 13.714 -16.5 3.46 s
04_keys 48000 PCM_24 13.714 -18.7 0.00 s
05_adlibs 48000 PCM_24 13.714 -20.5 8.15 s
null vs mix_clean.wav: residual -120.4 dB under the mix, best lag 0 samples
ok: same rate, same length, nulls against the mix
On the rushed one. The null test is skipped because the sample rates don’t match, and that alone is a reason to re-export:
$ python3 stem_check.py messy mix_glue.wav
stem rate format length s peak dBFS first audio
01_drums 48000 PCM_24 13.714 -1.6 0.00 s
02_808 44100 PCM_24 13.714 0.0 0.00 s
03_vocal 48000 PCM_24 10.256 -16.5 0.00 s
04_keys 48000 PCM_24 13.714 -18.7 0.00 s
05_adlibs 48000 PCM_24 13.714 -inf silent
- 02_808.wav: 18488 samples at full scale, clipped on export
- 05_adlibs.wav: silent (muted when you exported?)
- mixed sample rates [44100, 48000]: re-export everything at the session rate
- 03_vocal.wav: 3.459 s shorter than the longest stem
And on correct stems sent with a rough that went through the bus glue. Everything looks fine file by file. Only the null test catches it:
$ python3 stem_check.py busonly mix_glue.wav
stem rate format length s peak dBFS first audio
01_drums 48000 PCM_24 13.714 -1.6 0.00 s
02_808 48000 PCM_24 13.714 -11.4 0.00 s
03_vocal 48000 PCM_24 13.714 -16.5 3.46 s
04_keys 48000 PCM_24 13.714 -18.7 0.00 s
05_adlibs 48000 PCM_24 13.714 -20.5 8.15 s
null vs mix_glue.wav: residual -9.9 dB under the mix, best lag 0 samples
- stems don't add back up to the mix (bus processing, missing FX return, moved stem?)
Note the vocal’s “first audio” of 3.46 s on the clean export. That’s correct. A stem should carry its silence. A first-audio time of 0.00 on a part you know comes in late is the tell that it was trimmed.
Export settings that save a day
- Format. WAV at the session sample rate, 24-bit or 32-bit float. Float if any stem peaks over 0 dBFS on its own fader. Turn normalise off. No MP3s, ever, not even “just to listen”.
- Start and end. Same start for every stem. Run the export at least a bar past the last note so reverb and delay tails aren’t cut, two bars for long halls.
- Mix bus off. Bypass the bus compressor, clipper, limiter and anything “mastering” on the main out. If that chain is the sound, say so in a note and send a second rough with it on.
- Send effects get their own stems. A reverb or delay on a send becomes its own file: “Vocal FX”, “Keys verb”. Insert effects that are part of the part (the distortion on the 808, the tuning on the lead) get printed on that stem. For the lead vocal, send the printed version and the raw one if you can.
- Sidechain. If the 808 ducks off the kick and you like it, print it ducked and include the kick as its own stem, so the mixer can keep it or rebuild it.
- Names. Number them in a sensible order (
01_Kick,02_808,08_Lead_Vox) and put the BPM and key in the folder name. A short text file with the BPM, key, sample rate and which stems have printed effects answers the first three questions before they’re asked. - Send the rough from the same start. It’s what the mixer listens to first and what the null test runs against.
Mono or stereo files is mostly preference. Mono sources can go as mono files; stereo is never wrong. If you don’t know what your mixer likes, ask once and save the answer.
Lost the session and only have the two-track? StemSplit can pull vocals, drums and bass out of the bounce on your own machine, so a mixer can at least hear what they’re working with. Just tell them which stems came from a separator. Separated stems won’t null against the bounce, and they’re a rescue, not a delivery: stems diagnose mixes, they don’t replace them.
Genre notes
- Trap and drill. Kick and 808 always on separate stems, with the 808’s saturation printed. Split the vocal by role (lead, doubles, adlibs, tags), because those get very different treatment. Tags with long throws need their delay stem too.
- R&B and soul. Group stacks and harmonies into a few stems (low BGVs, high BGVs) rather than one “background vocals” file. Lead dry plus lead FX. Long tails, so give the export two bars after the last note.
- Rock and live bands. If the mixer is actually mixing the drums, send the multitracks (kick in, snare top, overheads and so on), not a stereo drum stem, and keep the count-in on every file so everything shares a start.
- House and techno. Long arrangements, so the null test earns its keep: one moved clip in a six-minute track is easy to miss by ear. Print the pumping if the sidechain is the groove, and send the kick separately.
- How do I export stems for a mixing engineer?
- Export every track or group from the same start point to the same end, past the last tail, as WAV at the session sample rate in 24-bit or 32-bit float. Bypass the mix-bus chain, give send effects their own stems, then sum the stems against your rough bounce. If they null, the mixer can rebuild your rough exactly.
- Should stems have effects on them?
- Print insert effects that are part of the sound, like 808 distortion or vocal tuning, on the stem. Put send effects such as reverb and delay on their own stems so the mixer can rebalance them. Leave the mix-bus chain off, and send a second rough with it on if it’s part of the vibe.
- What sample rate and bit depth should stems be?
- The session’s own sample rate and 24-bit or 32-bit float WAV. Don’t mix rates in one folder: in our test a 44.1 kHz 808 in a 48 kHz session would play about 8.8% fast and 1.47 semitones sharp if the DAW didn’t convert it. Use float if any stem peaks over 0 dBFS.
- How do I check that my stems line up?
- Drop them all at zero with your rough bounce, flip the bounce’s polarity and play. Silence means they line up and add back up. In our synthetic session a clean export nulled to −120 dB, while correct stems against a rough with mix-bus glue left a residual only 9.9 dB under the mix.