Platform SDK: DirectX

ウェーブ ファイルの読み込み

[Visual Basic]

短いサウンドでは、DirectSound.CreateSoundBufferFromFile メソッドを使ってスタティック バッファにウェーブ ファイルをロードすることが、ウェーブ ファイルを再生する最も簡単な方法である。メモリに都合よく収まらないほどの長いファイルでは、ストリーム バッファを作成し、部分的にファイルを読み込まなくてはならない。

ウェーブ ファイルは RIFF (Resource Interchange File Format、リソース交換ファイル フォーマット) を採用しており、ファイル ヘッダーと、それぞれヘッダーとデータから成る任意の数の「チャンク」によって構成されている。チャンク ヘッダーは、データの型を識別する 4 文字のタグとデータの長さを与える Long で構成される。

ウェーブ ファイル ヘッダーは次のようになる。

最初のチャンクは常にフォーマット チャンクであり、次のようなフォーマットになる。

ウェーブ ファイルの PCM サンプル データは、文字列 "data" で始まるチャンクに保持される。これは、Long &H61746164 としても読み取られる。ほとんどの場合このチャンクはフォーマット チャンクに直接続くが、RIFF が拡張可能なフォーマットなので、他のタイプのチャンクが先行しないという保証はない。ファイル パーサーには、処理できないチャンクを無視できる能力が必要である。

ウェーブ ファイルを解析するには、3 つのユーザー定義型を準備するとよい。最初のタイプは、ファイル ヘッダーとフォーマット チャンクのヘッダー内の情報をすべて受け取る。

Private Type FileHeader
    lRiff As Long
    lFileSize As Long
    lWave As Long
    lFormat As Long
    lFormatLength As Long
End Type

2 番目のタイプは、フォーマット データを受け取る。これには、メンバが異なる順序である WAVEFORMATEX 型は使えない。この型は、16 バイトのチャンクのうち 14 バイトだけを取得しなければならない。なぜなら、最後の Integer 値は WAVEFORMATEX.lExtra と同等で、PCM ファイルでは使われないからである。

Private Type WaveFormat
    wFormatTag As Integer
    nChannels As Integer
    nSamplesPerSec As Long
    nAvgBytesPerSec As Long
    nBlockAlign As Integer
    wBitsPerSample As Integer
End Type

3 番目のタイプは、データ チャンクも含め任意のチャンクのヘッダーを取得するのに使われる。

Private Type ChunkHeader 
    lType As Long
    lLen As Long
End Type

次のサンプル関数は、ファイルが RIFF ウェーブ ファイルであることを確認し、サンプル データの始まりを検索し、ウェーブ フォーマットについての情報を保持する WAVEFORMATEX 型を返す。

Dim FileFree As Long           ' グローバルなファイル ハンドル。
Dim lDataLength As Long        ' グローバルなデータ長。

Private Function FillFormat(FileName As String) As WAVEFORMATEX
 
    Dim Header As FileHeader
    Dim HdrFormat As WaveFormat
    Dim chunk As ChunkHeader
    Dim by As Byte
    Dim i As Long
    
    ' ファイルを開き、ヘッダーを読み込む。
 
    Close #FileFree
    FileFree = FreeFile 
    Open FileName For Binary Access Read As #FileFree
    Get #FileFree, , Header 
 
    ' "RIFF" タグをチェックし、見つからない場合は終了する。
 
    If Header.lRiff <> &H46464952 Then 
        Exit Function
    End If
 
    ' "WAVE" タグをチェックし、見つからない場合は終了する。
 
    If Header.lWave <> &H45564157 Then 
        Exit Function
    End If
 
    ' フォーマット チャンクの長さをチェックする。16 バイトよりも
    ' 少ない場合は、PCM データではないので使えない。
 
    If Header.lFormatLength < 16 Then
        Exit Function
    End If
    
    ' フォーマットを取得する。
 
    Get #FileFree, , HdrFormat
 
    ' フォーマット バイトを捨て、次のチャンクを検索する。
 
    For i = 1 To Header.lFormatLength - 16
        Get #FileFree, , by
    Next
 
    ' "data" チャンクに達するまでチャンクを無視する。
 
    Get #FileFree, , chunk
    Do While chunk.lType <> &H61746164
        For i = 1 To chunk.lLen
            Get #FileFree, , by
        Next
        Get #FileFree, , chunk
    Loop
 
    ' データのサイズを取得する。
 
    lDataLength = chunk.lLen
 
   ' フォーマット情報を返された型に書き込む。

    With FillFormat 
        .lAvgBytesPerSec = HdrFormat.nAvgBytesPerSec
        .lExtra = 0
        .lSamplesPerSec = HdrFormat.nSamplesPerSec
        .nBitsPerSample = HdrFormat.wBitsPerSample
        .nBlockAlign = HdrFormat.nBlockAlign
        .nChannels = HdrFormat.nChannels
        .nFormatTag = HdrFormat.wFormatTag
    End With
 
