About

이어폰 「왼쪽만」 은 두 가지다 — StereoPanner(옮기기)와 ChannelSplitter(고르기)

pan: -1 은 오른쪽 소리를 버리지 않고 왼쪽으로 합친다. 실측으로 본 차이

「왼쪽 이어폰에서만 소리 나게」 는 웹페이지에서 충분히 정할 수 있다. 다만 방법이 두 개고 결과가 다르다. StereoPannerNode 의 pan: -1 은 소리 전체를 왼쪽으로 옮기고, ChannelSplitter 는 원본의 왼쪽 채널만 고른다. 이 글을 읽으면 어느 쪽을 써야 하는지, 그리고 왜 「오른쪽 악기 소리가 왼쪽에서 나요」 같은 리포트가 오는지 알 수 있다.

이어폰은 판단하지 않는다

이어폰은 받은 L 을 왼쪽에서, R 을 오른쪽에서 틀 뿐이다. 좌우는 그 앞에서 이미 정해져서 온다. 정해지는 곳은 네 군데다.

층 누가 정하나
① 음원 파일 녹음 · 믹싱할 때 L/R 에 다른 소리를 넣는다 (스테레오, ASMR)
② 플레이어 · 앱 재생하면서 바꾼다. 웹이면 Web Audio API
③ OS 좌우 밸런스, 모노 오디오 (접근성 설정)
④ 이어폰 받은 대로 튼다

개발자가 손대는 곳은 ② 다. ③ 은 사용자 설정이라 앱이 못 바꾸고, 오히려 ③ 이 ② 를 덮을 수 있다. 사용자가 모노 오디오를 켜면 OS 가 마지막에 좌우를 섞으니 앱의 「왼쪽만」 은 무력해질 것이다. 이건 아직 macOS 에서 직접 확인하지 못했다.

웹에서 하는 법 두 가지

A. StereoPannerNode — 옮기기

const ctx = new AudioContext()
const src = ctx.createMediaElementSource(videoEl)
const panner = new StereoPannerNode(ctx, { pan: -1 })   // -1 왼쪽, 0 가운데, +1 오른쪽
src.connect(panner).connect(ctx.destination)

B. ChannelSplitter + Merger — 고르기

const splitter = ctx.createChannelSplitter(2)
const merger = ctx.createChannelMerger(2)
src.connect(splitter)
splitter.connect(merger, 0, 0)   // 원본 L → 출력 L. 원본 R 은 아무 데도 안 잇는다
merger.connect(ctx.destination)

실측 — 차이는 왼쪽에 880Hz 가 있느냐

L 에만 440Hz, R 에만 880Hz 가 나오는 테스트 신호를 넣고 출력 채널을 분석기로 쟀다. Chrome 152 / macOS, 2026-09-19.

출력 L 출력 R
원본 440 880
pan = -1 440 + 880 무음
왼쪽만 (Splitter) 440 무음
좌우 교차 880 440
A. StereoPannerNode (move) new StereoPannerNode(ctx, { pan: -1 }) in L 440 Hz in R 880 Hz out L 440 + 880 Hz out R silent B. ChannelSplitter + Merger (pick) splitter.connect(merger, 0, 0) in L 440 Hz in R 880 Hz out L 440 Hz out R silent dropped

처음엔 pan -1 이면 오른쪽 소리가 그냥 꺼질 줄 알았다. 스펙 계산식을 보면 그렇지 않다. 스테레오 입력에 pan ≤ 0 이면 gainL = cos(x·π/2), outL = inL + inR × gainL 이다(x = pan + 1). pan = -1 이면 gainL = 1 이라 오른쪽 소리가 왼쪽에 통째로 더해진다.

하고 싶은 것 쓸 것
음악 전체를 왼쪽 귀로 (한쪽만 꼈을 때) A. Panner pan: -1
원본의 왼쪽 채널만 (오른쪽 소리는 버림) B. Splitter
왼쪽엔 원어, 오른쪽엔 통역 (음원 2개) 음원마다 Panner, -1 / +1
좌우 바꾸기 B. Splitter 에서 0→1, 1→0 교차 연결

소리가 아예 안 나는 경우

createMediaElementSource 를 거는 순간 무음이 되는 경우가 있다. 에러도 안 난다.

  • CORS 헤더 없는 다른 도메인 미디어: 스펙상 교차 출처 오디오는 스크립트에 넘겨지지 않고 무음으로 채워진다. 직접 재현해서 콘솔 문구를 확인한 건 아직이다

  • DRM 콘텐츠(EME): 보호된 오디오는 Web Audio 로 꺼낼 수 없다

  • 클릭 전에 만든 AudioContext: 자동재생 정책으로 suspended. 클릭 안에서 ctx.resume()

  • YouTube iframe: 남의 페이지 안의 <video> 라 접근 자체가 안 된다

그래서 「넷플릭스를 왼쪽 귀로만」 같은 건 웹페이지에서 못 한다. OS 밸런스 쪽 얘기가 된다.

곁가지 — 5.1 을 이어폰으로 접으면 LFE 가 사라진다

같은 데모에 채널마다 주파수가 다른 5.1ch WAV 를 넣어 봤다. 센터와 서라운드는 약 -3dB(0.707배)로 양쪽에 섞였고, 저음 전용 LFE 채널은 -141dB, 사실상 버려졌다. 스펙의 speakers 다운믹스 공식 그대로다. 영화 믹싱에서 중요한 저음을 메인 채널에도 넣는 이유가 이거다.

모노 오디오가 접근성 메뉴에 있는 데도 이유가 있다. 한쪽 귀가 안 들리는 사람은 스테레오에서 한쪽 악기를 통째로 놓친다. 왼쪽에만 중요한 정보를 싣는 설계는 피한다.

어학 앱의 좌우 분리 재생이나 이어폰 좌우 테스트 같은 기능을 만들고 있다면 negabaro 에 물어봐도 된다. 위 표는 우리 데모에서 직접 잰 값이다.

핵심 포인트

1

이어폰은 L/R 을 틀기만 한다 — 좌우는 음원 · 앱 · OS 에서 정해진다

2

pan: -1 은 오른쪽 소리를 왼쪽으로 합친다 (outL = inL + inR)

3

원본 왼쪽 채널만 원하면 ChannelSplitter 로 0번만 잇는다

4

CORS 없는 외부 미디어 · DRM · YouTube iframe 은 Web Audio 로 못 다룬다

사용 사례

어학 앱 — 왼쪽 원어, 오른쪽 번역 음성 이어폰 한쪽만 꼈을 때 「한쪽으로 모으기」 버튼, 좌우 확인 페이지
← setInterval 로 드럼을 치면 박자가 흔들린다 — 「지금 울려」 말고 「몇 초에 울려」 오디오 테스트는 화면 말고 AnalyserNode 로 — 백그라운드 탭에서 17개가 전부 「무음」 으로 나온 이유 →