About

イヤホンの「左だけ」は二種類 — StereoPanner(寄せる)とChannelSplitter(選ぶ)

pan: -1は右の音を捨てずに左へ合わせる。実測で見た違い

「左のイヤホンからだけ音を出す」は、Webページ側で十分に決められる。ただ方法が二つあって、結果が違う。StereoPannerNode の pan: -1 は音全体を左へ寄せ、ChannelSplitter は元音源の左チャンネルだけを選ぶ。読み終えれば、どちらを使うべきか、そして「右の楽器の音が左から聞こえる」という報告がなぜ来るのかがわかる。

イヤホンは判断しない

イヤホンは受け取ったLを左から、Rを右から鳴らすだけだ。左右はその手前ですでに決まって届く。決まる場所は四つある。

層 誰が決めるか
① 音源ファイル 録音・ミックスの時点でL/Rに別の音を入れる(ステレオ、ASMR)
② プレーヤー・アプリ 再生しながら変える。WebならWeb Audio API
③ OS 左右バランス、モノラルオーディオ(アクセシビリティ設定)
④ イヤホン 受け取ったとおりに鳴らす

開発者が触れるのは②だ。③はユーザーの設定なのでアプリからは変えられず、むしろ③が②を上書きしうる。ユーザーがモノラルオーディオをオンにすれば、OSが最後に左右を混ぜるので、アプリの「左だけ」は効かなくなるはずだ。これはまだmacOSで確かめていない。

Webでのやり方は二つ

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> なので、そもそも触れない

だから「Netflixを左耳だけで」のようなことはWebページからはできない。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個すべて「無音」と出た理由 →