DirectX Class
Chapter 18: サウンドの再生
* Chapter List *
* Others *
サウンド
諸事情によりサウンドの章を書くことになりました。
なぜかと申しますと、色々と大人の事情があるのです。
理由を書くと18歳未満のお子様には不適切な表現が含まれる可能性がありますので割愛させていただきます。

サウンド、いわゆる『音』はゲームにおいても非常に重要な役割を占めています。
ゲーム中のサウンドはBGMや効果音といったものです。
一般的なゲームをする際に音を消してやってみると、いかにゲームがやりにくいものか体感できるでしょう。
普段、無意識のうちにBGMから雰囲気を感じ取ったり、効果音でタイミングを計ったりしているのかが分かります。

ゲームにはBGMや効果音、音声など色々なサウンドが使われていますが、
『音を出す』という点に関してはみな同じなので、プログラムレベルでは同じ処理をすることになります。
つまり音の出し方は一種類覚えてしまえばOKということになります。

世の中には様々な形式の音声ファイルがありますが、今回説明するのはWAVE形式の音声ファイルだけです。
WAVEファイルはファイルサイズが大きくなりやすいといった欠点はありますが、
MIDIファイルと違って音声まで扱え、モノラルからステレオまで対応しているので、
表現の幅としては一番オールマイティだと思われます。

また、筆者は昔DirectSoundというものを使ってサウンドを流していたのですが、
先日同じプログラムを書いてみたところ、コンパイルが通りませんでした。
また、DirectXSDKでも、ヘッダやライブラリは付属しているのにサンプルは付属していませんでした。
ここ何年かの間に、DirectSoundではなく『XAudio2』というものを使うようになったらしく、
まるで気分はMr.Urashima。
しかも検索してもBCCでXAudio2を使うことに関しての説明をしてあるサイトが全く出てこなかったので、
結構大変でした。
BCCでXAudio2を使おうとするとコンパイルの際に『sal.h』が見つからないと言われます。
sal.hというのは適当に検索すれば出てきますので、DirectXSDKのIncludeファイルの中に入れておいてください。
一応ここにも置いておきます。

sal.h

以前、2MBまでのファイルしか扱えないと書きましたが、2MB以上のファイルでも問題なく再生されました。
何かの勘違いかなと思います。
プラズマや霊的なものが筆者の脳に作用して混乱させたのではないかと思います。
とりあえず43.9MBまでのファイルは途切れたり途中で止まったりといった由々しき事態は発生せず、再生されました。
プリプロセッサ部
まず、『xaudio2.h』をインクルードしてください。

#include <xaudio2.h>

プリプロセッサ部は以上です。
『あれ?』と思った方もおられるかもしれませんが、ライブラリのリンクはありません。
筆者も『あれ?』と思いましたがリンクしなくて大丈夫です。

先ほどの『 sal.h 』はこのxaudio2.hの中でインクルードされていますので、
xaudio2.hの中から見える位置に置いておいてください。
(筆者はxaudio2.hと同じディレクトリに放り込みました)
変数の宣言
必要な変数を宣言します。

まず、いくつ音声ファイルを利用する場合でも1つで済む共通の変数の宣言をします。

IXAudio2               *g_pXAudio2        = NULL;
IXAudio2MasteringVoice *g_pMasteringVoice = NULL;

これは扱う音声ファイルの種類によらず1つずつで大丈夫です。
次に各音声ファイルごとに必要な変数を宣言します。

IXAudio2SourceVoice    *g_pSourceVoice    = NULL;

これは音声ファイル1つにつき、1つ用意することになります。
初期化
まず最初に以下のコードを実行します。
何をやっているのは私にはよく分かりませんが、とりあえずこうしないと動きません。

CoInitializeEx( NULL , COINIT_MULTITHREADED );

これ以後、各処理の成功の有無に関わらず、ソフトウェアを終了する際には、
CoUninitialize(); を実行する必要があります。

続いてXAudio2を作成します。

XAudio2Create( &g_pXAudio2 , 0 );

これ以後、各処理の成功の有無に関わらず、ソフトウェアを終了する際には、
if ( g_pXAudio2 ) g_pXAudio2->Release(); を実行する必要があります。

次にマスタリングボイスの作成をします。

g_pXAudio2->CreateMasteringVoice( &g_pMasteringVoice );

これで共通となる初期化処理は終わりです。
終了処理
初期化に対する各種終了処理が必要なので先にやっておきます。

g_pMasteringVoice->DestroyVoice();
if ( g_pXAudio2 ) g_pXAudio2->Release();
CoUninitialize();

