オーディオエンジンの設計(下調べ編)

グラフィックスと同様に、teslawireのオーディオエンジンは複数のオーディオAPIを抽象化して使用するという難しい目標に向ってデザインする。というわけで、いろいろなオーディオエンジンを調べて最小公約数を取る準備をする。

仕様の概略

teslawireのオーディオは、いわゆる oldschool な波形メモリ音源 + 3Dパンを持つPCM効果音の組合せという仕様で、携帯機を含めた全てのハード上でリアルタイム合成を目指している。

  • OSC type1: 128音PCM音源
    • サンプル長制限なし(4Gサンプルまで)、32bit float波形
    • 2 ループ(ホールド、キーオフ)、2 ループタイプ(通常、ピンポン)
    • 2 ADSR EG(ゲイン、ピッチ)
    • 2 LFO(三角波、EGを変調)
  • OSC type2: 128音PCM - ドラムチャンネルや効果音に使用
    • (ピッチ固定、ワンショット)
  • 2Dまたは3Dポジショニング
  • 3 エフェクト(リバーブ、LPF、HPF)
  • 4 エフェクトバス(ただし、エフェクトバス0はドライセンド専用)

今回のゲームには音声によるセリフは無いため、いわゆるストリーム音源は無い。合計は256音に及ぶが、実際のハードウェアでは32音程度が(CPUバジェット的な意味で)限界となる。
ADSRやLFOはソフト制御にしたかったが。。某エンジンでうまいこと再現する方法を見つけられなかったのでOSC1に関してはボイスは全て固定周波数で、リサンプルはソフトで行う。OSC2も最初の実装では48KHz固定。

OpenAL(Creative, OpenAL Soft)

プライマリな実装ターゲットとしてはOpenAL Softを選んだ。これはCreativeの初期の実装のforkで、LGPL。あと、今回の企画のためにワザワザCreativeのOpenAL実装も買った。。
OpenALは、その名の通りOpenGLに似たC APIで、習慣の多くはOpenGLに似せてある。

バーブは独自仕様(EAX)で、いわゆるI3DL2を元にした仕様。細かいパラメタの違いは後程。
歴史的事情で、OpenALはalBufferDataでデータをエンジンにコピーし、さらにsourceにalSourceQueueBuffersでqueueして再生するという形を取っている。というわけで、OpenALはこの業界では珍しく波形データをクライアントが自発的にpushする必要があるAPIとなっている。つまり、バッファの再生状態を定期的に監視する必要があるため、今回のような動的に波形が生成されるシンセサイザを実現する上ではかなり不都合となる。(このようなタイプのAPIに対応するために、OSC1とOSC2に分割している。)
OpenALには本来アップデートを同期的に行う方法が無いが、OpenAL Softに最近(2011)導入された拡張alDeferUpdatesSOFT、alProcessUpdatesSOFTを使用するとロックが実現できる。(OpenAL自体にはalcProcessContextとalcSuspendContextが有るが、これらは正確な挙動が規定されていないので役に立たない)
3DオーディオはOpenALの存在意義でもあるので、全てのオーディオソースは3次元位置を持つ。いわゆる2次元パンニングは存在しない。
OpenAL Softには、いわゆるバウンスを可能にする拡張alcLoopbackOpenDeviceSOFT、alcRenderSamplesSOFTもある。これらを使用することで、オーディオをオフラインで処理することもできる。
ちなみにAppleOpenALはCoreAudio上に実装されていて、拡張部分はこれらの実装と一切互換が無い。一部のApple拡張はOpenAL Softにも実装されている。

XAudio2(Windows, WinRT, XBox)

XAudio2はWindows標準のオーディオエンジンで、ある意味業界的なスタンダードを定義している。他のDirectXAPI同様COM風なのでC++ API

