Loudness normalization

음량 정규화

Automatically adjusting sounds from different sources so they're all perceived as equally loud, measured in LUFS.

Also known as: LUFSLoudness matching
···
html
<div class="wrap">
  <div class="chart">
    <div class="col"><div class="bar" id="b1"></div><span>A</span></div>
    <div class="col"><div class="bar" id="b2"></div><span>B</span></div>
    <div class="col"><div class="bar" id="b3"></div><span>C</span></div>
  </div>
  <div class="mode" id="mode">원본 (피크만 다름)</div>
  <button class="play" id="play" type="button" aria-label="play sound"><span class="tri"></span></button>
</div>
css
.wrap{position:relative;width:92%;height:86%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px}
.chart{display:flex;align-items:flex-end;justify-content:center;gap:8%;height:60%;width:78%}
.col{display:flex;flex-direction:column;align-items:center;gap:4px;width:26%;height:100%;justify-content:flex-end}
.bar{width:100%;border-radius:5px 5px 0 0;background:linear-gradient(180deg,var(--accent),var(--accent-3));transition:height .4s ease}
.col span{font:700 10px/1 monospace;color:var(--muted)}
.mode{font:600 11px/1 monospace;color:var(--muted)}
.play{position:absolute;right:0;bottom:0;width:clamp(28px,15%,40px);aspect-ratio:1;border-radius:50%;border:1px solid var(--line);background:var(--surface);display:grid;place-items:center;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.12)}
.play .tri{width:0;height:0;border-style:solid;border-width:6px 0 6px 9px;border-color:transparent transparent transparent var(--fg);margin-left:2px}
.play:active{transform:scale(.92)}
js
const b1 = document.getElementById('b1');
const b2 = document.getElementById('b2');
const b3 = document.getElementById('b3');
const modeEl = document.getElementById('mode');
const RAW = [30, 78, 48];
const NORM = [56, 60, 54];
let showNorm = false;
function apply(vals, label) {
  b1.style.height = vals[0] + '%';
  b2.style.height = vals[1] + '%';
  b3.style.height = vals[2] + '%';
  modeEl.textContent = label;
}
apply(RAW, '원본 (피크만 다름)');
setInterval(() => {
  showNorm = !showNorm;
  apply(showNorm ? NORM : RAW, showNorm ? '정규화 후 (LUFS 일치)' : '원본 (피크만 다름)');
}, 2400);
function tone(ctx, t, freq, type, peakGain, dur) {
  const osc = ctx.createOscillator();
  const gain = ctx.createGain();
  osc.type = type;
  osc.frequency.value = freq;
  gain.gain.setValueAtTime(0, t);
  gain.gain.linearRampToValueAtTime(peakGain, t + 0.02);
  gain.gain.exponentialRampToValueAtTime(0.0001, t + dur);
  osc.connect(gain).connect(ctx.destination);
  osc.start(t);
  osc.stop(t + dur + 0.02);
}
document.getElementById('play').addEventListener('click', () => {
  const ctx = new (window.AudioContext || window.webkitAudioContext)();
  const now = ctx.currentTime;
  if (showNorm) {
    tone(ctx, now, 300, 'sine', 0.1, 0.28);
    tone(ctx, now + 0.32, 500, 'sawtooth', 0.06, 0.28);
    tone(ctx, now + 0.64, 700, 'triangle', 0.11, 0.28);
  } else {
    tone(ctx, now, 300, 'sine', 0.04, 0.28);
    tone(ctx, now + 0.32, 500, 'sawtooth', 0.14, 0.28);
    tone(ctx, now + 0.64, 700, 'triangle', 0.08, 0.28);
  }
  setTimeout(() => ctx.close(), 1100);
});

Two sounds at the same -6dB peak — sustained music and a single hand-clap — can feel completely different in loudness to human ears, because raw peak level (dBFS) doesn't capture "how loud it feels." LUFS (Loudness Units Full Scale) measures perceived loudness by combining average level over time with how sensitive human hearing is at different frequencies. YouTube, Spotify, and Netflix normalize everything to roughly -14 to -16 LUFS so you don't have to reach for the volume knob between videos.

When several people build UI sounds from different raw waveforms (one loud sawtooth, one quiet sine), the finished set often feels wildly inconsistent in loudness once you hear it on a real device. Always run a normalization pass before shipping — measure every sound asset's LUFS and bring it to a target (mobile apps typically aim for -18 to -23 LUFS). Matching peaks alone isn't enough — a sound can still feel uniquely loud even at the same peak level.

Used to unify volume across content on streaming platforms, keep a consistent felt loudness across an app's notification sound set, and fix the "loudness war" where ads feel much louder than the program.

When to use

Run it before shipping a set of sound assets together. Match perceived loudness by LUFS, not just peak level.