初期化処理のときに三種類の初期化を行いましたので、
それぞれに対応する終了処理を行います。
必ずこの順番で終了させてください。
音声ファイルの読み込み
音声ファイルの読み込みはXAudio2とはあまり関係なく、
各フォーマット特有の地道な読み込み作業になりますので、ここでは説明を割愛して、
サンプル提示のみにさせていただきます。
#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'



HANDLE               hFile;
DWORD                dwChunkSize;
DWORD                dwChunkPosition;
DWORD                filetype;
WAVEFORMATEXTENSIBLE wfx;
XAUDIO2_BUFFER       buffer;
BYTE                 *pDataBuffer;



//******************************************************************************
//    Find Chunk.
//******************************************************************************
HRESULT FindChunk( HANDLE hFile , DWORD forucc , DWORD *dwChunkSize , DWORD *dwChunkDataPosition )
{
	HRESULT hr;
	
	DWORD dwRead;
	DWORD dwChunkType;
	DWORD dwChunkDataSize;
	DWORD dwRIFFDataSize = 0;
	DWORD dwFileType;
	DWORD bytesRead = 0;
	DWORD dwOffset = 0;
	
	hr = S_OK;
	
	if ( SetFilePointer( hFile , 0 , NULL , FILE_BEGIN ) == INVALID_SET_FILE_POINTER )
		return HRESULT_FROM_WIN32( GetLastError() );
	
	while( hr == S_OK ){
		if ( ReadFile( hFile , &dwChunkType , sizeof( DWORD ) , &dwRead , NULL ) == 0 )
			hr = HRESULT_FROM_WIN32( GetLastError() );
		if ( ReadFile( hFile , &dwChunkDataSize , sizeof( DWORD ) , &dwRead , NULL )
		     == 0 )
			hr = HRESULT_FROM_WIN32( GetLastError() );
		switch( dwChunkType ){
			case fourccRIFF:
				dwRIFFDataSize  = dwChunkDataSize;
				dwChunkDataSize = 4;
				if ( ReadFile( hFile ,
				     &dwFileType , sizeof( DWORD ) , &dwRead , NULL ) == 0 )
					hr = HRESULT_FROM_WIN32( GetLastError() );
				break;
			default:
				if ( SetFilePointer( hFile ,
				     dwChunkDataSize , NULL , FILE_CURRENT )
				     == INVALID_SET_FILE_POINTER )
					return HRESULT_FROM_WIN32( GetLastError() );
		}
		dwOffset += sizeof( DWORD ) * 2;
		if ( dwChunkType == forucc ){
			*dwChunkSize         = dwChunkDataSize;
			*dwChunkDataPosition = dwOffset;
			return S_OK;
		}
		dwOffset += dwChunkDataSize;
		if ( bytesRead >= dwRIFFDataSize ) return S_FALSE;
	}
	
	return S_OK;
}



//******************************************************************************
//    Read Chunk Data.
//******************************************************************************
HRESULT ReadChunkData( HANDLE hFile , void *buffer , DWORD buffersize , DWORD bufferoffset )
{
	DWORD dwRead;
	
	if ( SetFilePointer( hFile , bufferoffset , NULL , FILE_BEGIN )
	     == INVALID_SET_FILE_POINTER )
		return HRESULT_FROM_WIN32( GetLastError() );
	
	if ( ReadFile( hFile , buffer , buffersize , &dwRead , NULL ) == 0 )
		return HRESULT_FROM_WIN32( GetLastError() );
	
	return S_OK;
}



/****************************/
/****    読み込み部分    ****/
/****************************/
memset( &wfx , 0 , sizeof( WAVEFORMATEXTENSIBLE ) );
memset( &buffer , 0 , sizeof( XAUDIO2_BUFFER ) );

hFile = CreateFile( "bgm.wav" , GENERIC_READ , FILE_SHARE_READ , NULL ,
                    OPEN_EXISTING , 0 , NULL );
if ( hFile == INVALID_HANDLE_VALUE ){
	return HRESULT_FROM_WIN32( GetLastError() );
}
if ( SetFilePointer( hFile , 0 , NULL , FILE_BEGIN ) == INVALID_SET_FILE_POINTER ){
	return HRESULT_FROM_WIN32( GetLastError() );
}

// Check Wave File.
hr = FindChunk( hFile , fourccRIFF , &dwChunkSize , &dwChunkPosition );
if ( FAILED( hr ) ) return S_FALSE;
hr = ReadChunkData( hFile , &filetype , sizeof( DWORD ) , dwChunkPosition );
if ( FAILED( hr ) ) return S_FALSE;
if ( filetype != fourccWAVE ) return S_FALSE;

