6-3 DirectSoundでサウンドを再生

ここではDirectSoundの概要から、DirectSoundを使って
実際にサウンドの再生を行うまでのプログラムについて説明します。

■DirectSoundの概要

概要といってもDirectSoundの決まりごとを説明するだけですが、
これを説明しておくとDirectSoundがどういったものかが見えてくるかと思います。

まずDirectSoundはWindowsに組み込まれているCOMで出来たライブラリです。
ただし、バリバリのCOMプログラムを行う必要は無く、
関連するものは全て関数が用意されているため特に難しいということはありません。

次にDirectSoundはリアルタイムに音声を合成出来ますが、これを実現するためには
実際のサウンドデータとは別に合成用のサウンドバッファを必要とします。
これをプライマリサウンドバッファといい、DirectSound対応のサウンドカードでは
ハードウェアの方に取られたりします。

もしDirectSoundに対応していない場合でもバッファは通常のメモリ上に確保されるため、
DirectSound対応のハードウェアが無くても問題無くDirectSoundが使えます。

プライマリバッファはDirectSoundが管理しており、再生や合成が必要になった時に自動的に処理が行われるため、
プログラマーはこのバッファを作成するのみでそれ以上の処理を行う必要はありません。
※ノートPCなどでは何も再生されていない時は省電力機能によりデバイスがスリープ状態となることがあります。
 ここで再生を行おうとするとスリープが解除されるまで音が出なかったり遅れたりすることがあります。
 この場合は無音のWAVをループ再生しておくことで、スリープにしないように制御出来ます

通常のサウンドデータを読み込むサウンドバッファをセカンダリバッファと言い、
サウンドファイルごとに複数作成することが出来ます。

サウンドバッファは基本的に「再生」「停止」「シーク」を行い操作します。
再生中に停止した場合で次に再生を行ったときは、自動的に先頭には戻らず停止した位置から再生が再開されます。

同じくサウンドの再生が終了しても自動的に先頭に戻ったりはしないため(ループ再生以外)、
きちんとシーク操作を行っておかないと、音が途中から鳴ったり音が何も出ないといった問題が発生します。

さらに再生中にシークを行った場合、停止はされずにそのままシーク先から即座に再生が行われます。

サウンドバッファは再生時に特殊な制御を行うことが出来ます。
例えば再生速度を速くしたり(ピッチ)、音量の調整を行う(ボリューム)といったことが可能です。
ただし、これを可能にするためにはサウンドバッファの作成時に、
使用する機能のフラグをセットしなければなりません。
※フラグの組み合わせによってはサウンドバッファが作成出来ないことがあります

停止中など再生が行われていない状態でも制御によるパラメータの変更が可能で、
次の制御を行うまでは最後に設定したパラメータが保持されます。


■DirectSoundの初期化

概要が分かったところでここでは実際にDirectSoundを使って音を鳴らしてみます。

DirectSoundはDirect3Dと同様に最低1つのウインドウが必要です。
ここではDirect3Dのサンプルで行ったようにCWindowクラスを使ってウインドウ処理を行います。
この辺りはすでに何度も説明しているためソースコードは前のものを確認してください。

まずはDirectSoundを使うための準備をします。

#include "CWindow.h"
#include <dsound.h>

CWindow                 win;             // ウインドウクラス
LPDIRECTSOUND8          lpDS = NULL;     // DirectSound8

DirectSoundを使用するにはdsound.hが必要なので、
ここではまず最初にこのヘッダをインクルードしています。

次にDirectSoundを管理するためのIDirectSound8のポインタをグローバル変数として定義します。
なお、このIDirectSound8のポインタはLPDIRECTSOUND8としても定義されており、
Microsoftのサンプルなどではこちらの方を使用しているので、
同じようにここではLPDIRECTSOUND8をグローバル変数として定義しています。

DirectSoundを使用したプログラムはdsound.libを一緒にリンクする必要があります。
またここではWAVファイルを扱うAPIを使用するため、winmm.libも必要になります。
libファイルはプロジェクトの設定で追加することも出来ますが(追加方法はこちら)、
ここではソースコード上に埋め込んでみます。

