Audio Spatializer SDK(オーディオ立体化 SDK)は、ネイティブオーディオプラグイン SDK の拡張機能で、オーディオソースから周囲のスペースへのオーディオの伝わり方を調整するためのものです。ビルトインのオーディオソースのパニングは、ソースを取得して AudioListener と AudioSource の間の距離と角度に基づいて左右の耳のゲインの寄与を調整するという意味において、簡単な形の spatialization(立体化)であると考えることができます。これは、水平面上でプレイヤーに方向の指示を与えます。
仮想現実システムや拡張現実システムの到来とともに、立体化(spatialization)がプレイヤーの没入感にとってますます重要な鍵となりつつあります。私たちの耳や脳は、ひとつの音源から来る音を受け取る際に、左右の耳同士の間に生じるごくわずかな遅延を、非常に鋭い感度で感知することができます。さらに私たちは、高周波のバランスの変化を無意識的に読み取ることで、物が自分の前にあるのか、後ろにあるのか、あるいは上や下にあるのかを認識する能力も備えています。また左右の耳で受け取る音の違いから物が部分的に閉塞していることが分かったり、音の反射を元に自分の居る部屋の形状をある程度読み取ることも可能です。つまり、あまり意識することはないかもしれませんが、音は私たちの日常生活にとって非常に重要なものなのです。
サウンドのオクルージョンは、計算能力の観点から見て非常に解決が困難な問題です。グローバルイルミネーションではライトの動きは実質、瞬間的であるといえますが、サウンドは非常にゆっくり動いています。したがって、サウンドが部屋の中を実際にどのように(波として)移動するかを計算するのはコンピューター的に実行不可能なのです。このことから、立体化に関しては他にも、色々な問題に取り組んださまざまなアプローチが存在します。
一部のソリューションは HRTF の問題のみ解決するものです。HRTF は Head-Related Transfer Function(頭部伝達関数)の略で、ざっくり例えると、グラフィックスで言うところの球面調和関数( spherical harmonics)です。すなわち、両耳に適用される、方向的影響を受けたサウンドのフィルタリングで、両耳間におけるごくわずかな遅延と、耳介と頭自体と肩によってもたらされる方向性フィルタリングの両方を含むものです。 HRTF フィルタリングを加えるだけで、従来のパニングのソリューション(「Virtual Barber Shop(仮想理髪店)」のバイノーラル録音がその代表的な例)に比べて方向的表現が格段に向上します。ただしダイレクト HRTF にはある程度の限界があります。なぜならダイレクト HRTF はオーディオのダイレクトパスのみを考慮しており、オーディオが空間においてどのように伝達するかは考慮しないためです。
次のステップはオクルージョンです。オクルージョンはサウンドを間接的に壁に反射させることができます。これはグラフィックスで言うと、ソースとリスナーの両方の位置によって効果が変わるという意味において、鏡面反射(specular reflection)に例えられるかもしれません。もちろん、反射したサウンドのそれぞれの方向波は、左右それぞれの耳に異なる HRTF で届き、波の伝達された進路の長さによって遅延の度合いも変わります。
最後に、部屋の反射があります。これは色々な意味でグローバルイルミネーション・ソリューションにおける乱反射(ディフューズ)の部分に相当します。サウンドが部屋にの中に放たれ、複数の壁に反射してから、異なる方向性と(オーディオソースに対する)異なる累積遅延を持つ波が重なり合ったフィールドとして、耳に届きます。
難解な問題が数多くある中にあって、オーディオ立体化のソリューションにはさまざまなものが存在します。これに対応する最善の策として、Unity では、オープン・インターフェースである Audio Spatializer SDK を作成しました。これは Native Audio Plugin SDK の拡張で、Unity の標準パナーをより高度なパナーに置き換えることを可能にし、計算に必要な、ソースとリスナーに関する重要なメタデータにアクセスできるようにするものです。
Spatializer の実装サンプルは こちら でご確認いただけます。この実装サンプルは敢えて簡易的に作られたもので、ダイレクト HRTF のみに対応しており、製品版で使用する場合には最適化が必要になります。プラグインには簡単なリバーブが付いてきます。これは、オーディオデータが Spatializer プラグインから Reverb プラグインに送信されるのを確認できるようにするためのものです。HRTF フィルタリング は KEMAR のデータセットに基づいています。これは、MIT Media Lab で Bill Gardner によってダミーの頭を使って作成された、片耳ごとのインパルス応答記録の一式です。このインパルス応答は、高速フーリエ変換による高速畳み込みを使用して、入力信号に畳み込みされています。位置に関するメタデータは正しいインパルス応答セットを選ぶためだけに使用されます。データセットは、頭の下–40°~頭の上90°の仰角範囲で配置された一式のインパルス応答によって構成されています。
Unity の立体化(Spatialization)エフェクトとミキサーエフェクトの主な違いは、Spatializer はオーディオデータのストリームを作り出すオーディオソースデコーダーの直後に配置されることです。これは、それぞれのソースに、そのソースが生んだオーディオのみを処理する独自のエフェクト・インスタンスを持たせるためです。これとは違いミキサープラグインは、ミキサーグループに接続された異なるオーディオソースから来るオーディオの混合したものを処理します。
definition.flags |= UnityAudioEffectDefinitionFlags_IsSpatializer;
初期化の際にこのフラグを設定すると、プラグインのスキャニングの段階で、これがSpatializer であることを Unity に知らせます。つまり、このプラグインのインスタンスが作成されると、UnityAudioSpatializerData 構造がUnityAudioEffectState 構造の spatializerdata メンバーに割り当てられます。
Spatializer をプロジェクトで使用するためには、Audio Project の設定であらかじめ選択しておく必要があります。
AudioSource 上で Spatialize のチェックボックスをオンにすると Spatializer が使えるようになります。これはスクリプトから AudioSource.spatialize プロパティーによって制御することもできます。サウンドを多く使用しているゲームでは、近くのサウンドにのみ Spatializer を有効にし、遠くのサウンドには従来のパニングを使用するほうが適しているかもしれません。
ミキサー上で、ミックスされたサウンドに対して使用される他のエフェクトと違って、Spatializer は AudioSource がオーディオデータをでデコードした直後に適用されます。したがって Spatializer エフェクトの各インスタンスが、 AudioSource に関する主要なデータと関連付けられた UnityAudioSpatializerData を1つ持ちます。
struct UnityAudioSpatializerData
{
float listenermatrix[16]; // sourcepos をリスナーのローカル空間に変換するマトリックス
float sourcematrix[16]; // オーディオソースのトランスフォームマトリックス
float spatialblend; // 距離が制御された spatial blend
float reverbzonemix; // オーディオソースのリバーブゾーン混合レベルパラメーター (およびカーブ)
float spread; // オーディオソースのスプレッドパラメーター (0~360 度)
float stereopan; // オーディオソースのステレオパニングパラメーター (-1: 完全に左 1: 完全に右)
// 立体化プラグインは、ボイスの優先順位に影響を与えるため減衰距離を
// オーバーライドする場合があります (ビルトインのオーディオソース
// 減衰カーブを使用するために、このコールバックを NULL のままにします)
UnityAudioEffect_DistanceAttenuationCallback distanceattenuationcallback;
};
この構造体は、リスナーとソースのためのフル4x4 トランスフォーム・マトリックスを含んでいます。リスナーのマトリックスはすでに反転されており、2つのマトリックスを簡単に乗算して相対的方向ベクトルを求められるようになっています。リスナーマトリックスは常に正規直交なので反転の計算負荷が軽くなっています。さらにこの構造体には、オーディオソースのプロパティーに対応したフィールド(Spatial Blend、Reverb Zone Mix、Spread、Stereo Pan)も含まれます。これらを正しく実装するのは Spatializer の役割です。なぜなら、Spatializer がアクティブになっているときは Unity のオーディオシステムは生のソースサウンドのみをステレオ信号として提供するからです。(ソースがモノあるいはマルチチャンネルになっている場合、つまりアップミックスあるいはダウンミックスが使用されている場合も同様です。)
sourcematrix フィールドには AudioSource に関連付けられた変形マトリックスの単純なコピーが含まれます。これは、回転されていないゲームオブジェクトの単純な AudioSource では、位置がエレメント12、13、14にエンコードされた変形マトリックスになります。 listenermatrix フィールドには AudioListner と関連付けられた変形マトリックスの逆行列が含まれます。これは、以下のようにリスナーからソースへの方向ベクトルを求めるのに非常に便利です。
float dir_x = L[0] * S[12] + L[4] * S[13] + L[ 8] * S[14] + L[12];
float dir_y = L[1] * S[12] + L[5] * S[13] + L[ 9] * S[14] + L[13];
float dir_z = L[2] * S[12] + L[6] * S[13] + L[10] * S[14] + L[14];
L は listenermatrix で S は sourcematrix です。回転されていない listenermatrix でスケーリングが 1 に統一されたもの(カメラのマトリックスはスケーリングしません)がある場合は、(L[12]、L[13]、L[14]) の位置は実際には、Unity のインスペクターに表示されたものに対する負の値になります。これは、listenermatrix がカメラの変形マトリックスの逆行列であるためです。もしカメラも回転されている場合は、単純にネゲートしただけではマトリックスから直接位置を読み取ることはできず、まず回転エフェクトの取り消しを行う必要があります。幸い、このような Transformation-Rotation-Scaling マトリックスの反転は、こちら に説明される通り簡単に行えます。つまり必要なのは、L の左上の 3x3 の回転マトリックスを転置して、以下のように位置を計算することです。
float listenerpos_x = -(L[0] * L[12] + L[ 1] * L[13] + L[ 2] * L[14]);
float listenerpos_y = -(L[4] * L[12] + L[ 5] * L[13] + L[ 6] * L[14]);
float listenerpos_z = -(L[8] * L[12] + L[ 9] * L[13] + L[10] * L[14]);
唯一、今もなお Unity オーディオシステムによって処理されているのは距離減衰です。距離減衰はサウンドが立体化の段階に入る前に適用されます。これはなぜかというと、ソースのおおよその可聴度をオーディオシステムに把握させるために必要だからです。可聴度は、ユーザー定義の Max Real Voices の限界に合わせるために、重要度に基づいた、サウンドの動的な視覚化に使用されることもあります。これは卵が先か鶏が先かという問題になるため、この情報は、実際の信号レベル測定値から取得されるのではなく、距離制御の減衰カーブから読み取られる値、ミキサーによって適用される Volume プロパティー、および減衰の組み合わせに一致します。ただし、自分で減衰カーブをオーバーライドしたり、AudioSource のカーブから算出された値を修正の基盤として使用することも可能です。これを行いたい場合に実装できるコールバックが UnityAudioSpatializerData 構造体の中にあります。
typedef UNITY_AUDIODSP_RESULT (UNITY_AUDIODSP_CALLBACK* UnityAudioEffect_DistanceAttenuationCallback)(
UnityAudioEffectState* state,
float distanceIn,
float attenuationIn,
float* attenuationOut);
簡単なカスタム対数カーブは、単純に以下のように実装することもできます。
UNITY_AUDIODSP_RESULT UNITY_AUDIODSP_CALLBACK SimpleLogAttenuation(
UnityAudioEffectState* state,
float distanceIn,
float attenuationIn,
float* attenuationOut)
{
const float rollOffScale = 1.0f; // Audio Project の設定にあるものと同様
*attenuationOut = 1.0f / max(1.0f, rollOffScale * distanceIn);
return UNITY_AUDIODSP_OK;
}
ネイティブ側を補足して、AudioSource に、Spatializer エフェクトから設定し取得できる 2つの新しいメソッドもあります。これらは、SetSpatializerFloat/GetSpatializerFloat といい、ジェネリック ネイティブ オーディオプラグイン インターフェースで使用される SetFloatParameter/GetFloatParameter と似ています。主な違いは、SetSpatializerFloat/GetSpatializerFloat はパラメーターに関連づけて設定や読み込みが行われますが、SetFloatParameter/GetFloatParameter はパラメーターを名前で参照します。
さらに、boolean プロパティーの AudioSource.spatializer は AudioSource インスペクターのチェックボックスに連動していて、Audio Project 設定の選択に基づいて、立体化エフェクトのインストールとデアロケーションを制御しています。立体化エフェクトのインスタンス化の負荷がとても高いなら (メモリーアロケーション、プレカルキュレーションなどのために)、Unity プラグインインターフェース群をとても軽くしておき、エフェクトをプールから動的にアロケーションすることを考えるとよいかもしれません。そうすると、アクティベーション/非アクティベーションがコマ落ちを引き起こすことを防げます。
使用されている高速畳み込みのアルゴリズムに起因して、高速移動の際にジッパーノイズが発生する場合があります。これは重畳保留畳み込みかクロスフェーディング・バッファーの使用によって取り除くことができます。また、コードでは頭を横に傾けることができませんが、これは簡単に解決可能であると思われます。 このデモで使用されているデータセットは KEMAR のデータセットのみです。IRCAM から、人体実験の被験者から入手される データセット がいくつか入手可能です。