XAPOFXで出来合いのエフェクトを使う

たまにはXAudio2とその関係のお話でもします。

XAPO? XAPOFX?

というか今までXAPOが何か、という話を真面目にしてなかったような気がするのでそこから話します。XAPOとはXAudio2と連携して利用するオーディオ処理オブジェクトを作成する手段を提供するAPIです。つまるところオーディオ処理用APIです。XAudio2はこのXAPOをサポートしており、XAPOインターフェイス(IXAPO,IXAPOParameters:前者はパラメータが無い、後者はパラメータが必要となる)をボイスに接続するとボイスが処理したデータをこのインターフェイスへ渡してくれ、XAPOインターフェイスが処理したオーディオデータをボイスが再生する、といった具合です。フィルタをかけるような感じですかね。XAPOインターフェイスはCOMオブジェクトになっています。
XAPOをでやる事は少し多いですが、基本的にはオーディオデータをvoidポインタとして受け取り、このデータを直接編集するだけです。(他にはロック状態アンロック状態持ったり、オーディオフォーマットが処理できるものかどうかの問い合わせ処理をしたり…など。)前に話しましたが、XAudio2はすべて32bit浮動小数点数データとしてオーディオデータを処理しているので、このvoidポインタはfloatに変換できます。*1
まあいろいろやる事があるのですが、XAPOでは実際にオーディオデータを処理するメソッド以外を実装したCXAPOBase,CXAPOParametersBaseというクラスが用意されているので簡単に実装はできます。(注:DirectX SDK June 2010以下のバージョンではCRTがDLL版でないと、この2つのクラスは正しく動かないようです。スタティック版に存在しない関数に依存しているのが原因のようです。)
実装するのが面倒なので今回はXAPOFXを使ってみましょう。
XAPOFXはXAPOインターフェイスを用いて実装した出来合いのエフェクトクラスを提供しているエフェクトライブラリです。DirectSoundFXと同じような感じです。まだ4個しかエフェクトがありません。現在の最新版であるDirectX SDK June 2010ではEcho, Equalizer, Volume limiter, Reverbの4つです。ReverbはXAudio2エフェクトと言った形であるのですが、あれとは別物です。あちらの場合パラメータは10個以上ありますが、XAPOFXの実装は2個です。
という訳で、今回はXAPOFX Reverbを使ってみましょう。

XAPOFX Reverbを使ってみる

XAPOFXのエフェクトを作成するのは非常に簡単です。CreateFX関数を使います。宣言はXAPOFX.h,実装はXAPOFX.libにあります。クラスIDを渡して作成してもらいます。IUnknownへのポインタで受け取る必要があるので注意。作成時の参照カウントは1です。

#pragma comment(lib, "XAPOFX.lib")

#include <XAPOFX.h>

::IUnknown* reverb;
::CreateFX(__uuidof(::FXReverb),&reverb);
assert(reverb);

後はボイスに接続するだけです。IXAudio2Voice::SetEffectChainメソッドを使用します。XAUDIO2_EFFECT_CHAINという構造体をメソッドに渡しますが、この構造体にはXAUDIO2_EFFECT_DESCRIPTORという構造体へのポインタが必要で、エフェクトの情報はこっちの構造体に記述します。

::IXAudio2SourceVoice* create_voice();

::IXAudio2SourceVoice* voice = create_voice();

::XAUDIO2_EFFECT_DESCRIPTOR desc;
desc.InitialState  = TRUE;
desc.OutputChannel = 2;      // 出力チャンネル数
desc.pEffect       = reverb; // エフェクトへのポインタ

::XAUDIO2_EFFECT_CHAIN chain;
chain.pEffectDescriptors = &desc; // Descriptorへのポインタ、複数個接続する場合は配列の先頭
chain.EffectCount        = 1;     // Descriptorがいくつあるのか

voice->SetEffectChain(&chain);

これで無事接続に成功し、エフェクトの参照カウントは1つ増えます。ここでこのエフェクトを使いまわす必要がない(ボイスを削除した後エフェクトを他のボイスへ接続しない)場合、IUnknown::Releaseメソッドを呼び出す事で参照カウントを1にし、エフェクトの所有権をボイスへ渡すことができます。こうするとボイスがエフェクトの削除権も有することになるので、ボイスの削除と同時にエフェクトも削除されるようになります。この方が便利なので、使いまわしを考えない場合はReleaseすることをお勧めします。

reverb->Release();

後はエフェクトに必要なパラメータを渡して初期化しなければいけません。FXReverbのパラメータはFXREVERB_PAREMTERSです。IXAudio2Voice::SetEffectParametersで初期化パラメータを渡します。第1引数はエフェクトの位置です。複数個ならんでいた場合Descriptorの順番にエフェクトが並んでいるのでこの指定が必要になります。1個しか指定していない場合は0で良いです。

::FXREVERB_PARAMETERS param;
param.Diffusion = FXREVERB_DEFAULT_DUFFUSION; // 散布量(拡散量?)
param.RoomSize  = FXREVERB_DEFAULT_ROOMSIZE;  // 音が鳴っている施設の大きさを示す

voice->SetEffectParameters(0,&param,sizeof(FXREVERB_PARAMETERS));

後は接続したボイスにオーディオデータをどんどん流し込むだけです。ボイスが受け取ったデータを必要な整形をした後FXReverbに渡し、FXReverbは受け取ったデータとパラメータから良い感じにオーディオデータを変形してくれます。XAPOFXは所詮出来合いのエフェクトですからあんまり面白くないかもしれませんが、簡単に利用したい場合は結構重宝しそうです。まあまだ4個しかないので自分で実装しなきゃいけないのは山とありますが…個人的にはフェードぐらいは用意してもらいたいなあ、と思う次第です。そのうちXAPOインターフェイスから自分で実装する記事を書きたいですね。

*1:voidポインタなのは恐らくfloat型が32bitサイズでは無い場合を考えている?