CoProducer Measure a mix
Two studio monitors flanking a cyan mid waveform with magenta side echoes on black marble

Stereo · 6 October 2026 · 8 min

Wide mixes fail in mono. Check mid/side before you bounce

NoDAW Labs · Lacquer Vol. 04

Stereo width that sounds expensive on headphones can cancel on a phone speaker. Mid/side balance and L/R correlation tell you whether the width is musical or just anti-phase. Measure it, fold to mono once, then bounce.

You’ve heard it. Headphones, the pad feels huge. Car stereo, fine. Then somebody plays it off a phone on a kitchen counter and the hook disappears. Not quieter overall — thinner. The part that lived in the sides never made it into one speaker.

That’s not a mystery genre thing. It’s mid and side fighting each other. Mid is what L and R share. Side is what’s different between them. Phones, club mono fills, Instagram stories, and a lot of Bluetooth speakers lean hard on the mid. If your side energy is huge and out of phase with the mid, mono isn’t a gentle fold. It’s a disappearing act.

Width isn’t the enemy. Unchecked width is.

A wide mix can be mono-safe. A narrow mix can still have a bad bar. What you want is a read on three things at once: how much side you have relative to mid, how correlated left and right are, and how much loudness you lose when you sum to mono. This script does that on a stereo bounce. It also walks the file in 400 ms windows so you can find the one thin moment that the whole-file average hides.

#!/usr/bin/env python3
"""midside_width.py — mid/side balance, stereo width, and mono-safe check.
Usage: python3 midside_width.py stereo.wav
Needs: numpy + ffmpeg on PATH.

Prints integrated mid vs side energy, a simple width ratio, Pearson
correlation (same idea as a phase meter), and how much loudness you
lose when the mix folds to mono. Wide ≠ broken; negative correlation
or a big mono LUFS drop is the red flag."""
import subprocess, sys
import numpy as np

SR = 48000
path = sys.argv[1]
raw = subprocess.run(
    ["ffmpeg", "-v", "error", "-i", path, "-ac", "2", "-ar", str(SR),
     "-f", "f32le", "-"],
    capture_output=True, check=True,
).stdout
x = np.frombuffer(raw, dtype=np.float32).reshape(-1, 2)
L, R = x[:, 0], x[:, 1]
# Mid/side (equal-power encode; factor 1/sqrt(2) keeps energy)
mid = (L + R) * (0.5 ** 0.5)
side = (L - R) * (0.5 ** 0.5)
# Energy (mean square)
e_m = float(np.mean(mid ** 2) + 1e-20)
e_s = float(np.mean(side ** 2) + 1e-20)
mid_db = 10 * np.log10(e_m)
side_db = 10 * np.log10(e_s)
# Width: side energy / mid energy (0 = mono, 1 = equal M/S, >1 = side-heavy)
width = e_s / e_m
# Pearson correlation ≈ phase-correlation style reading on the whole file
corr = float(np.corrcoef(L, R)[0, 1])
# Mono fold (L+R)/2 — how much integrated energy you keep
mono = (L + R) * 0.5
stereo_e = float(np.mean(0.5 * (L ** 2 + R ** 2)) + 1e-20)
mono_e = float(np.mean(mono ** 2) + 1e-20)
mono_drop_db = 10 * np.log10(mono_e / stereo_e)

# Short-term correlation every 400 ms to find the thinnest moments
hop = int(0.4 * SR)
wins = []
for i in range(0, len(L) - hop, hop):
    a, b = L[i:i + hop], R[i:i + hop]
    if np.std(a) < 1e-6 or np.std(b) < 1e-6:
        continue
    c = float(np.corrcoef(a, b)[0, 1])
    wins.append((i / SR, c))

