krustf の雑記

2011-02-28

XAudio2のサンプルコードを載せるリポジトリ作りました

たまに書いたと思ったら宣伝かよ!というのは置いといて作りました。

krustf/XAudio2-Sample ? GitHub

XAudio2のサンプルコード集です。XAudio2と連携するAPIを使用したサンプルコードも載せる予定です(XAPO,XAPOFX,X3DAudio...)。

このリポジトリに誰か良いサンプル追加してくれませんかね。といっても僕はgitの使い方が分からないので(ごめんなさい)どうやればいいなんてことは言えないんですが。なんかcloneして改編したのをforkした後pull-requestでいいんですか?本当によくわかってない誰か分かりやすいサイトあったら教えてください(ちゃんと調べます)。

後はなんかこういうサンプルどうよ?というのがTwitter辺りで言ってもらえるとできる/できない込みで取り込む所存です。まあXAudio2の布教活動の一環です。と言うことで1つお願いします。

2011-02-17

XAPO用の自作クラスは別にCLSIDは不要と言えば不要にできる

XAPOクラスにはCLSID(COMクラスID)が必要です。なのでVisualStudioの機能が使える人はそれで生成してVS用の特殊なプロパティを書いてCLSIDとの対応付けをして…と面倒この上作業を必要とします。がしかし、これは実は必要ないかもしれません。

というのもCLSIDはCoCreateInstanceで必要だったり、IUnknown::QueryInterfaceで利用しない限り必要でないからです。そしてXAudio2のボイスはQueryInterfaceにはIUnknown, IXAPO, IXAPOParametersの取得しか要請しないため、作成したプログラマとユーザが要請しない限り専用のCLSIDは不要である、ということにできます。

といってもXAPO_REGISTRATION_PROPERTIES構造体にはCLSIDを登録する必要があります。面倒なのでCLSID()で初期化すれば良いと思います。自分でQueryInterfaceを使って自作XAPOクラスへ変換しようとしないのが確実であるならば、この方法はオススメだと思います。

[余談というか本題] XAPOクラスを最初から自作とかしんどい

[追記 2011/02/17] ヘッダ名はSampleAPOBaseなのですが、クラス名はCSampleXAPOBaseでした。治しました。

というのは、XAPO_REGISTRATION_PROPERTIESとかを自前で用意したりだとか、無駄にメソッドが多いだとかで辛いです。とは言っても公式で用意されているBaseクラスは/MT, /MTdでコンパイルできない*1のがあったりというのは置いといて、そのBaseクラスをさらにラップしたクラスがありますのでこれをオススメします。

DirectX SDKに付属のサンプルでXAudio2CustomAPOというサンプルにはSampleAPOBase.hというヘッダがあり、CSampleXAPOBaseクラステンプレートというIXAPOParamatersBaseを継承したパラメータを持つエフェクトクラスの高レベルラッパがあります。IXAPOParamatersBaseが要するすべてのメソッドの単純なオーバーライドがされているのでこっちはCSampleXAPOBaseが要求するDoProcessメソッドをオーバーライドするだけです。

さて、自作XAPOクラスをこいつで作ります。必要なパラメータとクラスそのものをテンプレートに渡します。CRTPですね。

#include "SampleAPOBase.h"

// 適当なパラメータ
struct hoge_parameter
{
    float gain;
};

// 継承して作成
class
__declspec( uuid("{00000000-0000-0000-0000-000000000000}")) // 面倒だけどCLSIDを要するので適当に付けとく
hoge_effects : public CSampleXAPOBase<hoge_effects,hoge_parameter>
{
protected:
    void DoProcess(
        const hoge_parameter& params,
        FLOAT32* __restrict pData,
        UINT32 cFrames,
        UINT32 cChannels
    ){
        // pDataはcFrame * cChannels * 4Byte分のオーディオデータが入っている
        // XAudio2は内部ではオーディオデータはすべて32bit(4Byte)浮動小数点数で処理されている
        for( UINT32 i = 0; i < cFrames * cChannels; ++i )
        {
            pData[i] *= params.gain;
        }
    }
};

// 作成時はCreateInstanceを使うと良い?
hoge_effects* p;
if( FAILED(hoge_effects::CreateInstance(0,0,&p)) )
{
    throw std::runtime_error("effect create failed.");
}

※2011/02/18 コメントから、CreateInstanceのメソッド名を完全に間違えてました。

CSampleXAPOBaseが内部で__uuidof使ってるので適当にCLSIDはつけておきます。面倒な方はCSampleXAPOBaseのXAPO_REGISTRATION_PROPERTIES構造体の変数の__uuidofを消してCLSID()に書き換えれば動くはずです。

__restrictは最適化のためのキーワードです。CreateInstanceメソッドはstaticメソッド引数はIXAPO::Initializeメソッドに直接引き渡される引数と、作成したインスタンスを格納するポインタです。ちなみにIXAPO::Initializeはこれを書いてる時点では特に何もしてなかったはずです。そのうち拡張するかもしれないのでこのようなメソッドがある、とMSDNには書かれていました。

後は普通にボイスに渡してパラメータ初期化したりすれば良いだけなので簡単ですね。簡単すぎです。これがMTコンパイルでも通れば…。MSDNに報告してるんですが去年報告して結局改善されなかったので皆さんもMSDNに報告してくれないでしょうか。お願いいたします。

*1:詳しくは僕のツイッター見たりだとかググってみればどこかにあります

2011-01-09

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サイズでは無い場合を考えている?

2010-09-13

XAPOのインターフェイスを自動生成したい

というのも、XAPOの処理は基本的なものであればXAPO::Processをオーバーライドしてちょっとした操作をするだけだからです。

それ以外のメソッドはCXAPOBaseなどが用意する基本的な処理だけで十分事足ります。


// ゲイン値を半分にするだけ
class gain_cut : public CXAPOBase
{
public:
    void Process( ... )
    {
        ...

        for( UINT32 i = 0 ; i < Frame ; ++i )
           Buffer[i] *= 0.5f;

        ...
    }
};

コンストラクタなどもCXAPOBaseなど実装を用意すればほぼ一緒になりますし、重複箇所だらけです。

だとするならば、関数オブジェクトや関数を渡してインターフェイスは自動的に作成してくれると嬉しい訳です。


// 関数オブジェクトなどに応じて自動生成されるクラステンプレート
template< class Func >
class xapo_auto : public CXAPOBase { ... };

template< class Func >
IXAPO* create_xapo()
{
   return new detail::xapo_auto<Func>();
}

// ゲイン値を半分にする関数オブジェクト
struct gain_cut
{
    void operator()( ... ) { ... };
};

IXAPO* p = create_xapo<gain_cut>();

といったような。とりあえずfunctionとして渡せるものならば全部渡してやりたいですね。

ただ、制約が色々と。例えば関数の引数は固定になっちゃったりとか、パラメータを持つ(IXAPOParamtersを継承する)ものだったら新しいパラメータは返却するのか、それとも参照としておくのか。クラスIDはどうするのか、といったようなのもありますね。

いろいろ問題はあるのですが、関数を渡してXAudio2で使えるエフェクトクラスを自動生成できるのは魅力なのでちょっと挑戦してみたいですね。

※良く分からないコードになってので修正。失礼しました…。