はじめに
この記事では、Web Audio API の位置音声機能を使用して、WebGL シーンに 3D サウンドを追加する方法について説明します。音声をよりリアルにするために、Web Audio API で可能な環境効果についても説明します。Web Audio API の詳細については、Boris Smus による Web Audio API のスタートガイドをご覧ください。
位置オーディオを行うには、Web Audio API の AudioPannerNode を使用します。AudioPannerNode は、音の位置、向き、速度を定義します。また、Web Audio API オーディオ コンテキストには、リスナーの位置、向き、速度を定義できるリスナー属性があります。これら 2 つの機能を使用すると、ドップラー効果と 3D パンニングで指向性のあるサウンドを作成できます。
上記のシーンの音声コードを見てみましょう。これは非常に基本的な Audio API コードです。複数の Audio API ノードを作成して接続します。オーディオ ノードは、個々のサウンド、音量コントローラ、エフェクト ノード、アナライザなどです。このグラフを作成したら、音声を再生できるように音声コンテキストの宛先に接続する必要があります。
// Detect if the audio context is supported.
window.AudioContext = (
window.AudioContext ||
window.webkitAudioContext ||
null
);
if (!AudioContext) {
throw new Error("AudioContext not supported!");
}
// Create a new audio context.
var ctx = new AudioContext();
// Create a AudioGainNode to control the main volume.
var mainVolume = ctx.createGain();
// Connect the main volume node to the context destination.
mainVolume.connect(ctx.destination);
// Create an object with a sound source and a volume control.
var sound = {};
sound.source = ctx.createBufferSource();
sound.volume = ctx.createGain();
// Connect the sound source to the volume control.
sound.source.connect(sound.volume);
// Hook up the sound volume control to the main volume.
sound.volume.connect(mainVolume);
// Make the sound source loop.
sound.source.loop = true;
// Load a sound file using an ArrayBuffer XMLHttpRequest.
var request = new XMLHttpRequest();
request.open("GET", soundFileName, true);
request.responseType = "arraybuffer";
request.onload = function(e) {
// Create a buffer from the response ArrayBuffer.
ctx.decodeAudioData(this.response, function onSuccess(buffer) {
sound.buffer = buffer;
// Make the sound source use the buffer and start playing it.
sound.source.buffer = sound.buffer;
sound.source.start(ctx.currentTime);
}, function onFailure() {
alert("Decoding the audio buffer failed");
});
};
request.send();
位置
位置オーディオは、音声ソースの位置とリスナーの位置を使用して、スピーカーへの音声のミックス方法を決定します。リスナーの左側にある音源は左スピーカーで大きくなり、右側にある音源は右スピーカーで大きくなります。
開始するには、オーディオ ソースを作成し、AudioPannerNode にアタッチします。次に、AudioPannerNode の位置を設定します。これで、移動可能な 3D サウンドが作成されました。オーディオ コンテキスト リスナーの位置はデフォルトで(0,0,0)であるため、この方法で使用すると、AudioPannerNode の位置はカメラの位置を基準とします。カメラを移動するたびに、AudioPannerNode の位置を更新する必要があります。AudioPannerNode の位置をワールドに対して相対的にするには、オーディオ コンテキスト リスナーの位置をカメラの位置に変更する必要があります。
位置トラッキングを設定するには、AudioPannerNode を作成し、メイン ボリュームに接続する必要があります。
...
sound.panner = ctx.createPanner();
// Instead of hooking up the volume to the main volume, hook it up to the panner.
sound.volume.connect(sound.panner);
// And hook up the panner to the main volume.
sound.panner.connect(mainVolume);
...
フレームごとに AudioPannerNode の位置を更新します。以下の例では Three.js を使用します。
...
// In the frame handler function, get the object's position.
object.position.set(newX, newY, newZ);
object.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);
// And copy the position over to the sound of the object.
sound.panner.setPosition(p.x, p.y, p.z);
...
リスナーの位置を追跡するには、音声コンテキストのリスナーの位置をカメラの位置に合わせて設定します。
...
// Get the camera position.
camera.position.set(newX, newY, newZ);
camera.updateMatrixWorld();
var p = new THREE.Vector3();
p.setFromMatrixPosition(camera.matrixWorld);
// And copy the position over to the listener.
ctx.listener.setPosition(p.x, p.y, p.z);
...
速度
リスナーと AudioPannerNode の位置が決まったので、次は速度に注目します。リスナーと AudioPannerNode の速度プロパティを変更すると、音にドップラー効果を追加できます。Web Audio API のサンプル ページには、ドップラー効果の例がいくつかあります。
リスナーと AudioPannerNode の速度を取得する最も簡単な方法は、フレームごとの位置を追跡することです。リスナーの速度は、カメラの現在の位置から前のフレームのカメラの位置を引いたものです。同様に、AudioPannerNode の速度は、現在の位置から前の位置を引いたものです。
速度の追跡は、オブジェクトの前の位置を取得し、現在の位置から差し引いて、その結果を前回のフレームからの経過時間で除算することで行えます。Three.js で行う方法は次のとおりです。
...
var dt = secondsSinceLastFrame;
var p = new THREE.Vector3();
p.setFromMatrixPosition(object.matrixWorld);
var px = p.x, py = p.y, pz = p.z;
object.position.set(newX, newY, newZ);
object.updateMatrixWorld();
var q = new THREE.Vector3();
q.setFromMatrixPosition(object.matrixWorld);
var dx = q.x-px, dy = q.y-py, dz = q.z-pz;
sound.panner.setPosition(q.x, q.y, q.z);
sound.panner.setVelocity(dx/dt, dy/dt, dz/dt);
...
向き
向きは、音源が向いている方向と、リスナーが向いている方向です。向きを使用すると、指向性のある音源をシミュレートできます。たとえば、指向性スピーカーについて考えてみましょう。スピーカーの前に立つと、スピーカーの後ろに立つよりも音が大きくなります。さらに重要なのは、リスナーの向きによって、音がリスナーのどちら側から聞こえているかを判断できることです。左から聞こえる音が、振り向いたときに右から聞こえるようにする必要があります。
AudioPannerNode の向きベクトルを取得するには、音を発する 3D オブジェクトのモデル行列の回転部分を取り、vec3(0,0,1) を乗算して、最終的にどこを指しているかを確認する必要があります。コンテキスト リスナーの向きについては、カメラの向きベクトルを取得する必要があります。リスナーの向きは、リスナーの頭のロール角度を知る必要があるため、上向きベクトルも必要です。リスナーの向きを計算するには、カメラのビュー行列の回転部分を取得し、向きの vec3(0,0,1) と上向きベクトルの vec3(0,-1,0) を乗算します。
向きが音に影響するようにするには、音の円錐も定義する必要があります。音響コーンには、内側の角度、外側の角度、外側のゲインがあります。内側の角度内では通常の音量で再生され、外側の角度に近づくにつれてゲインが外側のゲインに徐々に変化します。外角の外側では、音は外側のゲインで再生されます。
Three.js で向きをトラッキングするのは、ベクトル数学と 4x4 ワールド行列の移動部分をゼロにする作業が伴うため、少し複雑です。それでも、コード行数は多くありません。
...
var vec = new THREE.Vector3(0,0,1);
var m = object.matrixWorld;
// Save the translation column and zero it.
var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;
// Multiply the 0,0,1 vector by the world matrix and normalize the result.
vec.applyProjection(m);
vec.normalize();
sound.panner.setOrientation(vec.x, vec.y, vec.z);
// Restore the translation column.
m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...
カメラの向きのトラッキングにも上向きベクトルが必要であるため、上向きベクトルを変換行列で乗算する必要があります。
...
// The camera's world matrix is named "matrix".
var m = camera.matrix;
var mx = m.elements[12], my = m.elements[13], mz = m.elements[14];
m.elements[12] = m.elements[13] = m.elements[14] = 0;
// Multiply the orientation vector by the world matrix of the camera.
var vec = new THREE.Vector3(0,0,1);
vec.applyProjection(m);
vec.normalize();
// Multiply the up vector by the world matrix.
var up = new THREE.Vector3(0,-1,0);
up.applyProjection(m);
up.normalize();
// Set the orientation and the up-vector for the listener.
ctx.listener.setOrientation(vec.x, vec.y, vec.z, up.x, up.y, up.z);
m.elements[12] = mx;
m.elements[13] = my;
m.elements[14] = mz;
...
音の音響コーンを設定するには、パンナー ノードの適切なプロパティを設定します。円錐の角度は度単位で、0 ~ 360 の範囲で指定します。
...
sound.panner.coneInnerAngle = innerAngleInDegrees;
sound.panner.coneOuterAngle = outerAngleInDegrees;
sound.panner.coneOuterGain = outerGainFactor;
...
すべて
まとめると、音声コンテキスト リスナーはカメラの位置、向き、速度に追従し、AudioPannerNode はそれぞれの音声ソースの位置、向き、速度に追従します。AudioPannerNodes とオーディオ コンテキスト リスナーの位置、速度、向きをフレームごとに更新する必要があります。
環境への影響
位置オーディオを設定したら、オーディオの環境効果を設定して、3D シーンの没入感を高めることができます。シーンが大きな大聖堂の中に設定されているとします。デフォルト設定では、シーン内の音は屋外にいるように聞こえます。映像と音声の不一致は没入感を損ね、シーンの印象を弱めます。
Web Audio API には、音の環境効果を設定できる ConvolverNode があります。音声ソースの処理グラフに追加すると、設定に合わせて音声を調整できます。ConvolverNodes で使用できるインパルス応答のサンプルはウェブで入手できます。また、独自のサンプルを作成することもできます。シミュレートする場所のインパルス レスポンスを記録する必要があるため、少し手間がかかりますが、必要に応じてこの機能を使用してください。
ConvolverNode を使用して環境音を作成するには、音声処理グラフを再配線する必要があります。音声をメイン ボリュームに直接渡すのではなく、ConvolverNode 経由でルーティングする必要があります。環境効果の強さを制御する必要がある場合は、ConvolverNode の周囲に音声をルーティングする必要があります。ミックス音量を制御するには、ConvolverNode とプレーン オーディオに GainNode を接続する必要があります。
私が使用している最終的な音声処理グラフでは、オブジェクトからの音声がパススルー ミキサーとして使用される GainNode を通過します。ミキサーから音声を ConvolverNode と別の GainNode に渡します。これは、通常の音声の音量を制御するために使用されます。ConvolverNode は独自の GainNode に接続され、畳み込み音声の音量を制御します。GainNode の出力はメイン ボリューム コントローラに接続されています。
...
var ctx = new webkitAudioContext();
var mainVolume = ctx.createGain();
// Create a convolver to apply environmental effects to the audio.
var convolver = ctx.createConvolver();
// Create a mixer that receives sound from the panners.
var mixer = ctx.createGain();
sounds.forEach(function(sound){
sound.panner.connect(mixer);
});
// Create volume controllers for the plain audio and the convolver.
var plainGain = ctx.createGain();
var convolverGain = ctx.createGain();
// Send audio from the mixer to plainGain and the convolver node.
mixer.connect(plainGain);
mixer.connect(convolver);
// Hook up the convolver to its volume control.
convolver.connect(convolverGain);
// Send audio from the volume controls to the main volume control.
plainGain.connect(mainVolume);
convolverGain.connect(mainVolume);
// Finally, connect the main volume to the audio context's destination.
volume.connect(ctx.destination);
...
ConvolverNode を機能させるには、インパルス応答サンプルをバッファに読み込み、ConvolverNode で使用できるようにする必要があります。サンプルの読み込みは、通常のサウンド サンプルの場合と同じ方法で行われます。以下にその例を示します。
...
loadBuffer(ctx, "impulseResponseExample.wav", function(buffer){
convolver.buffer = buffer;
convolverGain.gain.value = 0.7;
plainGain.gain.value = 0.3;
})
...
function loadBuffer(ctx, filename, callback) {
var request = new XMLHttpRequest();
request.open("GET", soundFileName, true);
request.responseType = "arraybuffer";
request.onload = function() {
// Create a buffer and keep the channels unchanged.
ctx.decodeAudioData(request.response, callback, function() {
alert("Decoding the audio buffer failed");
});
};
request.send();
}
概要
この記事では、Web Audio API を使用して 3D シーンに位置オーディオを追加する方法について説明しました。Web Audio API を使用すると、音源とリスナーの位置、向き、速度を設定できます。3D シーン内のオブジェクトを追跡するように設定することで、3D アプリに豊かなサウンドスケープを作成できます。
音声エクスペリエンスをさらに魅力的なものにするためには、Web Audio API の ConvolverNode を使用して、環境の一般的な音を設定します。大聖堂から密閉された部屋まで、Web Audio API を使用してさまざまなエフェクトや環境をシミュレートできます。
参照
- Web Audio API の仕様
- インパルス応答
- Three.js
- 3D 空間オーディオのクールな例
- Web Audio の例には、Web Audio API 機能の使用に関する多くの優れた例が記載されています。