print(f"file            : {path}")
print(f"duration        : {len(L) / SR:.2f} s")
print(f"mid energy      : {mid_db:+.1f} dB (rel arbitrary)")
print(f"side energy     : {side_db:+.1f} dB")
print(f"side/mid width  : {width:.3f}   (0=mono, ~0.3–0.7 typical mix, >1 side-heavy)")
print(f"L/R correlation : {corr:+.3f}   (≥0.3 usually mono-safe; <0.2 risky; <0 cancels)")
print(f"mono energy drop: {mono_drop_db:+.2f} dB vs stereo mean energy")
if wins:
    worst = min(wins, key=lambda t: t[1])
    best = max(wins, key=lambda t: t[1])
    print(f"worst 400 ms    : {int(worst[0] // 60)}:{worst[0] % 60:05.2f}  corr {worst[1]:+.3f}")
    print(f"best 400 ms     : {int(best[0] // 60)}:{best[0] % 60:05.2f}  corr {best[1]:+.3f}")
    thin = [w for w in wins if w[1] < 0.2]
    print(f"thin windows    : {len(thin)} of {len(wins)} under corr 0.2")

What the numbers looked like on synthetic tests

All of these are synthetic WAVs built on the box — labeled that way on purpose so nobody mistakes them for a real record.

A balanced “mix” with a centered kick/bass, a lightly detuned stereo pad, and hard-panned hat bursts measured side/mid width 0.30, correlation +0.54, and about −1.1 dB of mean-energy drop into mono. Integrated loudness still moved −4.8 LU stereo → mono on the ebur128 meter, mostly because the wide hats don’t survive the fold. That’s normal and musical. Three of nineteen 400 ms windows dipped under correlation 0.2 — the hat-only moments — and the worst sat at 0:03.20 with corr −0.09. The average looked fine. The thin bar was real.

A deliberately broken version — near-anti-phase pads with only a weak center kick — measured width 103, correlation −0.98, and a −20 dB energy drop into mono. Integrated loudness fell −26.6 LU stereo → mono. On a phone, that pad is gone. Pulling the side down 24 dB while leaving the mid alone brought width to 0.41, correlation to +0.42, and the mono loudness hit to about −5.8 LU. Another 6 dB of side cut (30 dB total) landed at width 0.10, corr +0.81, mono hit −3.9 LU. You don’t need mono. You need the mid to still own the song.

A near-mono control measured width 0.00, corr +1.00, and no energy drop. Safe, and also flat. The point isn’t maximum correlation. It’s “does the hook still exist when L and R become one speaker?”

How to listen for it without a script

CheckWhat you’re hearingWhat to do
Flip the master to mono for one chorusHook thins, pad vanishes, vocal doubles collapseFind which bus is wide: pads, FX, delayed ad-libs
Solo mid, then solo sideSide is louder than mid on the parts that matterTurn the widener down, or high-pass the side so low end stays mid
Correlation meter under ~0.2Phase meter hugs the middle or flips negativeShorten stereo delays, flip a polarity, collapse a Haas throw
Club / phone / Bluetooth checkBeat stays, atmosphere disappearsKeep kick, bass, lead vocal, and snare body in the mid

Low end wants to be mid. A stereo 808 or a widened kick is a classic mono trap. Keep the weight centered and let the air be wide.

Haas throws are side candy. A 10–30 ms delay on one side feels huge in headphones and cancels in mono. If you love the throw, duck it in the hook or keep a mono dry version underneath.

Doubles and ad-libs. Hard-panned stacked vocals are fine until every consonant lives only on the sides. Leave a solid lead in the mid; let the stacks decorate.

Screw and slow edits change the picture. Pitching and stretching can nudge phase relationships. Check mono on the final ScrewAI render, not only on the full-speed bounce.

Fix order that doesn’t kill the vibe

  1. Identify the bus, not the master. StemSplit the bounce if you don’t have the session — pads and FX are usually the culprits, not the kick.
  2. Cut side, don’t nuke stereo. A mid/side EQ or a width control that only turns down side above 200 Hz often fixes the mono hole without making the record feel small.
  3. Re-check correlation and a mono fold. Aim for whole-file correlation roughly above 0.3, and make sure no section you care about sits negative for long.
  4. Then master. A limiter will not repair anti-phase width. It will just make the thin parts louder and thinner.

Once the mono fold still carries the hook, run the bounce through CoProducer and glance at the phase/correlation row before upload. Width is a taste call. Mono survival is delivery hygiene.

Why does my mix sound thin on a phone?
Phones are basically mono. If too much of the hook lives in out-of-phase side information, it cancels when left and right collapse. Check mid/side balance and listen in mono.
What’s a “good” stereo width number?
There isn’t one magic width. Correlation above about 0.3 and a mono fold that still holds the hook are safer targets than chasing maximum width on a meter.

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.