// Get Format.
hr = FindChunk( hFile , fourccFMT , &dwChunkSize , &dwChunkPosition );
if ( FAILED( hr ) ) return S_FALSE;
hr = ReadChunkData( hFile , &wfx , dwChunkSize , dwChunkPosition );
if ( FAILED( hr ) ) return S_FALSE;

// Load Sound.
hr = FindChunk( hFile , fourccDATA , &dwChunkSize , &dwChunkPosition );
if ( FAILED( hr ) ) return S_FALSE;
pDataBuffer = (BYTE*)malloc( dwChunkSize );
hr = ReadChunkData( hFile , pDataBuffer , dwChunkSize , dwChunkPosition );
if ( FAILED( hr ) ) return S_FALSE;

音声ファイルの設定
読み込んだ音声ファイルはソースボイスに設定する必要があります。
ここからはファイル形式に関わらず共通です。
まずはソースボイスを初期化します。

g_pXAudio2->CreateSourceVoice( &g_pSourceVoice , &(wfx.Format) );

第二引数にはフォーマットの種類を指定します。
今回はWAVE形式なのでWAVE形式である旨の情報を渡しています。

続いて、読み込んだ音声ファイルとその情報を、XAUDIO2_BUFFER構造体に入れていきます。

XAUDIO2_BUFFER       buffer;

buffer.AudioBytes = dwChunkSize;
buffer.pAudioData = pDataBuffer;
buffer.Flags      = XAUDIO2_END_OF_STREAM;
buffer.LoopCount  = XAUDIO2_LOOP_INFINITE;

AudioBytesには、音声データのサイズを入れます。
これはファイル全体のサイズではなく、音声データの部分のみのサイズです。

pAudioDataには、音声データそのものが入っているメモリ領域のアドレスを渡します。
これが実際に再生されるデータになります。

Flagsには、なんかよく分かりませんが、XAUDIO2_END_OF_STREAMを指定しておけば良いようです。
難しいことは分かりません。

LoopCountには、繰り返し再生の回数を指定します。
XAUDIO2_LOOP_INFINITEを指定すれば永久に繰り返し再生されるので、BGM等に最適です。
0を指定すると1回のみ再生されるので、効果音等に最適です。

そして次に、この構造体をソースボイスに設定します。

g_pSourceVoice->SubmitSourceBuffer( &buffer );

これで準備は完了です。
次はいよいよ再生です。
再生と一時停止
XAduio2には、再生はありますが、停止がありません。
驚愕の事実です。
停止はなく、一時停止しかありません。
驚愕の事実です。
そんなはずはないと思ってリファレンスを探し、Googleで検索をかけ、MSDNに偵察に行ったのですが、
筆者は探しだすことができず、とうとうおじいさんになってしまいました。
実は、停止ができないというのはかなり面倒です。

何はともあれまず再生のしかたです。
最初に、今その音声が再生中でないかどうかをチェックします。

XAUDIO2_VOICE_STATE xa2state;
g_pSourceVoice->GetState( &xa2state );

ここで、xa2state.BuffersQueued0 でなければたぶん再生中です。
再生中の場合は再生が終わるのを待つか、停止(一時停止)してから再生しましょう。
続いていよいよ再生です。

g_pSourceVoice->Start( 0 );

たったこれだけで再生できます。
ループ回数が1回の場合は、音声データの最後まで再生が終わればそこで自動で停止(一時停止)します。

次に一時停止の方法です。
それは以下のコードによって実現されます。

g_pSourceVoice->Stop( 0 );

たったこれだけで一時停止します。

さて、次に難関の『停止』です。
一時停止した状態から再生した場合どうなるかというと、想像通り続きから再生されます。
では、ループしない設定で最後まで再生されたファイルに対して再度再生しようとした場合はどうなるのか。
恐ろしいことに続きがないと判断され、再生されません。
残暑厳しい8月の終わり、筆者を震え上がらせた怪談がそれです。

最終的に出した解決策は、一時停止したソースボイスの中身を洗い流し、
もう一度ソースボイスにデータを設定するというものです。
処理が煩雑になりますが、変数の引渡しもアドレスばかりなので負荷的にも問題ないでしょう。
そのコードは以下の通りとなります。

g_pSourceVoice->Stop( 0 );
g_pSourceVoice->FlushSourceBuffers();
g_pSourceVoice->SubmitSourceBuffer( &buffer );

