32:効果音の同時再生(2)

 効果音同時再生についての実装方法はわかりました。しかし、この状態ではIDirectSound8が解放されると同時に再生がストップしてしまうため、クラスや関数内でバッファを作成し効果音を再生すると、再生が完了する前にクラスが解放されてしまい、ブツ音ばかりが聞こえてくる可能性もあります。そこで今回は、効果音の再生終了とともに自分自身をdeleteさせるプログラムを作成することにしましょう。
 効果音が終了されたことを知るには、IDirectSoundNotifyを使います。IDirectSoundNotifyインターフェイスを作成したら、SetNotificationPositions()によって、イベントを発生させるタイミングを登録します。複数のタイミングを登録することも可能です。CSoundクラスでは、publicのEnableDeleteByEnd()が手動で呼び出されたときに作成することにします。クラスの作成・解放時における処理も忘れずに。
class CSound : public CGameObject
{
private:
    /* 省略 */

    // 再生終了を関知
    LPDIRECTSOUNDNOTIFY pDSNotify;
    HANDLE hEvent;
public:
    /* 省略 */

    void EnableDeleteByEnd();
};
void CSound::Reset()
{
    pDSBuffer = NULL;
    pDSNotify = NULL;
    hEvent = NULL;
}

CSound::~CSound()
{
    if(hEvent) CloseHandle(hEvent);
    RELEASE(pDSNotify);
    RELEASE(pDSBuffer);
}

void CSound::EnableDeleteByEnd()
{
    if(pDSBuffer == NULL) return;

    // IDirectSoundNotifyの作成
    HRESULT hr = pDSBuffer->QueryInterface
        (IID_IDirectSoundNotify, (LPVOID*)&pDSNotify);
    
    // 通知イベントに使用するハンドルの作成
    hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    // イベント発生に関する情報を作成
    DSBPOSITIONNOTIFY pn;
    pn.dwOffset = DSBPN_OFFSETSTOP;    // 終了まで再生
    pn.hEventNotify = hEvent;        // ハンドラを関連づける

    // イベントデータを登録
    pDSNotify->SetNotificationPositions(1, &pn);
}
 WaitForMultipleObjects()の引数で関連づけたイベントハンドラを登録したうえでこの関数を呼び出すと、対象のイベント(ここでは再生終了)が発生するまで待機状態となります。つまり、それまではプログラムが停止してしまうので、この処理は別スレッドで行わなくてはいけません。
class DECLSPEC CSound : public CGameObject
{
private:
    /* 省略 */
    
    // 新規スレッドに用いる関数
    static DWORD WINAPI NotifyFunction(LPVOID param);
};
// 別スレッドに渡すデータを格納する構造体
struct PlayThreadData
{
    HANDLE hEvent;
    CSound *soundobj;
};

void CSound::Play()
{
    if(pDSBuffer){
        pDSBuffer->SetCurrentPosition(0);
        pDSBuffer->Play(0, 0, 0);

        if(pDSNotify){
            // 再生終了を監視するスレッドを立ち上げる
            PlayThreadData *data = new PlayThreadData;
            data->hEvent = hEvent;
            data->soundobj = this;
            DWORD threadID;
            CreateThread(NULL, 0, CSound::NotifyFunction, data, 0, &threadID);
        }
    }
}

DWORD CSound::NotifyFunction(LPVOID param)
{
    PlayThreadData *data = (PlayThreadData*)param;

    // 再生終了通知が来るまで待機
    WaitForMultipleObjects(1, &data->hEvent, FALSE, INFINITE);

    // 終了したら対象のオブジェクトを削除
    delete data->soundobj;

    delete data;

    return 0L;
}
 使い方の例を以下に表記します。これでCSoundクラスを作成する元となったクラスを削除しても音声が途切れることはなくなります。
CSound *se = new CSound(*(CSound*)FindItemBox("explode1"));
se->EnableDeleteByEnd();
se->Play();