#include "CWindow.h"
#include <dsound.h>

// ライブラリリンク
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "winmm.lib")

CWindow                 win;             // ウインドウクラス
LPDIRECTSOUND8          lpDS = NULL;     // DirectSound8

※この機能はVCの機能であり、それ以外のコンパイラではエラーになるか無視されます

次にDirectSound8を実際に構築します。
ここではメインの中に入れずに関数化したものを掲載します。

/////////////////////////////////////////////////////////
// 初期化
/////////////////////////////////////////////////////////
BOOL Init( void )
{
    HRESULT ret;

    // COMの初期化
    CoInitialize( NULL );

    // DirectSound8を作成
    ret = DirectSoundCreate8( NULL,&lpDS,NULL );
    if( FAILED(ret) ) {
        DEBUG( "サウンドオブジェクト作成失敗\n" );
        return FALSE;
    }

    // 強調モード
    ret = lpDS->SetCooperativeLevel( win.hWnd,DSSCL_EXCLUSIVE|DSSCL_PRIORITY );
    if( FAILED(ret) ) {
        DEBUG( "強調レベル設定失敗\n" );
        return FALSE;
    }

    return TRUE;
}


ここではまず最初にCOMの初期化を行っています。
本来はDirectSoundはCOMを意識しなくても使用出来るはずですが、
何故かCOMを初期化しておかないとウィンドウの削除時にアプリがハングアップしてしまいます。
このためDirectSoundを使用するときは念のためCOMの初期化をするようにした方が良いでしょう。
※COMの初期化と終了は必ず1対1で呼び出す必要があり、終了時には確実のCOMの終了を呼び出すようにしなければなりません

次にDirectSoundCreate8関数を使用してメインとなるDirectSound8のオブジェクトを作成します。
関数が成功すると第2引数にLPDIRECTSOUND8のポインタが返ります。

ちなみに第1引数には本来は使用するハードウェアのGUIDを指定しなければなりませんが、
NULLにすることでデフォルトのサウンドデバイスが使用されます。
また第3引数は常にNULLを指定することになっているのでその通りにします。

関数が成功したらこれ以降はこのDirectSound8のポインタを経由してDirectSoundを操作します。
なお、このポインタはCOMオブジェクトのため終了時にReleaseが必要ですが、
終了処理については下の方にまとめています。


次に協調モードを設定しています。
協調モードとはWindowsで他のアプリとの整合性を取るためのもので、
例えば自分のプログラムからフォーカスが移った場合は、サウンドを鳴らさないようにするといった設定を行います。

1番目の引数は対象のウインドウハンドルなので、ここではCWindowクラス内のウインドウハンドルを指定しています。
そのためにはInit関数を呼び出す前にウィンドウを作成しておく必要があります。

2番目の引数は協調モードのフラグの組み合わせですが、DSSCL_EXCLUSIVEとはフォーカスが移ったら鳴らさないようにするのと、
DSSCL_PRIORITYとは優先レベルにすることでプライマリバッファのフォーマットを指定出来るようにしていますが、
DirectSound8ではDSSCL_EXCLUSIVEフラグにDSSCL_PRIORITYフラグの機能も含まれるようになったため、
現状はDSSCL_EXCLUSIVEのみでも問題ありません。
※それ以外のフラグについてはこちら

これでDirectSoundの初期化は完了ですがこれだけではまだ音は鳴らせません。
次に音声を合成するためのバッファをこのDirectSoundに設定します。

■プライマリサウンドバッファ

DirectSoundの概要で合成を行うためのバッファとして、
プライマリサウンドバッファが必要と説明しましたがここではこれを作成してみます。

なお、このバッファは通常のサウンドバッファを作成するのとほぼ同じやり方で構築しますが、
違いはフラグにプライマリバッファであるということを指示するだけです。

まずはこのプライマリサウンドバッファを管理するためのポインタをグローバルに定義します。

