스테레오 패닝

Stereo panning

소리를 왼쪽·오른쪽 스피커 사이 어디에 위치시킬지 정하는 것. 공간감과 방향성을 만듭니다.

다른 이름: PanL/R balance
···
html
<div class="wrap">
  <div class="meter l" id="ml"><i></i></div>
  <div class="field" id="field">
    <div class="mid"></div>
    <div class="dot" id="dot"></div>
    <button class="play" id="play" type="button" aria-label="play sound"><span class="tri"></span></button>
  </div>
  <div class="meter r" id="mr"><i></i></div>
</div>
css
.wrap{position:relative;width:94%;height:86%;display:flex;align-items:center;gap:4%}
.field{position:relative;flex:1;height:60%;border:1px solid var(--line);border-radius:12px;background:var(--surface);overflow:hidden}
.mid{position:absolute;left:50%;top:0;bottom:0;width:1px;background:var(--line)}
.dot{position:absolute;top:50%;width:14px;height:14px;border-radius:50%;background:var(--accent);transform:translate(-50%,-50%);left:50%}
.meter{width:10%;height:70%;border:1px solid var(--line);border-radius:6px;background:var(--surface);display:flex;align-items:flex-end;overflow:hidden}
.meter i{width:100%;height:10%;background:linear-gradient(0deg,var(--accent-3),var(--accent))}
.play{position:absolute;top:6px;right:6px;width:20px;height:20px;border-radius:50%;border:1px solid var(--line);background:var(--surface);display:grid;place-items:center;cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,.12);z-index:3}
.play .tri{width:0;height:0;border-style:solid;border-width:5px 0 5px 7px;border-color:transparent transparent transparent var(--fg);margin-left:1px}
.play:active{transform:scale(.92)}
@media (min-width:460px){
  .dot{width:26px;height:26px}
  .play{width:38px;height:38px;top:12px;right:12px}
  .play .tri{border-width:8px 0 8px 11px;margin-left:2px}
}
js
const dot = document.getElementById('dot');
const barL = document.querySelector('#ml i');
const barR = document.querySelector('#mr i');
let shotStart = null;
const IDLE_PERIOD = 3200;
function pos(t) {
  if (shotStart !== null) {
    const p = Math.min(1, (t - shotStart) / 1000);
    if (p >= 1) shotStart = null;
    return p * 2 - 1;
  }
  return Math.sin(((t % IDLE_PERIOD) / IDLE_PERIOD) * Math.PI * 2) * 0.85;
}
function frame(t) {
  const pan = pos(t);
  dot.style.left = (50 + pan * 42) + '%';
  const rNorm = (pan + 1) / 2;
  barL.style.height = (14 + (1 - rNorm) * 70) + '%';
  barR.style.height = (14 + rNorm * 70) + '%';
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
document.getElementById('play').addEventListener('click', () => {
  shotStart = performance.now();
  const ctx = new (window.AudioContext || window.webkitAudioContext)();
  const now = ctx.currentTime;
  const osc = ctx.createOscillator();
  const gain = ctx.createGain();
  const panner = ctx.createStereoPanner();
  osc.type = 'sine';
  osc.frequency.value = 523.25;
  gain.gain.setValueAtTime(0.13, now);
  gain.gain.setValueAtTime(0.13, now + 0.9);
  gain.gain.exponentialRampToValueAtTime(0.0001, now + 1);
  panner.pan.setValueAtTime(-1, now);
  panner.pan.linearRampToValueAtTime(1, now + 1);
  osc.connect(gain).connect(panner).connect(ctx.destination);
  osc.start(now);
  osc.stop(now + 1);
  osc.onended = () => ctx.close();
});

모노 소리 하나를 왼쪽·오른쪽 채널에 다른 비율로 나눠 보내면 사람의 뇌는 그 소리가 특정 방향에서 온다고 인식합니다. Web Audio API의 StereoPannerNode.pan은 -1(완전 왼쪽)부터 1(완전 오른쪽)까지의 값으로 이를 제어하며, 0은 정중앙입니다. 등가전력 패닝(equal-power panning)을 쓰면 중앙을 지날 때도 음량이 줄어드는 느낌 없이 자연스럽게 좌우로 이동합니다.

알림·UI 사운드는 대부분 정중앙(0)에 고정해 어떤 방향에서 듣든 놓치지 않게 합니다. 패닝을 적극적으로 쓰는 경우는 멀티채널 게임 효과음(왼쪽에서 나는 발소리)이나 앱 내 여러 요소를 구분해야 하는 알림처럼 공간 정보 자체가 의미를 가질 때뿐입니다. 이어폰과 스피커에서 체감이 다르고 휴대폰 하단의 모노 스피커 하나에서는 패닝이 아예 사라지므로, 패닝만으로 전달하는 정보는 절대 유일한 신호가 되면 안 됩니다.

게임의 3D 방향성 효과음, 멀티트랙 음악 믹싱, 좌우로 배치된 알림(협업 툴의 커서 위치 알림)에 쓰입니다.

언제 쓰나

공간·방향 정보 자체가 의미 있을 때만 씁니다. 알림음은 기본적으로 중앙(pan=0)에 고정하세요.