
Mix · 6 October 2026 · 8 min
How loud should the vocal sit? Measure it against the beat
There’s no single right vocal level, but there is a number you can check: the loudness gap between the vocal and the beat, in LU. Measure that gap on a record you trust, get your mix close to it, then fix the few bars where the vocal gets buried instead of pushing the whole vocal up.
You’ve been here. The vocal sounds perfect at 1 a.m. on the monitors. The next day in the car, the words disappear in the hook, or the voice sits on top of the beat like a sticker. You nudge the fader up a dB, then down a dB, and after an hour you can’t hear it anymore. Ears adapt fast, and vocal level is the first thing they lie to you about.
The way out is to stop asking “is this loud enough?” and start asking “how far under or over the beat is the vocal, and where does that change?” You can measure that, and it works in any genre.
Turn the vibe into a number
Loudness meters read in LUFS, and the difference between two LUFS readings is in LU, where one LU is one dB of loudness. If the vocal stem alone reads −20 LUFS and the instrumental reads −17 LUFS, the vocal sits 3 LU under the beat. That gap is the whole conversation. It doesn’t care how loud the master is, so you can compare a rough mix to a finished record without level-matching anything.
The script below takes a vocal stem and either one instrumental bounce or a pile of stems, which it sums. It runs both through ffmpeg’s EBU R128 meter, the same measurement a LUFS meter uses, and prints the whole-song gap, the gap while the singer is actually singing, and the spots where the vocal drops well below its usual level against the beat.
#!/usr/bin/env python3
"""vocalseat.py — how loud does the vocal sit against the beat, and where does it get buried?
Usage: python3 vocalseat.py vocals.wav beat.wav [more_stems.wav ...]
(one instrumental bounce, or drums/bass/other stems from your session or StemSplit)
Needs: ffmpeg on PATH. Uses ffmpeg's EBU R128 meter, so numbers match a LUFS meter."""
import re, statistics, subprocess, sys
def short_term(inputs):
"""Return (integrated LUFS, [(seconds, short-term LUFS), ...]) for one file or a summed set."""
args = ["ffmpeg", "-hide_banner", "-nostats"]
for p in inputs:
args += ["-i", p]
mix = "".join(f"[{i}:a]" for i in range(len(inputs)))
pre = f"{mix}amix=inputs={len(inputs)}:normalize=0," if len(inputs) > 1 else f"{mix}"
graph = pre + "ebur128=metadata=1,ametadata=print:key=lavfi.r128.S:file=-"
out = subprocess.run(args + ["-filter_complex", graph, "-f", "null", "-"],
capture_output=True, text=True, check=True)
times = [float(t) for t in re.findall(r"pts_time:([\d.]+)", out.stdout)]
vals = [float(v) for v in re.findall(r"lavfi\.r128\.S=(-?[\d.]+|-inf)", out.stdout)]
integ = float(re.findall(r"I:\s+(-?[\d.]+) LUFS", out.stderr)[-1])
return integ, list(zip(times, vals))
def mmss(s):
return f"{int(s // 60)}:{int(s % 60):02d}"
vox_i, vox = short_term([sys.argv[1]])
beat_i, beat = short_term(sys.argv[2:])
beat_at = {round(t, 1): v for t, v in beat}
# Only count moments where the singer is actually singing (short-term above -40 LUFS).
gaps = [(t, v - beat_at[round(t, 1)]) for t, v in vox[30:]
if v > -40 and round(t, 1) in beat_at and beat_at[round(t, 1)] > -70]
if not gaps:
sys.exit("No overlapping sung sections found.")
d = sorted(g for _, g in gaps)
print(f"vocal integrated : {vox_i:6.1f} LUFS")
print(f"beat integrated : {beat_i:6.1f} LUFS")
print(f"vocal minus beat : {vox_i - beat_i:+6.1f} LU (whole song)")
print(f"while singing : median {statistics.median(d):+.1f} LU, "
f"range {d[len(d) // 10]:+.1f} to {d[len(d) * 9 // 10]:+.1f} LU (10th-90th pct)")
# Flag spots at least 3 LU more buried than the song's own median.
floor = statistics.median(d) - 3
picked = []
for t, g in sorted(gaps, key=lambda x: x[1]):
if g > floor or len(picked) == 3:
break
if all(abs(t - p) > 8 for p, _ in picked):
picked.append((t, g))
print("buried moments :" + ("" if picked else " none more than 3 LU under the median"))
for t, g in sorted(picked):
print(f" {mmss(max(t - 3, 0))}-{mmss(t)} vocal {g:+.1f} LU vs beat")
Run it like python3 vocalseat.py vocals.wav drums.wav bass.wav other.wav. On a synthetic test, a steady vocal-like tone over a beat that doubles in level for ten seconds the way a hook gets bigger, it reports a median gap of −3.3 LU while singing and flags 0:34–0:37 at −9.3 LU. That’s exactly what “the words vanish in the hook” looks like. The vocal didn’t change. The beat did.
Get your target from a record you trust
Don’t borrow a number from a forum. Pick two or three references in the same lane as your song, split them into vocals and everything else with StemSplit (or any separator you like), and run the same script. Separation isn’t perfect, so read the result as a range, not a decimal. Then mix toward that range.
You’ll see the lanes really are different. Rap and a lot of current pop put the vocal right on top, because the words are the hook. Rock and shoegaze often tuck the voice into the guitars so the band feels like one wall. Dance toplines can sit a little under the drop and come forward in the breakdown. None of that is wrong. The point is knowing which record you’re making, then checking that you made it.
Fix the buried bars, not the whole fader
| Fix | Use it when | Watch for |
|---|---|---|
| Clip gain or automation on the vocal | A few words or one section drop under the gap you want | Ride before the compressor so it works evenly. Don’t flatten the performance. |
| Pull the beat back where it grows | The beat gets bigger in the hook and the vocal stays put | Losing hook energy. 1–2 dB on the part that just came in is usually enough. |
| Make room in the beat around 2–4 kHz | The level is right but the words still smear | A static cut dulls the beat. A dynamic EQ keyed from the vocal only dips while it’s singing. |
Ride before you compress. If you fix level by hitting the compressor harder, the loud words get squashed and the quiet ones still hide. Clip-gain the phrases first, then let a gentle compressor smooth what’s left.
The beat usually caused it. When the script flags the hook, look at what came in: a second synth, a wider pad, a busier hat pattern. Ducking that one part a couple of dB under the vocal, or carving 2–4 kHz only while the vocal’s singing, keeps the hook big without a vocal ride you can hear.
Check on a phone at low volume. Small speakers and quiet playback both lean on the midrange, which is where the voice lives, and they show you fast when the beat is crowding it. If every word still reads on a phone at low volume, the gap is right.
Settle it in the mix, then measure the bounce
The vocal gap is a mix decision, so settle it before the master. A limiter won’t fix it. At best it squeezes the gap a little, and at worst it ducks the vocal on every kick. Once the bounce is done, run it through CoProducer’s analyze table for LUFS, true peak, and clips, and A/B it against your earlier version at matched loudness. When the new one feels louder at the same LUFS, it’s often because the words finally read.
- How loud should vocals be compared to the beat?
- It depends on the genre and the record you’re chasing. Measure the vocal-to-beat gap in LU on a couple of trusted references, then match your mix to that range instead of guessing.
- Can I just turn the vocal up in the master?
- No. The master moves everything together. If the vocal gets buried in certain bars, fix those bars in the mix with clip gain or by pulling the beat back, then master.