Platform SDK: DirectX

ステップ 6 : ウェーブ ファイルからのデータのストリーミング

[Visual Basic]

ここでは、C および C++ でのアプリケーション開発について説明する。Visual Basic については、「DirectSound Visual Basic チュートリアル」を参照すること。

[C++]

前の 2 つのステップで通知の設定と処理を行ったのは、バッファ内のデータの半分が再生されたときにアラートを送ることを目的としている。カレント プレイ ポジションがバッファの先頭または中間を過ぎたらすぐに、再生を終えたばかりのバッファ セグメントにデータを書き込む。(バッファが開始された直後に通知を受け取り、バッファの後半に書き込みを行う。) このバッファは 2 秒分のデータを保持するので、カレント プレイ ポジションよりほぼ 1 秒先にデータを書き込むことになるが、これは再生位置が新しいデータに達する前に処理を完了するのに十分な時間である。

WinMain 関数では、イベントのシグナルが送信されたときに、このイベントのインデックスを StreamToBuffer 関数に渡す。このインデックスは、DSBPOSITIONNOTIFY 配列内で通知が占める位置のインデックスに対応する。

ここに、StreamToBuffer 関数の先頭部分を示す。

BOOL StreamToBuffer(DWORD dwPos)
{
    LONG            lNumToWrite;
    DWORD           dwStartOfs;
    VOID            *lpvPtr1, *lpvPtr2;
    DWORD           dwBytes1, dwBytes2;
    UINT            cbBytesRead;
    static DWORD    dwStopNextTime = 0xFFFF;
 
    if (dwStopNextTime == dwPos)   // すべてのデータが再生された。
    {
        lpdsb->Stop();
        dwStopNextTime = 0xFFFF;
        return TRUE;
    }
 
    if (dwStopNextTime != 0xFFFF)  // ストリームすべきものはないが、
                                 // データの最後まで演奏を続ける。
        return TRUE;
 

dwStopNextTime 変数は、ファイルの最後に達したことを示すフラグである。関数の残りの部分でこの変数をセットする。この値が 0 ではない場合、ストリームすべきデータはもう存在しない。dwStopNextTime の値が処理中の通知のインデックスに等しい場合、ファイルの終わりに達したときに相当する場所へ、カレント プレイ ポジションが戻ったこと、つまりバッファにコピーしたデータの最後のセグメントが既に再生されたことがわかる。この場合はバッファを停止すべきであり、古いデータの再生を続けない。

StreamToBuffer 関数の続く部分では、バッファ内で、新しいデータのコピーを開始する場所のオフセットを決定する。このチュートリアルのバッファには通知位置が 2 つしかないが、このコードは任意の通知数で動作するよう設計されている。dwPos が、カレント プレイ ポジションによって渡されたばかりの通知位置のインデックスである点に注意すること。直前の通知位置から始まるバッファ セグメントに書き込みを行うことになる。

    if (dwPos == 0)
        dwStartOfs = rgdsbpn[NUMEVENTS - 1].dwOffset;
    else
        dwStartOfs = rgdsbpn[dwPos-1].dwOffset;
 

ここで、バッファ セグメントのサイズを決定する。

    lNumToWrite = (LONG) rgdsbpn[dwPos].dwOffset - dwStartOfs;
    if (lNumToWrite < 0) lNumToWrite += dsbdesc.dwBufferBytes;
 

ここまでで、データ書き込みを行うためにバッファをロックするのに必要なすべての情報を入手した。

    IDirectSoundBuffer_Lock(lpdsb,
                     dwStartOfs,       // ロック開始位置のオフセット。
                     lNumToWrite,      // ロックされるバイト数。
                     &lpvPtr1,         // ロック開始アドレス。
                     &dwBytes1,        // ロックされるバイト カウント。
                     &lpvPtr2,         // ラップ アラウンド アドレス。
                     &dwBytes2,        // ラップ アラウンド バイト カウント。
                     0);               // フラグ。
 

この例では、ロックは決してラップ アラウンドしない (ロックされた部分がバッファの先頭へ戻り、バッファの最後と先頭の 2 つにまたがることはない) ので、ファイルから 1 回のデータ コピーを行うだけで十分である。Wavread.cpp 内の WaveReadFile 関数を呼び出して、データを取得する。

    WaveReadFile(hmmio,                // ファイル ハンドル。
                 dwBytes1,             // 取得されるバイト数。
                 (BYTE *) lpvPtr1,     // 書き込み先。
                 &mmckinfoData,        // ファイル チャンク情報。
                 &cbBytesRead);        // 実際に読み込むバイト。
 

今度は、ファイルの最後に達したかどうかを判断する。達した場合は、ファイルを閉じ、このセグメントの残りの部分に無音のデータを書き込み (セグメント全体を再生した後でバッファを停止する仕様となっているため)、dwStopNextTime フラグをセットする。

    if (cbBytesRead < dwBytes1)        // ファイルの最後に達した。
    {
        WaveCloseReadFile(&hmmio, &pwfx);
        FillMemory((PBYTE)lpvPtr1 + cbBytesRead,
                dwBytes1 - cbBytesRead, 
                (dsbdesc.lpwfxFormat->wBitsPerSample==8) ? 128 : 0);
        dwStopNextTime = dwPos;
    }
 

最後にバッファをアンロックし、メッセージ ループへ戻る。

    IDirectSoundBuffer_Unlock(lpdsb, 
            lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);
    
    return TRUE;
} // StreamToBuffer() の終わり。
 

次項 : ステップ 7 : DirectSound のシャット ダウン