Deep dive
Even loud.
Even quiet.
Just right.
Modern releases are mixed at very different loudness levels — a 2024 pop master sits roughly twice as loud as a 2002 jazz ballad. Run2Beat measures every track on import and adjusts playback so you never have to reach for the volume control between songs. This page is the long version of how that works.
The problem
For two decades the music industry mastered records louder and louder. A modern pop release typically lands at −7 to −9 LUFS integrated; a jazz album from 2002 sits at −18 to −22 LUFS. Perceptually, that’s roughly a factor of two in loudness — the difference between “in the room” and “background music”.
The phone’s volume slider can’t fix that. It’s the input to whatever your headphones do; it doesn’t touch the mastering decisions baked into each file. A single global gain in the audio engine wouldn’t help either — it would raise or lower the whole queue together and leave the relative gap between a loud pop master and a quiet jazz ballad exactly as wide. Per-track normalisation is the only solution that flattens the perceived-loudness curve.
Three different things people call “volume”
- Sample peak — the highest single sample value in the file. Useful for clipping protection, but barely correlates with what you hear.
- True peak (dBTP) — inter-sample peak after the digital-to-analogue reconstruction filter. EBU R 128 says this should stay below −1 dBTP so the audio never clips on the way out. That ceiling is what lets Run2Beat raise a quiet track safely.
- Integrated loudness (LUFS) — the hearing-weighted mean level over the whole track, gated to ignore long silences. Two tracks with identical sample peaks can still be 8 dB apart in LUFS — and the LUFS gap is what you actually hear.
LUFS (Loudness Units relative to Full Scale) is K-weighted: a filter chain from ITU-R BS.1770 that mimics the ear’s frequency response, then block mean-square measurement with gating so a long fade-out or thirty seconds of room tone doesn’t pull the average down. Every modern loudness tool — ffmpeg, foobar2000, Audacity, libebur128 — is implementing that same maths. There is essentially one right answer for “what is this file’s integrated loudness”.
Standards, reference and target
Run2Beat follows ITU-R BS.1770 for the measurement and borrows the −1 dBTP true-peak ceiling from EBU R 128 (broadcast TV’s −23 LUFS target is far quieter than streaming; we are not a broadcaster). Streaming services have converged on a listening reference around −14 LUFS:
- −14 LUFS (default) — matches Apple Music’s current stream, Spotify Normal and YouTube. The right choice for most listeners.
- −16 LUFS — quieter, more headroom on dynamic recordings (closer to older Apple Sound Check).
- −11 LUFS — louder, like Spotify’s “Loud” setting; leaves less headroom on aggressive masters.
Changing the reference never re-analyses your files. The player recomputes gain from the stored LUFS and the live setting, so switching −14 → −16 is instant.
Why not just trust iOS Sound Check?
Two hard blockers. Sound Check is off by default in iOS Settings → Music, so most people have never enabled it. And even when it is on, it only applies to tracks the Music app owns. Run2Beat plays its own files through its own audio-engine graph and would receive zero benefit. Self-managed loudness is not optional — it is the only path that reliably evening out a Run2Beat library on every device and OS configuration.
Architecture: measure once, apply at playback
Three approaches were considered:
- Analyse at import, apply at playback (chosen) — store integrated LUFS and true peak on the track; both iPhone and Apple Watch apply the same gain formula at play time. One-time cost, zero CPU in the hot path, toggleable, and the reference can change without touching the audio again.
- Analyse while playing (rejected) — roughly what Sound Check does. Needs lookahead (otherwise the first seconds of each track are unnormalised), continuous CPU, and cannot be mirrored on the Watch without a second heavy analyser or accepting phone/watch mismatch.
- Re-encode files to a normalised level (rejected) — destroys headroom, locks you into one reference forever, risks new clipping, and makes files sound permanently odd against other apps that already stream at −14 LUFS.
The chosen hybrid keeps the original files untouched. Gain is never persisted as a fixed number — only the measurement is — so Settings can always retarget.
How a track gets its loudness number
When you import a file, Run2Beat first looks for embedded tags that already tell the truth: ReplayGain 2.0 track gain / peak, or Apple’s iTunNORM Sound Check blob. If either parses cleanly, the heavy pass is skipped — the file already knows its loudness. Otherwise the audio is decoded at native sample rate and run through a BS.1770 analyser (libebur128 under the hood) on device. The result — integrated LUFS plus true peak in dBTP — is stored on the track and never needs to be measured again for ordinary playback.
Analysis runs on the original decoded audio, not on a later HE-AAC re-encode, so the numbers represent the source you handed the app. Channels are clamped to stereo and the read is bounded to roughly the first two and a half minutes: integrated loudness for consumer music converges well inside that window, and the bound keeps decoder memory safely under iOS’s background jetsam ceiling on long bulk jobs. Very short tracks and all-silent files are skipped rather than persisting an unreliable value.
The work is part of the few-seconds-per-song import cost mentioned on the home page. Everything below about wall-clock time and battery applies to the standalone Re-measure all loudness button in Settings, not to ordinary import.
How the gain is applied at playback
On every track change Run2Beat computes the gain it needs to land the track on your chosen reference, then sets a mixer-node output volume (a sample-accurate linear scalar in the existing mix pass):
gainDB = referenceLUFS − measuredLUFS
gainDB = min(gainDB, −1.0 − measuredTruePeakDBTP)
volume = 10^(gainDB / 20)
The first line raises quiet tracks and lowers loud ones. The second is the true-peak ceiling: a quiet track is never amplified so much that its inter-sample peaks would clip after D/A reconstruction. Tracks that already clip in the source (true peak above 0 dBTP) never get positive gain. If normalisation is off, or a legacy track has no measurement yet, the mixer stays at unity and the file plays at its native level.
Cost: effectively zero — a multiply during mixing that already happens. No extra DSP stage, no extra latency. The loudness stage is independent of the equalizer and crossfade; those features behave the same with normalisation on or off.
Same numbers, same loudness on Apple Watch
When a BPM list or playlist syncs to your Apple Watch, per-track loudness and true-peak values travel in the transfer metadata — two numbers, negligible against the audio payload. The Watch player applies the same gain formula; it never runs the analyser itself. The reference level rides the regular shared-settings preferences channel, so changing −14 → −16 on the phone is honoured on the wrist after the next settings push.
Because both devices compute gain = reference − measured from identical metadata, switching from headphones on the phone to AirPods paired to the watch should not need a volume bump. The phone measures the library file; the watch plays a watch-export HE-AAC variant. Those encodings keep near-identical integrated loudness in practice, so one original-file measurement serves both sides.
Settings: toggle, reference, true-peak protection
The Loudness levelling card in Settings mirrors the BPM-analysis card on purpose: expand/collapse, info sheet, toggle (on by default), reference picker, and a primary Re-measure all loudness button. An optional true-peak protection switch keeps the −1 dBTP clamp on by default — leave it on unless you know why you want it off. You can also ask for a notification when a bulk re-measure finishes.
Re-measuring the library
Use the bulk button when you want every legacy track that predates the feature to have a value, or after an analyser update. Some honest expectations:
- Takes time. Each track needs a couple of seconds of analysis. Across several hundred tracks, the full run easily exceeds an hour.
- Battery-heavy. The phone decodes every file and runs broadcast-grade DSP. Connecting a charger is recommended.
- Keeps running in the background. The run continues with the screen locked or the app backgrounded, using the same background-activity keeper as BPM re-analysis and Watch sync — a silent keep-alive that exploits the app’s audio background mode, far beyond the ~30 s grace of a bare system background task. Between tracks the loop sleeps briefly so iOS’s background CPU watchdog does not kill a sustained full-core decode marathon; that roughly halves duty cycle and stretches wall-clock time, by design.
- Replaces existing values and pushes fresh metadata to the Watch on the next sync. There is no auto-trigger on launch — libraries with thousands of tracks would otherwise spin up unprompted.
Rule of thumb: around a thousand tracks takes well over an hour and uses roughly as much battery as a couple of hours of music playback. Plan accordingly.
AirDrop and shared playlists
When someone sends you a Run2Beat playlist package, per-track loudness and true-peak measurements from the sender can travel in the manifest. Genuinely new tracks adopt those values; deduplicated hits that still lack a local measurement can be back-filled — but a value you already measured locally is never overwritten by a remote one. Older packages without the fields simply decode as “not measured yet” until you run a bulk re-measure.
What this does not do
- No dynamic-range compression. A quiet intro into a loud chorus still has that internal shape; only the overall level matches the rest of the library. That is correct behaviour for levelling.
- No album-gain mode in the first iteration — track gain is enough when albums are mastered consistently, which covers almost all personal libraries.
- No per-track manual override yet (the moral equivalent of manual BPM). Easy to add later; no demand yet.
- Integrated LUFS only — not short-term or momentary loudness.
The short version
Measure integrated loudness once at import (or trust ReplayGain / Sound Check tags when present), store LUFS and true peak, apply a true-peak-safe gain at playback on iPhone and Apple Watch from your chosen −14 / −16 / −11 reference. Don’t re-encode. Don’t rely on iOS Sound Check. Don’t analyse mid-song. Even loud, even quiet, same formula on both devices.
→ Back to the overview