음량 정규화

Loudness normalization

서로 다른 소스의 소리를 사람이 "똑같이 크다"고 느끼는 기준(LUFS)에 맞춰 자동으로 조정하는 것입니다.

다른 이름: 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);
});

같은 -6dB 피크라도 지속적인 음악과 순간적인 박수 소리는 사람 귀에 전혀 다른 크기로 들립니다 — 단순 피크(dBFS)는 "얼마나 크게 들리는가"를 잘 반영하지 못합니다. LUFS(Loudness Units Full Scale)는 시간에 걸친 평균 음압과 인간 청각의 주파수 민감도를 함께 반영해 "체감 음량"을 수치화합니다. 유튜브·스포티파이·넷플릭스는 모든 콘텐츠를 -14~-16 LUFS 안팎으로 정규화해서, 콘텐츠를 넘길 때마다 볼륨을 다시 만지지 않아도 되게 합니다.

UI 사운드를 여러 사람이 각자 다른 파형 소스로 만들면(어떤 건 시끄러운 톱니파, 어떤 건 조용한 사인파) 완성 뒤 실제 기기에서 들어보면 소리마다 체감 크기가 들쭉날쭉합니다. 배포 전 모든 사운드 자산의 LUFS를 측정해 목표치(모바일 앱은 대개 -18~-23 LUFS 권장)로 맞추는 정규화 단계를 반드시 거칩니다. 볼륨을 단순히 피크 기준으로만 맞추면 어떤 소리는 여전히 유독 시끄럽게 느껴질 수 있습니다.

스트리밍 플랫폼의 콘텐츠 간 볼륨 통일, 앱의 여러 알림음 세트 간 일관된 체감 음량 맞추기, 광고 소리가 프로그램보다 유독 크게 들리는 문제(러우드니스 워) 해결에 쓰입니다.

언제 쓰나

여러 사운드 자산을 한 세트로 배포하기 전에. 피크만 맞추지 말고 LUFS 기준으로 체감 음량을 맞추세요.