End Function

これでアプリケーションは、ファイルからのデータを読み込み、セカンダリ サウンド バッファにそのデータをストリーミングできるようになる。ファイルからデータを直接 DirectSoundBuffer に読み込む方法はないので、最初にプライベート バッファにそのデータを読み込んでから、DirectSoundBuffer.WriteBuffer を使ってそのデータを書き込まなければならない。詳細については、「ストリーム バッファの使い方」を参照すること。

[C++]

Wavread.cpp 内でラッパー関数を使うには、次の 4 つの変数を宣言しなければならない。

WAVEFORMATEX  *pwfx;          // ウェーブ フォーマット情報。
HMMIO         hmmio;          // ファイル ハンドル。
MMCKINFO      mmckinfoData;   // チャンク情報。
MMCKINFO      mmckinfoParent; // 親チャンクの情報。
 

ウェーブ ファイルを読み込む最初のステップは、WaveOpenFile 関数を呼び出すことである。この関数はファイルへのハンドルを取得し、そのファイルが RIFF フォーマットであることを確認し、ウェーブ フォーマットに関する情報を取得する。パラメータとして、ファイル名と、宣言した変数のうち 3 つの変数のアドレスを指定する。

if (WaveOpenFile(lpzFileName, &hmmio, &pwfx, &mmckinfoParent) != 0)
{
    // 失敗。
}
 

どのラッパー関数も、成功した場合は 0 を返す点に注意すること。

次のステップは WaveStartDataRead 関数を呼び出し、ファイル ポインタを使ってデータ チャンクに到達することである。この関数はデータ チャンク用の MMCKINFO 構造体への書き込みを行うので、利用可能なデータの量を把握できる。

if (WaveStartDataRead(&hmmio, &mmckinfoData, &mmckinfoParent) != 0)
    {
    // 失敗。
    }
 

これで、アプリケーションはファイルからセカンダリ バッファへのコピーを開始できる。通常は、データ チャンクのサイズとウェーブのフォーマットを取得するまではサウンド バッファを作成しない。次のコードは、ファイル内の全データを保持するのにちょうど良いサイズのスタティック バッファを作成する。

/* lpds が DirectSound オブジェクトへの有効なポインタである
   と想定する。*/
 
LPDIRECTSOUNDBUFFER  lpdsbStatic;
DSBUFFERDESC         dsbdesc;
 
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); 
dsbdesc.dwSize = sizeof(DSBUFFERDESC); 
dsbdesc.dwFlags = DSBCAPS_STATIC; 
dsbdesc.dwBufferBytes = mmckinfoData.cksize;  
dsbdesc.lpwfxFormat = pwfx; 
 
if FAILED(lpds->CreateSoundBuffer(&dsbdesc, &lpdsbStatic, NULL))
{
    WaveCloseReadFile(&hmmio, &pwfx);
    return FALSE; 
}
 

この場合は、アプリケーションはデータのストリーミングを行わず、単純にスタティック バッファへの書き込みを行うので、バッファ全体は先頭からロックされる。ラップ アラウンド (先頭へ戻る) も発生しないので、必要なのは 1 つのポインタとバイト カウントである。

LPVOID lpvAudio1;
DWORD  dwBytes1;
 
if FAILED(lpdsbStatic->Lock(
        0,              // ロック開始のオフセット。
        0,              // ロックのサイズ。この場合は無視される。
        &lpvAudio1,     // ロック開始のアドレス。
        &dwBytes1,      // ロックされたバイト数。
        NULL,           // ラップ アラウンドの開始。不使用。
        NULL,           // ラップ アラウンドのサイズ。不使用。
        DSBLOCK_ENTIREBUFFER))  // フラグ。
{
    // エラー処理。
    Close();
    .
    .
    .
}
 

WaveReadFile 関数は、ファイルからバッファのポインタへデータをコピーし、成功した場合は 0 を返す。

UINT cbBytesRead;
 
if (WaveReadFile(
        hmmio,              // ファイル ハンドル。
        dwBytes1,           // 読み込むべきバイト数。
        (BYTE *) lpvAudio1, // コピー先。
        &mmckinfoData,      // ファイル チャンク情報。
        &cbBytesRead))      // 実際に読み込まれたバイト数。
{
    // 0 以外の値が返された場合の失敗を処理する。
    WaveCloseReadFile(&hmmio, &pwfx);
    .
    .
    .
}
 

最後にアプリケーションはバッファをアンロックし、ウェーブ ファイルを閉じる。

lpdsbStatic->Unlock(lpvAudio1, dwBytes1, NULL, 0);
Close();
 

ストリーム バッファの場合は、通常は現在の読み込み位置によって決定された一定の間隔で WaveReadFile を呼び出す。(「再生バッファの通知」を参照。) バッファ内のロックされた部分がラップ アラウンドして (先頭に戻って) いる場合は、もちろんロックされているセグメントごとに WaveReadFile を 1 回呼び出すべきである。