// グローバル変数
CWindow             win;                    // ウインドウクラス
LPDIRECTSOUND8      lpDS = NULL;            // DirectSound8
LPDIRECTSOUNDBUFFER lpPrimary = NULL;       // プライマリサウンドバッファ


次にプライマリサウンドバッファを作成しますが、以下はこのバッファの作成を関数にしてみたものです。

/////////////////////////////////////////////////////////
// プライマリサウンドバッファの作成
/////////////////////////////////////////////////////////
BOOL CreatePrimaryBuffer( void )
{
    HRESULT ret;
    WAVEFORMATEX wf;

    // プライマリサウンドバッファの作成
    DSBUFFERDESC dsdesc;
    ZeroMemory( &dsdesc,sizeof(DSBUFFERDESC) );
    dsdesc.dwSize           = sizeof( DSBUFFERDESC );
    dsdesc.dwFlags          = DSBCAPS_PRIMARYBUFFER;
    dsdesc.dwBufferBytes    = 0;
    dsdesc.lpwfxFormat      = NULL;
    ret = lpDS->CreateSoundBuffer( &dsdesc,&lpPrimary,NULL );
    if( FAILED(ret) ) {
        DEBUG( "プライマリサウンドバッファ作成失敗\n" );
        return FALSE;
    }

    // プライマリバッファのステータスを決定
    wf.cbSize = sizeof( WAVEFORMATEX );
    wf.wFormatTag           = WAVE_FORMAT_PCM;
    wf.nChannels            = 2;
    wf.nSamplesPerSec       = 44100;
    wf.wBitsPerSample       = 16;
    wf.nBlockAlign          = wf.nChannels * wf.wBitsPerSample / 8;
    wf.nAvgBytesPerSec      = wf.nSamplesPerSec * wf.nBlockAlign;
    ret = lpPrimary->SetFormat( &wf );
    if( FAILED(ret) ) {
        DEBUG( "プライマリバッファのステータス失敗\n" );
        return FALSE;
    }

    return TRUE;
}


DSBUFFERDESC構造体はサウンドバッファを作成するための構造体ですが、
これに値を入れることで作成するバッファの情報を定義します。

なお、サウンドバッファを作成するには通常はバッファサイズを指定しなければなりませんが、
プライマリサウンドバッファを作成する場合は例外的に必ず0を指定しなければなりません。
これによりDirectSoundがサウンドバッファのサイズを自動的に設定します。

さらにここでは赤字で示したようにdsdesc.dwFlagsにはDSVCAPS_PRIMARYBUFFERを指定しています。
これでプライマリサウンドバッファであることを指示したことになります。

構造体の構築が完了したらこれをCreateSoundBufferに渡してプライマリバッファを作成します。
このプライマリバッファもIDirectSound8同様COMオブジェクトであるため、終了時にReleaseをする必要があります。


プライマリバッファが作成出来たら次はこのプライマリバッファのフォーマットを指定します。
フォーマットを指定するにはWAVEFORMATEX構造体に設定したい値を入れ、
プライマリサウンドバッファに対してSetFormatメソッドを実行します。

ここではCD音質「44Kの16bit、ステレオ」を設定していますが、
実際に音を再生する際にはこのフォーマットに合わせてミキシングされます。

なお、例えば音質の悪い「11KHzの8bit、モノラル」を設定した場合、ロードされた音源がいくら
44KHzだったとしても結果としてダウンサンプリングされた11KHzの音質で再生されることになります。
つまりここで設定した値で最終的に音が鳴るため、CPUのスペックを見て適切に値を入れるようにします。

なお、最近のOSで利用されているWDMドライバを使用したサウンドデバイスの場合、
SetFormatにてサウンドフォーマットを設定することは出来ません。
関数自体は呼び出すことは出来ますが、音質はドライバ側で設定されているものになります。


これで本当の意味でのDirectSoundの初期化が完了したことになるので、
あとは実際に鳴らしたいデータをロードして再生を行うことが出来ます。

■WAVからサウンドバッファを作成

