Skip to content

/docs · MixLab · Deep

Mid/Side decomposition in WebAudio

Sum and difference signals are the right way to think about stereo. Here is the WebAudio API code to do it.


The stereo channels you receive from a typical browser audio file are left and right. The stereo channels your ears actually parse are centre and sides. Mid/Side decomposition translates between the two — and once you have it, a lot of “stereo” questions become trivial.

The maths

For each sample:

mid  = (L + R) / 2
side = (L - R) / 2

To go back:

L = mid + side
R = mid - side

That’s it. Two adds and a couple of divides per sample. There’s no hidden cleverness.

In WebAudio

WebAudio has a ChannelSplitterNode and a ChannelMergerNode. With a couple of gain stages you can build M/S in-graph:

const source = ctx.createMediaElementSource(audioEl);
const splitter = ctx.createChannelSplitter(2);
const merger = ctx.createChannelMerger(2);

// L → mid+side, R → mid−side
const lGain = ctx.createGain(); lGain.gain.value = 0.5;
const rGainPos = ctx.createGain(); rGainPos.gain.value = 0.5;
const rGainNeg = ctx.createGain(); rGainNeg.gain.value = -0.5;

source.connect(splitter);

// Mid = 0.5*L + 0.5*R → channel 0 of merger
splitter.connect(lGain, 0);
splitter.connect(rGainPos, 1);
lGain.connect(merger, 0, 0);
rGainPos.connect(merger, 0, 0);

// Side = 0.5*L + (-0.5)*R → channel 1 of merger
splitter.connect(lGain, 0);
splitter.connect(rGainNeg, 1);
lGain.connect(merger, 0, 1);
rGainNeg.connect(merger, 0, 1);

merger.connect(ctx.destination);

For offline analysis on a decoded AudioBuffer, the cheaper path is to do it directly on the typed arrays:

const left = buffer.getChannelData(0);
const right = buffer.getChannelData(1) ?? left;
const mid = new Float32Array(left.length);
const side = new Float32Array(left.length);
for (let i = 0; i < left.length; i++) {
  mid[i]  = (left[i] + right[i]) * 0.5;
  side[i] = (left[i] - right[i]) * 0.5;
}

This is what MixLab Analyzer does under the hood.

What you can measure once you have M/S

  • True stereo width. The ratio of side energy to mid energy is a direct measure of how much “stereo” is in the mix. RMS-based comparison is robust and fast:

    const widthRatio = rms(side) / (rms(mid) + 1e-6);
  • Mono compatibility. If rms(side) is large but Pearson correlation between L and R is strongly negative, the mix will lose content when summed to mono. Worth flagging.

  • Phase issues. A negative correlation with high side energy is the classic symptom of a flipped channel somewhere upstream.

  • Stereo dynamics processing. Treating mid and side independently — compress the mid, leave the sides alone — is the basis of mid/side mastering. You can prototype the effect chain entirely in WebAudio nodes.

Common mistakes

  1. Forgetting the 0.5 factor. Without it, you’re computing L+R and L−R, not mid and side. The energy scaling will be off by 6 dB.
  2. Assuming mono files have zero sides. A mono file ingested as stereo (L = R) will have exactly zero side energy. That’s a useful detection signal.
  3. Working in dB before recombining. Don’t. Stay linear until the very end.

When M/S is the wrong tool

Mid/Side is a sum-and-difference framing of the channels you have. It doesn’t tell you anything about the spatial perception of those channels — for that you need binaural / HRTF-aware analysis. If you’re building something that needs to reason about where a sound is in the soundstage, M/S is the wrong starting point.

For mix analysis and almost every mastering decision, M/S is the right framing. It’s also the one that surprises the most home producers when they first see it.