ゆとりーなの日記

日記的な事を書いて行くと思はれる

第6回〜XAudio2の初期化〜

誰が何と言おうとゲームはBGMが9割なので、音声を鳴らすためにXAudio2の初期化を行います。何故にDirectSoundでないの?と思う人もいるかもしれませんが、Microsoft的にはもうXAudio2を使ってDirectSoundは使うなな空気を出しているのでおとなしく従がっときましょう。
取り敢えず必要な設定とヘッダをプリコンパイル済みヘッダに追加しておきましょう。

#pragma once
#include <cassert>
#include <stdexcept>
#pragma warning (disable: 4996)
#include <memory>
#pragma warning (default: 4996)
#include <string>
#define _WIN32_WINNT 0x0501
#define _WIN32_DCOM // 追加
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <xaudio2.h> // 追加
#include <boost/noncopyable.hpp>
#include <boost/intrusive_ptr.hpp>
#pragma warning (disable: 4512)
#include <boost/signals2.hpp>
#pragma warning (default: 4512)
#include <boost/flyweight.hpp>
#include <boost/flyweight/key_value.hpp>

まずはXAudio2を管理するSoundDeviceクラスを作ります。

// souddevice.h
#pragma once
#include "deleter.h"
#include "cominiter.h"

class SoundDevice : private boost::noncopyable {
private:
    friend class Application;
    SoundDevice();
    ComIniter com_init_;
    boost::intrusive_ptr<IXAudio2> xaudio_;
    std::unique_ptr<IXAudio2MasteringVoice, MasteringVoiceDeleter> mastering_voice_;
};

設計方針としてはGraphicDeviceクラスとさほど変わりません。

// sounddevice.cc
#include "sounddevice.h"

namespace {
IXAudio2 *createXAudio() {
#ifndef NDEBUG
    const UINT32 flag(XAUDIO2_DEBUG_ENGINE);
#else
    const UINT32 flag(0);
#endif
    IXAudio2 *xaudio(nullptr);
    // IXAudio2*を得る
    if (FAILED(XAudio2Create(&xaudio, flag))) {
        throw std::runtime_error("XAudio2の初期化に失敗");
    }
    return xaudio;
}

IXAudio2MasteringVoice *createMasteringVoice(boost::intrusive_ptr<IXAudio2> xaudio) {
    assert(xaudio.get());
    IXAudio2MasteringVoice *mastering_voice(nullptr);
    // IXAudio2MasteringVoice*を得る
    if (FAILED(xaudio->CreateMasteringVoice(&mastering_voice))) {
        throw std::runtime_error("マスターボイスの作成に失敗");
    }
    return mastering_voice;
}
}

SoundDevice::SoundDevice() : com_init_(), xaudio_(createXAudio()), mastering_voice_(createMasteringVoice(xaudio_)) {
}

XAudio2の初期化にはIXAudio2*をXAudio2Create関数を使って得て、IXAudio2::CreateMasteringVoiceメソッドを使ってIXAudio2MasteringVoice*を得る必要があります。いつも通りスマポを円滑に生成するための関数を作っておきます。まあ上の様な感じで初期化しておけば特に問題はないです。IXAudio2*を得るところではデバッグビルドかどうかで生成フラグが変わるので、それの切り替えを行っています。
IXAudio2*は普通のCOMと同じ様な感じなので、いままで通りboost::intrusive_ptrで管理すればいいのですが、IXAudio2MasteringVoice*は例外で、後始末に使うメソッドがReleaseではなくDestroyVoiceであること、また参照カウンタを実装している気配がないのでstd::unique_ptrで管理することにします。また、デフォルトのdeleteが呼ばれるとまずいのでstd::unique_ptrに渡すDestroyVoiceメソッドを実行するカスタムデリータを"deleter.h"に追加します。

// "deleter.h"
struct MasteringVoiceDeleter {
    void operator ()(IXAudio2MasteringVoice *ptr) {
        assert(ptr);
        ptr->DestroyVoice();
    }
};

MasteringVoiceDeleterをstd::unique_ptrのテンプレート第二引数に渡してやれば、インスタンスが落ちるときに自動的にこの子のoperator ()を呼んでくれます。
あとXAudio2を使うには、COMの初期化を行わなければいけません。COMの初期化にはCoInitializeEx関数、後始末にはCoUninitialize関数を使います。こいつらをSoundDeviceのコンストラクタとデストラクタで扱ってもいいのですが、コンストラクタで例外を投げまくる仕様上、それは危険なのでかんたんなRAIIオブジェクトでラップしてあげます。

// cominiter.h
#pragma once

class ComIniter {
public:
    ComIniter();
    ~ComIniter();
};

// cominiter.cc
#include "cominiter.h"

ComIniter::ComIniter() {
    if (CoInitializeEx(nullptr, COINIT_MULTITHREADED)) {
        throw std::runtime_error("COMの初期化に失敗しました");
    }
}

ComIniter::~ComIniter() {
    CoUninitialize();
}

この子をSoundDeviceの最初のメンバ変数にしてやれば、COMの初期化、後始末は完了です。
最後にSoundDeviceクラスをApplicationクラスに組み込みます。

// application.h
#pragma once

class GraphicDevice;
class DrawObject;
class SoundDevice;

class Application : private boost::noncopyable {
public:
    // インターフェイス
protected:
    // protectedインターフェイス
    std::unique_ptr<SoundDevice> sound_device_;
private:
    // 内部実装関係
};

// application.cc
Application::Application(const std::string &caption, int client_width, int client_height, bool windowed) : graphic_device_(), sound_device_(new SoundDevice()), window_handle_(nullptr), caption_(caption), client_width_(client_width), client_height_(client_height), windowed_(windowed) {
    init();
}

初期化に引数を取らないので遅延初期化とか必要ないのですが、GraphicDeviceクラスの扱いと合わせるためにstd::unique_ptrにしておきました。これで音声再生のための初期化準備は完了です。