ここでは実際に鳴らしたいサウンドファイル(WAV)からセカンダリサウンドバッファを作成するやり方を説明します。

以下はサウンドファイルからサウンドバッファを作成してそのポインタを返す関数のサンプルです。

/////////////////////////////////////////////////////////
// サウンドバッファの作成
/////////////////////////////////////////////////////////
BOOL CreateSoundBuffer( LPDIRECTSOUNDBUFFER *dsb,const char *file )
{
    HRESULT ret;
    MMCKINFO mSrcWaveFile;
    MMCKINFO mSrcWaveFmt;
    MMCKINFO mSrcWaveData;
    LPWAVEFORMATEX wf;

    // WAVファイルをロード
    HMMIO hSrc;
    hSrc = mmioOpenA( (LPSTR)file,NULL,MMIO_ALLOCBUF|MMIO_READ|MMIO_COMPAT );
    if( !hSrc ) {
        DEBUG( "WAVファイルロードエラー\n" );
        return FALSE;
    }

    // 'WAVE'チャンクチェック
    ZeroMemory( &mSrcWaveFile,sizeof(mSrcWaveFile) );
    ret = mmioDescend( hSrc,&mSrcWaveFile,NULL,MMIO_FINDRIFF );
    if( mSrcWaveFile.fccType!=mmioFOURCC('W','A','V','E') ) {
        DEBUG( "WAVEチャンクチェックエラー\n" );
        mmioClose( hSrc,0 );
        return FALSE;
    }

    // 'fmt 'チャンクチェック
    ZeroMemory( &mSrcWaveFmt,sizeof(mSrcWaveFmt) );
    ret = mmioDescend( hSrc,&mSrcWaveFmt,&mSrcWaveFile,MMIO_FINDCHUNK );
    if( mSrcWaveFmt.ckid!=mmioFOURCC('f','m','t',' ') ) {
        DEBUG( "fmt チャンクチェックエラー\n" );
        mmioClose( hSrc,0 );
        return FALSE;
    }

    // ヘッダサイズの計算
    int iSrcHeaderSize = mSrcWaveFmt.cksize;
    if( iSrcHeaderSize<sizeof(WAVEFORMATEX) )
        iSrcHeaderSize=sizeof(WAVEFORMATEX);

    // ヘッダメモリ確保
    wf = (LPWAVEFORMATEX)malloc( iSrcHeaderSize );
    if( !wf ) {
        DEBUG( "メモリ確保エラー\n" );
        mmioClose( hSrc,0 );
        return FALSE;
    }
    ZeroMemory( wf,iSrcHeaderSize );

    // WAVEフォーマットのロード
    ret = mmioRead( hSrc,(char*)wf,mSrcWaveFmt.cksize );
    if( FAILED(ret) ) {
        DEBUG( "WAVEフォーマットロードエラー\n" );
        free( wf );
        mmioClose( hSrc,0 );
        return FALSE;
    }
    DEBUG( "チャンネル数       = %d\n",wf->nChannels );
    DEBUG( "サンプリングレート = %d\n",wf->nSamplesPerSec );
    DEBUG( "ビットレート       = %d\n",wf->wBitsPerSample );


    // fmtチャンクに戻る
    mmioAscend( hSrc,&mSrcWaveFmt,0 );

    // dataチャンクを探す
    while(1) {
        // 検索
        ret = mmioDescend( hSrc,&mSrcWaveData,&mSrcWaveFile,0 );
        if( FAILED(ret) ) {
            DEBUG( "dataチャンクが見つからない\n" );
            free( wf );
            mmioClose( hSrc,0 );
            return FALSE;
        }
        if( mSrcWaveData.ckid==mmioStringToFOURCCA("data",0) )
            break;
        // 次のチャンクへ
        ret = mmioAscend( hSrc,&mSrcWaveData,0 );
    }
    DEBUG( "データサイズ       = %d\n",mSrcWaveData.cksize );


    // サウンドバッファの作成
    DSBUFFERDESC dsdesc;
    ZeroMemory( &dsdesc,sizeof(DSBUFFERDESC) );
    dsdesc.dwSize = sizeof( DSBUFFERDESC );
    dsdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STATIC | DSBCAPS_LOCDEFER;
    dsdesc.dwBufferBytes = mSrcWaveData.cksize;
    dsdesc.lpwfxFormat = wf;
    dsdesc.guid3DAlgorithm = DS3DALG_DEFAULT;
    ret = lpDS->CreateSoundBuffer( &dsdesc,dsb,NULL );
    if( FAILED(ret) ) {
        DEBUG( "サウンドバッファの作成エラー\n" );
        free( wf );
        mmioClose( hSrc,0 );
        return FALSE;
    }

    // ロック開始
    LPVOID pMem1,pMem2;
    DWORD dwSize1,dwSize2;
    ret = (*dsb)->Lock( 0,mSrcWaveData.cksize,&pMem1,&dwSize1,&pMem2,&dwSize2,0 );
    if( FAILED(ret) ) {
        DEBUG( "ロック失敗\n" );
        free( wf );
        mmioClose( hSrc,0 );
        return FALSE;
    }

    // データ書き込み
    mmioRead( hSrc,(char*)pMem1,dwSize1 );
    mmioRead( hSrc,(char*)pMem2,dwSize2 );

    // ロック解除
    (*dsb)->Unlock( pMem1,dwSize1,pMem2,dwSize2 );

    // ヘッダ用メモリを開放
    free( wf );

    // WAVを閉じる
    mmioClose( hSrc,0 );

    return TRUE;
}


