ステレオパンニングの実装(R Advent Calendar 2014)

はじめに

R Advent Calendar 2014 (ATND)の28日目の記事です。Qiitaにもアドベント・カレンダーがあるのですね。

昨年のカレンダーでもRを使って音響信号処理っぽいことをしたのですが、今年もその路線でやりたいと思います。今年は少し簡単な素材として、ステレオパンニングを実装します。

ただ、ステレオパンニングそのものの説明から入ったため、説明が長くなっていて、Rコードはちょこっとだけです。

両耳聴のかんたんな説明

まず耳の仕組みから説明します。左耳に届いた音と右耳に届いた音が類似しているとき(音色、到達時刻、音量などの差が小さいとき)には、左右耳に届いた音はひとつの物体から発せられたものだろうと脳が推測します。このような左右の耳の連携がないと、話し相手の口から出てきた言葉が(似た声を持った二人の人が同じ言葉を同時に話しているように)二重に聞こえてしまうことになり、日常生活に不便です。なので、人によって異なりますが、だいたい到達時刻の差が30〜50ミリ秒以内であれば、似た音の場合は一つの音源だということにしてしまおう、となっているのです。

さらに、左右の耳へ到達した音の時間差や音量差によって、音がやってくる方向も知ることができます。左右の耳への音の到達時刻の差がおよそ1ミリ秒以下のときには、早く届いた耳の側に音が寄ることが分かっていますし、時間差がゼロのときには音量の大きな方に音が寄ります。このとき音量の差が大きければ大きいほど、より音の偏りが顕著になります。

以上のふたつの耳の仕組みを利用して、音を左右方向の狙った位置に定位させるのがステレオパンニングです。

パンニング

通常のステレオ再生(開き角60度に設置した2台のスピーカーやヘッドホン再生)では、左右のスピーカの中央にはスピーカーが置かれていないにもかかわらず、左右スピーカの間から音が聞こえるような音響表現が当たり前のように行われています。また、中央からわずかに左右に音がずれている表現などもあります。これは上記の耳の仕組みを利用しているわけです。こういった、音を左右にずらして定位させる表現のことをパンニングと呼び、パンニングをする装置のことをPan Potentiometer略してパンポット(Pan Pot)と呼んでいます。

f:id:amarui:20141229213609p:plain

さて、ヘッドホン再生の時には左右の時間差を調整すると、およそ意図通りの時間差で耳に音が届くでしょうが、スピーカー再生の時には頭が左右スピーカーの中央にきっちり置かれている保障がないので、時間差によるパンニングは使用しにくいです。また、時間差を付けた音源を左右加算してモノラル再生をすると(櫛形フィルタ効果によって)音色が変化してしまうので、伝統的に音楽作品・放送番組などの制作をするときには左右の音量差だけをつける方法を取っています。厳密にいえば、音量は“人が感じる音の大きさ”なので直接音量を操作することはできません。それに最もよく対応する音圧(音の強さ)を操作することになります。

立体音響まで話を広げると、パンニングには上下や前後の表現も含むことになりますが、本稿では2チャンネルステレオ再生における音圧パンニングだけを考えます。

パンニング・ロー

実装するには、左右の音圧差をどのように設定するか、ということを考える必要があります。具体的には、左右の位置を–100〜+100で表すとして、例えば下図のように+40の場所に音を定位させるとき、左のスピーカーと右のスピーカーにはどのくらいの音の強さを配分すればいいのだろうか、という問題です。図ではすでにカーブが描かれていますが、このカーブをどのようなものにするかによって、左右スピーカーへの配分率が変わるのです。

f:id:amarui:20141228121908p:plain

いくつかのカーブが考えられているのですが、ここでは図のように正弦波を用いる方法*1を採用します。緑色の線が左スピーカーへの音圧の配分率、赤色の線が右スピーカーへの音圧の配分率を示しています。このカーブはそれぞれ正弦波の一部を取り出したものです。

ここで左右スピーカーへ正弦関数を用いて配分する理由を考えたいと思います。まず、上図横軸をpとして\thetaを定義しておきます。(pの範囲が–100〜+100なのを、0〜\frac{\pi}{2}に変換しています)

\displaystyle\theta=\frac{p+100}{200}\cdot\frac{\pi}{2}

ここで話が少しややこしくなるのですが、人間の耳は音圧そのものを音量として感じているのではなく、音圧の2乗のほうが音量に関係があると言われています。そのため、左右スピーカーからの音量の合計は

\displaystyle\sin\theta + \sin\left(\theta+\frac{\pi}{2}\right)

ではなく、それぞれを2乗した

\displaystyle\sin^2\theta + \sin^2\left(\theta+\frac{\pi}{2}\right)

に関係するということになります。ここで、\cos\theta=\sin\left(\theta+\frac{\pi}{2}\right)ですので、上記式は

\displaystyle\sin^2\theta + \cos^2\theta

となり、つまり1になります。\thetaの値によらず音量が一定になるため、この正弦関数を用いることで、どの位置に音を定位させても音量が変化しないことになります。

長くなりましたが、これでだいたい準備が整いました。

Rでの実装

前段の説明が長くなってしまいました。いよいよRを使ってパンポットを実装します。

今回は、1chモノラルのWAVファイルがあるものとして、それを読み込んでパンニングをかけて2chステレオのWAVファイルとして書き出しましょう。以下のような短いプログラムで書けてしまいます。

# WAVファイルの読み書きにはtuneRパッケージを用いる
library(tuneR)

# パンニング値の設定(–100〜+100)
pan <- -50

# パンニング値からθを、さらに配分率を計算
theta <- (pan + 100) / 200 * pi/2
factor.L <- cos(theta)
factor.R <- sin(theta)

# WAVファイルを読み込む
wavobj.in <- readWave(filename="input.wav")

# 読み込んだ音に配分率をかけて、新しいwaveオブジェクトを作成する
wavobj.out <- Wave(
  left = data.frame(round(wavobj.in@.Data * factor.L),
                    round(wavobj.in@.Data * factor.R)),
  samp.rate = wavobj.in@samp.rate,
  bit = wavobj.in@bit,
  pcm = wavobj.in@pcm)

# WAVファイルに保存
writeWave(object=wavobj.out, filename="output.wav")

まとめ

  • 音を加工するときには原理さえ分かってしまえば、Rでの実装は簡単。
  • Rは統計以外のこともいろいろできる。
  • でも、あまりRには関係なかったかもしれません……。

*1:今回採用したのは正弦波を用いることで中央での配分率が左右それぞれ–3 dBになるカーブです。この方法でパンニングを行ったステレオ信号を、左右加算してモノラルにすると中央定位させた音の音量が大きくなってしまいます。そのため、中央を–6 dBにする方法や、–3 dBのカーブとの折衷案で–4.5 dBにする方法なども提案されています。ProToolsなどのDAWではカーブが選択できるようになっています。