pythonで擬似的な立体音響
はじめに
pythonを用いてモノラル音源から、擬似的な立体音響音声を生成してみます。
立体音響とは
人間は、目をつむっていても周囲の音が何処からなっているのか判断する事が出来ます。
これは、音が空気を伝搬する際にかかる時間や音の減衰などを、両耳の差で捉える事で実現しています。
例えば、以下の図は右に音源がある場合の例です。
図のように音は、水面に小石を投げ込んだ時の様に、同心円状の波面を形成します。
音が大気等の物質(媒質)を進む時間を音速というのですが、特に15℃の空気中では340.65m/sで進みます。
図では右耳に音が入ってくる時刻をt=7, 左耳に音が入ってくる時刻をt=8としていますが、ここの間に数msの差があるということになります。
この左右の耳の時間差を上手く捉えた録音をする為には、次のような人間の頭部をもしたマイクを用いるのが最もてっとり速いです。
引用: https://www.soundhouse.co.jp/products/detail/item/55883/
今回は次の図のように、左右の音声の再生タイミングをずらす事で擬似的な立体音響を行ってみたいと思います。
音声の時間差の計算
擬似的な立体音響を実現する為に、遅延時間をシミュレーションしてみます。
以下の様に、頭部の中心位置をO、頭部の中心から左右の耳までの距離をB、音源の位置をPと仮定します。
頭部Oから音源Pまでの距離をDとした時、音源Pから左右の耳までの距離をL、Rとします。
このRとLを求めて、音速で割ることで、擬似的な立体音響を実現できます。
RとLを求める為にはまず、xとyを求める必要があります。
Dとx, yの関係は三平方の定理で関係づけられますが、今回は既知の値とします。
xとyは以下の様に求める事が出来ます。
最終的にRとLは次のように求めます。
pythonによるシミュレーション
pythonで実装すると次のようになります。
import numpy as np import cv2 def draw_head(img, head, base): cv2.circle(img, head, base, (255, 200, 100), 1) cv2.line(img, (head[0]-base, head[1]-base//2), (head[0]-base, head[1]), (255, 255, 255), 1) cv2.line(img, (head[0]+base, head[1]-base//2), (head[0]+base, head[1]), (255, 255, 255), 1) cv2.line(img, (head[0], 0), (head[0], head[1]), (55, 55, 55), 1) def draw_point(img, head, base, point): cv2.circle(img, point, 5, (0, 255, 255), 1) cv2.line(img, point, (head[0]-base, head[1]), (255, 100, 100), 1) cv2.line(img, point, (head[0]+base, head[1]), (100, 100, 255), 1) cv2.line(img, point, head, (100, 150, 200), 1) def calc_D(head, point): return np.sqrt((head[0]-point[0])**2 + (head[1]-point[1])**2) def calc_A(head, point): return np.arcsin((point[0]-head[0])/calc_D(head, point)) def calc_xy(D, A): return D*np.sin(A), D*np.cos(A) def draw_text(img, head, point, L, R, A, D): cv2.putText(img, text="L: "+str(L), org=(10, 30), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(255, 100, 100), thickness=1, lineType=cv2.LINE_4) cv2.putText(img, text="R: "+str(R), org=(10, 60), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(100, 100, 255), thickness=1, lineType=cv2.LINE_4) cv2.putText(img, text="A: "+str(A/np.pi*180), org=(10, 90), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(200, 200, 200), thickness=1, lineType=cv2.LINE_4) cv2.putText(img, text="D: "+str(D), org=(10, 120), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.5, color=(200, 200, 200), thickness=1, lineType=cv2.LINE_4) def run(): W, H = 640, 640 head = (W//2, H) base = 60 while True: img = np.zeros((W, H, 3), dtype=np.uint8) point_x, point_y = np.random.randint(W), np.random.randint(H) D = calc_D(head, (point_x, point_y)) A = calc_A(head, (point_x, point_y)) x, y = calc_xy(D, A) L = np.sqrt((x+base)**2 + y**2) R = np.sqrt((x-base)**2 + y**2) draw_head(img, head, base) draw_point(img, head, base, (point_x, point_y)) draw_text(img, head, (point_x, point_y), L, R, A, D) cv2.imshow("", img) cv2.waitKey(0) if __name__ == "__main__": run()
実行すると次のような出力が得られます。
左上のRとLが音源から左右の耳までの距離です。
またAは音源までの角度α[deg]、Dは音源から頭部までの距離です。
gimpの定規ツールを用いる事で正しく動作している事が確認できます。
時間差の計算
このプログラムを用いる事で、音源から左右の耳までの距離の差を求める事が出来ます。
距離の差を音速(340m/s)で割ることで、左右の耳に対する時間差を求める事が出来ます。
ここで求めた時間差の分だけ、左右どちらかの音声信号をシフトすれば擬似的な立体音響が出来ます。
実際の音声信号シフト量は、サンプリングレートから求める事が出来ます。
例えばサンプリングレートが44.1KHzの時、時系列信号に対し、indexを44100進めると実時間で1秒分の信号になります。
すなわち、音の時間差にサンプリングレートを乗算した値が実際のシフト量になります。
実際に作った擬似的な立体音響
効果音ラボ - フリー、商用無料、報告不用の効果音素材をダウンロード 効果音ラボ様が公開している、音声素材を利用させていただきました。
以下の音声をイヤフォンやヘッドフォンで聞いてみると、いい感じに立体音声になっていると思います。
仮想的な音源位置 | 生成した音声 |
---|---|
終わりに
音声信号に遅延処理を施す事で擬似的に立体音声化する事が出来ました。
この技術を使うことで、音源を自在に再配置出来るようになり、没入感をより高められるかもしれませんね。