この関数は引数で指定したWAVファイルをロードし、作成したセカンダリサウンドバッファを返すようになっていますが、
たくさんのサウンドバッファポインタを用意すれば、同時にたくさんのWAVファイルをロードさせることが出来ます。

サンプルではひとまずグローバル変数としてセカンダリバッファを1つだけ確保しています。

LPDIRECTSOUNDBUFFER lpSecondary = NULL;     // セカンダリサウンドバッファ


この関数ではWAVファイルを扱うためにWinAPIのMMIO関数を利用して、
指定されたWAVファイルをオープンします。

WAVファイルが開けたら次にこのWAVファイルのフォーマットなどを取得します。

WAVファイルはチャンクというまとまりで管理されており、
たとえばRIFFチャンクはファイル全体のヘッダ情報を管理する領域、
fmtチャンクはそのファイルのフォーマットを管理しています。

そしてdataチャンクには実際のサウンドデータが保存されている領域となりますが、
最終的にはこのサウンドデータをセカンダリサウンドバッファに転送しなければなりません。

ヘッダやサウンドフォーマットを取得出来たら最後にdataチャンクを探します。
なおdataチャンクには実データのサイズが含まれますが、このサイズがセカンダリサウンドバッファのサイズとなります。

サイズが取得出来たらセカンダリサウンドバッファを作成するためのDSBUFFERDESC構造体を初期化します。
上でプライマリバッファの場合はサイズを0としていましたが、セカンダリバッファを作成する場合はサイズを指定し、
さらにセカンダリバッファバッファで使用したい機能に合わせてフラグを設定しなければなりません。

ここでは以下のようなフラグを設定しています。

DSBCAPS_GETCURRENTPOSITION2 このバッファの現在の再生ポイントをもっと詳しく調べられる
DSBCAPS_STATIC メモリ上に全てをロードして確保する
DSBCAPS_LOCDEFER バッファをハードウェアかメインメモリに取るかを自動で管理させる

このほかにもいろいろ指定が出来ますが現状はこれで問題ありません。
※詳細はこちら


セカンダリサウンドバッファが構築出来たらこのバッファに実際にサウンドデータをコピーします。

ただしここで1点注意ですが、サウンドバッファはうかつに書き換えが出来ないようになっています。
このため書き込みを行うにはまずサウンドバッファに対してロックを行い、その時に書き込み先のメモリアドレスを受け取ります。
メモリアドレスが取得出来たら実際にこのアドレスにデータを書き込み、最後に必ずアンロックして書き込みを終了しなければなりません。