XAudio2は、というか普通のオーディオエンジンは、オーディオバッファの処理に関してコールバックを提供している。このため、ボイスの再生をエンジンに指示したらコールバック(IXAudio2VoiceCallback::OnBufferStart)が呼ばれるのを待ち、バッファの内容をセットするというpull型のプログラミングスタイルが採れる。
同時更新はOperation setという仕組みで実現される( http://msdn.microsoft.com/en-us/library/windows/desktop/ee415807%28v=vs.85%29.aspx )。予約した変更を複数回に分けてコミットできるのはユニークな機能だけど、あまり用途が思いつかない。。
3DオーディオはXAudio2においては一級市民でない。X3DAudioと呼ばれる、パラメタ計算用のヘルパが提供される( http://msdn.microsoft.com/en-us/library/windows/desktop/ee415717%28v=vs.85%29.aspx )。X3DAudioで計算したパラメタはユーザが自分でセットする必要がある。DirectSound3D時代はOpenALのようにサウンドバッファ自体に3次元位置を設定できたのから比べると大分サポートは後退している。
同期処理モードは存在しない。エンジンのインスタンスを作成するときにCPUアフィニティは設定できるが、それ以上のコントロールは提供していない。
XAudio2の微妙なポイントは、DirectX SDKに付属する2.7と、Windows8 SDKに付属する2.8でAPIに微妙に互換性が無い点。両対応のコードを書くことはそれほど難しくないが、事実上Windows7Windows8で違うバイナリになってしまうのはちょっと。。

FMOD

現在のFMODは高レベルAPIである"Studio API"と低レベルAPIである"Low Level API"に分かれている。Studio APIは、FMOD Studioで作成したサウンドバンクを再生するためのAPIで、今回はLow Level APIを使用することになる。C/C++両方のAPIが提供されている。
サウンドのストリーミングは普通のコールバックで行われる。リンクバッファをFMODに管理させてクライアントコードはlock/unlockを使用して書き込みを行う方式を採ることもできる。
3Dオーディオの更新には明示的にFMOD_System_Update()を呼ぶ必要がある。また、Updateは.wav writerのためのサンプル生成等にも使用される。2Dオーディオと3Dオーディオの両方を同時に使用できる。ミキシングスレッドをUpdateで駆動することもできる(FMOD_INIT_MIX_FROM_UPDATE)が、純粋なシングルスレッドモードは用意していないように見える。

irrKlang

irrKlangは商用のサウンドエンジンで、非営利利用は無料となっている。C++ API

APIはかなりハイレベルで、あまりこの手のものを実装するのに向いていない。
エフェクトはDirectoSoundのものDSOがそのまま使用される。ということはWindows以外ではエフェクトは実装されていない..?
スタティックな波形はISoundEngine::addSoundSourceFromPCMDataで読ませることができるが、動的な波形のための良い考察は無いので誠に遺憾ながら見送り。

CRI ADX2 LE

ADX2は高レベルAPIである"AtomEx"と低レベルAPIである"Atom"がある(サウンドレンダラをAtomと呼ぶらしい)。しかし、ADX2 LEでは低レベルAPIであるAtomは使用できないので、この手のものを実装するのには向いていない。

ADX2 LEは基本的にCRI Atom Craftでオーサリングされたサウンドバンクのランタイムとしての機能のみが提供されているが、無音のサウンドを登録してcriAtomExPlayer_SetFilterCallbackで波形を置き換えれば原理的には自由波形を流せることに気付いたので努力してみるのも良いかもしれない。
ADX2はスレッド統合の選択肢をかなりリッチに提供している:

  • CRIATOMEX_THREAD_MODEL_MULTI - 全てライブラリ任せ
  • CRIATOMEX_THREAD_MODEL_MULTI_USER_DRIVEN - ミキシングスレッドの作成はライブラリ任せにするが、処理の起動タイミングはユーザが制御するモード
  • CRIATOMEX_THREAD_MODEL_USER_MULTI - ステートの更新は外部スレッドから行えるが、処理はユーザのスレッドで行うモード
  • CRIATOMEX_THREAD_MODEL_SINGLE - 排他処理のないシングルスレッドモード

しかし、XAudioやOpenALと異なり、criAtomEx_ExecuteMainはどのモードでも定期的に呼び出す必要が有る。
同期更新はcriAtomEx_Lock/criAtomEx_Unlockのペアで行うことができる。

Wwise

WwiseはADX2と同様に、専用のオーサリングソフトで作成したサウンドバンクのランタイムとしての性質が濃いが、マイク等の入力を想定した入力ソースプラグインを記述することができるので、今回の目的に使用することができそう。
Wwiseにはシングルスレッドのモードが存在しない。プライオリティやアフィニティマスク、スタックサイズを設定することはできるが、ユーザが処理タイミングを図ることはできない。
イベントのトリガー等の処理は AK::SoundEngine::RenderAudio() が呼び出されたタイミングで行われる。というわけで、イベントは常に同期更新される。