これでいわゆる普通の一般社会で言われるところの『停止』は実現できます。
ソースボイスの破棄処理
ゲームを終了する際や、不要になったソースボイスは破棄することになります。
それは以下のコードになります。

g_pSourceVoice->DestroyVoice();

これはゲームを終了する際などには、マスタリングボイスの破棄処理よりも前に行ってください。
サンプル
このサンプルを実行すると、DOSウインドウが開き、音楽が再生されます。
5秒ほど再生した後、再生を中断して終了します。

Sleep( 5000 )で再生している間の5秒間を待っているので、
ここを変更してループ再生など色々試してみてください。

同じ素材を使用したい方はこちらからどうぞ。
BGM(右クリックから保存)
音声ファイルは実行ファイルと同じフォルダに保存してください。

//-----------------------------------------------------------------
//
//    X Audio2.
//
//-----------------------------------------------------------------
#include <stdio.h>
#include <windows.h>
#include <xaudio2.h>

#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'



//-----------------------------------------------------------------
//    Global Variables.
//-----------------------------------------------------------------
IXAudio2               *g_pXAudio2        = NULL;
IXAudio2MasteringVoice *g_pMasteringVoice = NULL;
IXAudio2SourceVoice    *g_pSourceVoice    = NULL;



//-----------------------------------------------------------------
//    Prototypes.
//-----------------------------------------------------------------
HRESULT FindChunk( HANDLE , DWORD , DWORD* , DWORD* );
HRESULT ReadChunkData( HANDLE , void* , DWORD , DWORD );



//-----------------------------------------------------------------
//    Main.
//-----------------------------------------------------------------
int main()
{
	HRESULT      hr;
	
	HANDLE               hFile;
	DWORD                dwChunkSize;
	DWORD                dwChunkPosition;
	DWORD                filetype;
	WAVEFORMATEXTENSIBLE wfx;
	XAUDIO2_BUFFER       buffer;
	BYTE                 *pDataBuffer;
	
	XAUDIO2_VOICE_STATE xa2state;
	
	CoInitializeEx( NULL , COINIT_MULTITHREADED );
	
	/************************/
	/**** Create XAudio2 ****/
	/************************/
	hr = XAudio2Create( &g_pXAudio2 , 0 );
	if ( FAILED( hr ) ){
		CoUninitialize();
		return -1;
	}
	
	/********************************/
	/**** Create Mastering Voice ****/
	/********************************/
	hr = g_pXAudio2->CreateMasteringVoice( &g_pMasteringVoice );
	if ( FAILED( hr ) ){
		if ( g_pXAudio2 ) g_pXAudio2->Release();
		CoUninitialize();
		return -1;
	}
	
	/*************************/
	/**** Initalize Sound ****/
	/*************************/
	memset( &wfx , 0 , sizeof( WAVEFORMATEXTENSIBLE ) );
	memset( &buffer , 0 , sizeof( XAUDIO2_BUFFER ) );
	
	hFile = CreateFile( "sample0019.wav" , GENERIC_READ , FILE_SHARE_READ , NULL ,
	                    OPEN_EXISTING , 0 , NULL );
	if ( hFile == INVALID_HANDLE_VALUE ){
		return HRESULT_FROM_WIN32( GetLastError() );
	}
	if ( SetFilePointer( hFile , 0 , NULL , FILE_BEGIN ) == INVALID_SET_FILE_POINTER ){
		return HRESULT_FROM_WIN32( GetLastError() );
	}
	
	// Check Wave File.
	hr = FindChunk( hFile , fourccRIFF , &dwChunkSize , &dwChunkPosition );
	if ( FAILED( hr ) ) return S_FALSE;
	hr = ReadChunkData( hFile , &filetype , sizeof( DWORD ) , dwChunkPosition );
	if ( FAILED( hr ) ) return S_FALSE;
	if ( filetype != fourccWAVE ) return S_FALSE;
	
	// Get Format.
	hr = FindChunk( hFile , fourccFMT , &dwChunkSize , &dwChunkPosition );
	if ( FAILED( hr ) ) return S_FALSE;
	hr = ReadChunkData( hFile , &wfx , dwChunkSize , dwChunkPosition );
	if ( FAILED( hr ) ) return S_FALSE;
	
	// Load Sound.
	hr = FindChunk( hFile , fourccDATA , &dwChunkSize , &dwChunkPosition );
	if ( FAILED( hr ) ) return S_FALSE;
	pDataBuffer = (BYTE*)malloc( dwChunkSize );
	hr = ReadChunkData( hFile , pDataBuffer , dwChunkSize , dwChunkPosition );
	if ( FAILED( hr ) ) return S_FALSE;
	
	buffer.AudioBytes = dwChunkSize;
	buffer.pAudioData = pDataBuffer;
	buffer.Flags      = XAUDIO2_END_OF_STREAM;
	buffer.LoopCount  = 0;
	
	g_pXAudio2->CreateSourceVoice( &g_pSourceVoice , &(wfx.Format) );
	g_pSourceVoice->SubmitSourceBuffer( &buffer );
	
	/********************/
	/**** Play Sound ****/
	/********************/
	g_pSourceVoice->GetState( &xa2state );
	if ( xa2state.BuffersQueued != 0 ){
		g_pSourceVoice->Stop( 0 );
		g_pSourceVoice->FlushSourceBuffers();
		g_pSourceVoice->SubmitSourceBuffer( &buffer );
	}
	g_pSourceVoice->Start( 0 );
	
	Sleep( 5000 );
	
	/********************/
	/**** Stop Sound ****/
	/********************/
	g_pSourceVoice->Stop( 0 );
	g_pSourceVoice->FlushSourceBuffers();
	g_pSourceVoice->SubmitSourceBuffer( &buffer );
	
	/*****************/
	/**** Cleanup ****/
	/*****************/
	g_pSourceVoice->Stop( 0 );
	
	g_pSourceVoice->DestroyVoice();
	
	free( pDataBuffer );
	
	g_pMasteringVoice->DestroyVoice();
	
	if ( g_pXAudio2 ) g_pXAudio2->Release();
	
	CoUninitialize();
	
	return 0;
}