サウンドバッファをロックするにはLockメソッドを使用します。

ロックが成功すると書き込み用のバッファのポインタとサイズがそれぞれ2つ返ってきます。

2つ返る理由としてサウンドバッファはループバッファになっているためですが、
ループとは最後までデータが再生されたら再び先頭に戻って続けて再生を行うもので、
たとえばバッファの途中から全バッファをロックした場合、その途中から最後までのバッファが1つ目、
再び戻って先頭からその途中までのバッファが2つ目という感じになっています。

なお、先頭から全部のバッファをロックしたからといっても必ず1つしか返らないという保障はありません。
これはOSやドライバ側でどのようにサウンドデータを扱っているかによるためで、
必ず2つ分のバッファサイズを確認しておく必要があります。


ここでは書き込みバッファに対して直接ファイルロード関数を使っています。
ロックされたバッファは通常のメモリ領域のため、これをそのままロード先にすることが出来ます。

さらに1つ目のバッファと2つ目のバッファに対してそれぞれロードを行うことで、
これで全てのWAVデータが格納されたことになり、これでサウンドバッファへの書き込みは完了となるので、
最後にUnlockを呼び出してバッファをアンロックし、DirectSoundで再生出来るように制御を戻します。

これで一通り難しい作業は完了しましたが、終了時の処理がまだ入っていないので、
次は終了処理をまとめた関数について説明します。

■開放関数

DirectSoundやプライマリサウンドバッファ、セカンダリサウンドバッファは全てCOMオブジェクトのため、
プログラムの終了前には必ず開放する必要があります。

以下は開放を行うための終了関数となります。

/////////////////////////////////////////////////////////
// 終了
/////////////////////////////////////////////////////////
BOOL Exit( void )
{
    if( lpSecondary ) {
        lpSecondary->Release();
        lpSecondary = NULL;
    }

    if( lpPrimary ) {
        lpPrimary->Release();
        lpPrimary = NULL;
    }

    if( lpDS ) {
        lpDS->Release();
        lpDS = NULL;
    }

    // COMの終了
    CoUninitialize();

    return TRUE;
}


この書き方はどこぞで見たかもしれませんが、基本的にオブジェクトが存在していたら開放しNULLとするため、
初期化が行われていない場合は何もしないで終わるようになっています。

なお、この関数はInitと対で使用することを想定しているため、
InitでCOMを初期化していたのでExitでCOMの終了を行うようにしています。


■メインプログラム

ここでは上記の関数が既に使用出来る状態になっているものとして、
以下は実際にWAVファイルを読み込んで再生する部分のサンプルとなります。

#include "CWindow.h"
#include <dsound.h>

#define DEBUGMODE
#include "DEBUG.H"

// ライブラリリンク
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "winmm.lib")


// グローバル変数
CWindow             win;                    // ウインドウクラス
LPDIRECTSOUND8      lpDS = NULL;            // DirectSound8
LPDIRECTSOUNDBUFFER lpPrimary = NULL;       // プライマリサウンドバッファ
LPDIRECTSOUNDBUFFER lpSecondary = NULL;     // セカンダリサウンドバッファ


~~~~~ 略 ~~~~~


// メインルーチン
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
    INITDEBUG();
    CLEARDEBUG;

    // ウインドウ
    if( !win.Create( hInstance,L"TestDirectSound",TRUE,640,480) ) {
        DEBUG( "ウィンドウエラー\n" );
        return -1;
    }

    // DirectSound初期化
    if( !Init() ) {
        Exit();
        return -1;
    }

    // プライマリサウンドバッファ
    if( !CreatePrimaryBuffer() ) {
        Exit();
        return -1;
    }

    // サウンドバッファ
    if( !CreateSoundBuffer(&lpSecondary,"nayu_dra.wav") ) {
        Exit();
        return -1;
    }

    // 起動時の再生
    lpSecondary->Play( 0,0,0 );

    MSG msg;
    while(1) {
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
                DEBUG( "WM_QUIT\n" );
                break;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        BYTE key[256];
        GetKeyboardState( key );

        if( key[VK_ESCAPE]&0x80 )
            break;

        if( key[VK_F1]&0x80 )
            lpSecondary->Play( 0,0,0 );

        if( key[VK_F2]&0x80 )
            lpSecondary->Stop();

        if( key[VK_F3]&0x80 )
            lpSecondary->SetCurrentPosition(0);

        Sleep(8);                                       // CPUへのウエイト
    }

    // 開放
    Exit();

    win.Delete();

    return 0;
}


