サウンドの再生
前回の記事で『XAudio2の初期化』を行いました。
次はサウンドの再生部分を作っていきたいと思います。
XAudio2で個々のサウンドを制御するのはソースボイス(SourceVoice)が行います。
ソースボイスの作成
XAudio2の初期化と同じくソースボイスも作成は簡単です。
ただし、ソースボイスでサウンドを再生するには別途波形データの情報が必要となります。
どちらかと言えばこちらで苦労することが多くなってきます。
今回、先ずはソースボイスの初期化から順番に説明していきます。
SourceVoiceの作成
IXAudio2SourceVoice* pSourceVoice = nullptr; // wfexは後述する波形データのフォーマット情報となります pXAudio->CreateSourceVoice(&pSourceVoice, &wfex);
これだけで作成できます。
ただ、サウンドデータのフォーマット(wfex)が分からなければ作成ができません。
ソースボイスを作成するには前提としてサウンドデータのフォーマットを知っておく必要があります。
しかし、フォーマットと言ってもサウンドには定まったフォーマットがある訳ではありません。
なので事前にサウンドデータを読み込んでのフォーマットを取得しておく必要があります。
Waveファイルの読み込み
次はWave(.wav)ファイルを使った形で説明していきます。
今回は基本的なファイルフォーマットにのみ対応した例外無視の実装をしていきます。
Waveを読むために、先ずは各情報を読み取るための構造体を用意します。
// チャンクデータの基本構造 struct Chunk { char id[4]; // チャンク毎のID int32_t size; // チャンクサイズ }; // RIFFヘッダー struct RiffHeader { Chunk chunk; // "RIFF" char type[4]; // "WAVE" }; // FMTチャンク struct FormatChunk { Chunk chunk; // "fmt " WAVEFORMAT fmt; // 波形フォーマット };
こんな感じです。
この3つがあれば簡単にWAVEが読み込めてしまいます。
WaveファイルはChunkと呼ばれる形式で管理されています。
Chunkの基本フォーマットは下記です。
id | チャンクの識別子として4文字 |
---|---|
size | データ部のサイズ |
データ部 | 各チャンク毎のデータ |
Waveこのフォーマットで並べられたデータとなっています。
先ず先頭にRIFFチャンク
id | “RIFF” |
---|---|
size | データ部のサイズ |
データ部 | チャンクデータ一覧 |
このようにRIFFチャンクは全体のチャンクを内包したチャンクとなります。
sizeにはidとsizeの8バイトを除いたファイルサイズが入っています。
次にFormatチャンクです。
波形のフォーマットが格納されています。
id | “fmt “ |
---|---|
size | データ部のサイズ |
WAVEFORMAT | 波形のフォーマット |
この部分を元にSourceVoiceで再生するフォーマットを取得します。
最後にDataチャンクです。
ここに本体となる波形データが格納されています。
id | “data” |
---|---|
size | データ部のサイズ |
data | 波形データ |
この波形データをSourceVoiceに流し込むことで音が鳴るようになります。
ではこれらを読み込んで再生する処理を見ていきましょう。
SourceVoiceでの再生
再生するためには先ほどの説明を元に読み込みを行う必要があります。
Waveファイルの読み込み
FILE* file = NULL; // Waveファイルを開く if (fopen_s(&file, filename, "rb") != 0) { return nullptr; } // RIFFヘッダーの読み込み RiffHeader riff; fread(&riff, sizeof(riff), 1, file); // Formatチャンクの読み込み FormatChunk format; fread(&format, sizeof(format), 1, file); // Dataチャンクの読み込み Chunk data; fread(&data, sizeof(data), 1, file); // Dataチャンクのデータ部(波形データ)の読み込み char* pBuffer = (char*)malloc(data.size); fread(pBuffer, data.size, 1, file); fclose(file);
これでWaveフォーマットの各チャンクを元に読み込みが行えます。
※特殊なフォーマットのWaveは読み込めません
Waveが読み込めたら後はSourceVoiceを作るだけです。
SourceVoiceの生成
WAVEFORMATEX wfex{}; // 波形フォーマットの設定 memcpy(&wfex, &format.fmt, sizeof(format.fmt)); // 1サンプル辺りのバッファサイズを算出します wfex.wBitsPerSample = format.fmt.nBlockAlign * 8 / format.fmt.nChannels; IXAudio2SourceVoice* pSourceVoice = nullptr; // 波形フォーマットを元にSourceVoiceの生成 if (FAILED(pXAudio->CreateSourceVoice(&pSourceVoice, &wfex))) { free(pBuffer); return nullptr; }
SourceVoiceに必要なフォーマットはFormatチャンクに含まれていた情報です。
あとはこれを元に1サンプル辺りのバッファサイズを追加すれば出来上がりです。
SourceVoicceの再生
XAUDIO2_BUFFER buf{}; // 再生する波形データの設定 buf.pAudioData = (BYTE*)pBuffer; buf.Flags = XAUDIO2_END_OF_STREAM; buf.AudioBytes = data.size; // 波形データの再生 sourceVoice->SubmitSourceBuffer(&buf); sourceVoice->Start();
これで波形データの再生が行えます。
では一連の流れをつなげて関数化してみましょう。
Waveファイルの再生関数
再生まで一気に行うPlayWave関数の例です。
IXAudio2SourceVoice* PlayWave(const char* filename) { FILE* file = NULL; if (fopen_s(&file, filename, "rb") != 0) { return nullptr; } // RIFFヘッダーの読み込み RiffHeader riff; fread(&riff, sizeof(riff), 1, file); // Formatチャンクの読み込み FormatChunk format; fread(&format, sizeof(format), 1, file); // Dataチャンクの読み込み Chunk data; fread(&data, sizeof(data), 1, file); // Dataチャンクのデータ部(波形データ)の読み込み char* pBuffer = (char*)malloc(data.size); fread(pBuffer, data.size, 1, file); fclose(file); WAVEFORMATEX wfex{}; // 波形フォーマットの設定 memcpy(&wfex, &format.fmt, sizeof(format.fmt)); wfex.wBitsPerSample = format.fmt.nBlockAlign * 8 / format.fmt.nChannels; // 波形フォーマットを元にSourceVoiceの生成 IXAudio2SourceVoice* pSourceVoice = nullptr; if (FAILED(pXAudio->CreateSourceVoice(&pSourceVoice, &wfex))) { free(pBuffer); return nullptr; } // 再生する波形データの設定 XAUDIO2_BUFFER buf{}; buf.pAudioData = (BYTE*)pBuffer; buf.Flags = XAUDIO2_END_OF_STREAM; buf.AudioBytes = data.size; // 波形データの再生 pSourceVoice->SubmitSourceBuffer(&buf); pSourceVoice->Start(); return pSourceVoice; }
今回の関数では再生まで全て一連の流れにしていますが、もちろん読み込みだけ行って後で再生するといった使い方も可能です。
今回の場合はSourceVoiceを戻り値として返しているので、呼び出し側で止めたい歳にStopを呼ぶ形となります。
削除の方法はMasteringVoiceと同じでDestroyVoiceを呼び出します。
pSourceVoice->DestroyVoice(); pSourceVoice = nullptr;
これでXAudio2を使用した再生は完了です。
Waveファイルのフォーマットさえ分かれば、難しい処理もないので、DirectSoundなどと比べても楽に再生できるようになったんではないでしょうか!