//-----------------------------------------------------------------
//    Find Chunk.
//-----------------------------------------------------------------
HRESULT FindChunk( HANDLE hFile , DWORD forucc , DWORD *dwChunkSize , DWORD *dwChunkDataPosition )
{
	HRESULT hr;
	
	DWORD dwRead;
	DWORD dwChunkType;
	DWORD dwChunkDataSize;
	DWORD dwRIFFDataSize = 0;
	DWORD dwFileType;
	DWORD bytesRead = 0;
	DWORD dwOffset = 0;
	
	hr = S_OK;
	
	if ( SetFilePointer( hFile , 0 , NULL , FILE_BEGIN ) == INVALID_SET_FILE_POINTER )
		return HRESULT_FROM_WIN32( GetLastError() );
	
	while( hr == S_OK ){
		if ( ReadFile( hFile , &dwChunkType , sizeof( DWORD ) , &dwRead , NULL ) == 0 )
			hr = HRESULT_FROM_WIN32( GetLastError() );
		if ( ReadFile( hFile , &dwChunkDataSize ,
		     sizeof( DWORD ) , &dwRead , NULL ) == 0 )
			hr = HRESULT_FROM_WIN32( GetLastError() );
		switch( dwChunkType ){
			case fourccRIFF:
				dwRIFFDataSize  = dwChunkDataSize;
				dwChunkDataSize = 4;
				if ( ReadFile( hFile , &dwFileType ,
				     sizeof( DWORD ) , &dwRead , NULL ) == 0 )
					hr = HRESULT_FROM_WIN32( GetLastError() );
				break;
			default:
				if ( SetFilePointer( hFile , dwChunkDataSize ,
				     NULL , FILE_CURRENT ) == INVALID_SET_FILE_POINTER )
					return HRESULT_FROM_WIN32( GetLastError() );
		}
		dwOffset += sizeof( DWORD ) * 2;
		if ( dwChunkType == forucc ){
			*dwChunkSize         = dwChunkDataSize;
			*dwChunkDataPosition = dwOffset;
			return S_OK;
		}
		dwOffset += dwChunkDataSize;
		if ( bytesRead >= dwRIFFDataSize ) return S_FALSE;
	}
	
	return S_OK;
}



//-----------------------------------------------------------------
//    Read Chunk Data.
//-----------------------------------------------------------------
HRESULT ReadChunkData( HANDLE hFile , void *buffer , DWORD buffersize , DWORD bufferoffset )
{
	DWORD dwRead;
	
	if ( SetFilePointer( hFile , bufferoffset ,
	     NULL , FILE_BEGIN ) == INVALID_SET_FILE_POINTER )
		return HRESULT_FROM_WIN32( GetLastError() );
	
	if ( ReadFile( hFile , buffer , buffersize , &dwRead , NULL ) == 0 )
		return HRESULT_FROM_WIN32( GetLastError() );
	
	return S_OK;
}




実行結果

クリックすると実物大で表示されます。