このサンプルは1つのWAVファイルをロードし、これを再生したり停止したり、
また再生位置を先頭に戻したりといった処理を行うものです。

このサンプルの動作を詳しく説明します。

まずWinMainの最初でCWindowを使ってウィンドウを作っていますが、
これは以前説明してあるウィンドウクラスを使用しているだけなので説明は省きます。

ウインドウが作成されたら次はInit関数を呼び出してDirectSoundを初期化しています。

その次にCreatePrimaryBufferを呼び出してプライマリバッファを作成しています。
なおこの時もしエラーが発生していた場合は、Exitを呼び出して構築されていたオブジェクトを
開放してから終了するようになっています。

次にCreateSoundBufferを呼び出してWAVファイルからセカンダリサウンドバッファを作成しています。
この時生成されたサウンドバッファは第1引数に指定した変数に返されますが、
ここではグローバル変数として確保していたlpSecondaryを指定しています。

ちなみにこのセカンダリバッファを複数用意し、それぞれ別なWAVファイルを指定して
CreateSoundBufferを呼び出すことで、同時にたくさんのWAVをロードすることが出来ます。


ロードが完了した段階でもう再生や停止が行えますが、
ここでは試しにメインループに入る前にPlayを呼び出して再生をしています。

Play関数の第1引数は予約されているため常に0を指定し、第2引数はサウンドの優先度を0~0xFFFFFFFFで指定します。
これはサウンドバッファをDSBCAPS_LOCDEFERで作成した際に参照され、
もし同時に再生するサウンドが多くなったりして処理が重くなるようならば、
この優先順位の高いものを優先的に鳴らし、低いものは再生させないといったことを自動で行わせることが出来ます。
なお、特に優先度が必要無ければ全て0を指定しても問題ありません。

そして第3引数は再生方法のフラグとなり、ループ再生にしたり優先管理を指示したりします。
通常は0を指定するとループせずに1回鳴って終わりで、DSBPLAY_LOOPINGを指定するとループ再生を行います。
その他のフラグについては通常は使用しないので、基本的に0かループかのみでよいと思います。

メインループ内ではキーの入力によってWAVの再生を制御できます。

ソースコード見れば分かると思いますが、F1で再生、F2で停止は特に説明する必要は無いと思います。
そしてF3にてSetCurrentPositionを呼び出して再生位置を先頭に戻すようになっています。

なお、SetCurrentPositionの引数は時間ではなくアドレス指定となるため、時間指定で位置を変えたい場合は
サンプリングレートやビット数、チャンネル数を考慮して計算しなければなりません。
単純に先頭に戻すのであれば0を指定すれば問題ありません。

そしてここからが重要ですが、DirectSoundでPlayを行い再生が終わっても自動的にStop状態にはなりません。
つまりまだ再生は続いている状態となっているため、処理的にはStopを呼び出して停止させるのが一番良い停止方法となります。

ちなみにこの状態はこのサンプルで実験できます。
再生が終わった後にF3を押すことで再生位置を先頭へ戻すことが出来ますが、
停止を行っていなければすぐに先頭から再生が始まります。

最後にプログラムを終了する場合は、Exitを呼び出してから終了します。

■サンプルのソースファイル

上記のプログラムのサンプルを用意しました。

VisualStudio2010のプロジェクト
※VC2012、VC2013はこちら
TestDirectSound_vc2010.zip
VisualStudio2015のプロジェクト TestDirectSound_vc2015.zip