DirectSoundによるサウンド再生実装方法

概要

最終更新日:2020/03/07

DirectSoundでサウンドを再生するための内容を書いています。
この記事は以下の内容を知りたい方に向けて書いています。
  • DirectSoundの仕組みが知りたい
  • DirectSoundを使うための環境構築の設定が知りたい
  • DirectSoundの初期化方法が知りたい
  • セカンダリバッファの作成方法が知りたい
  • サウンドの再生方法が知りたい
  • サウンドの停止方法が知りたい
  • DirectSoundの解放方法が知りたい
Wavファイルの読み込みについては別記事にします。

サンプル

サンプルはこちらからダウンロードできます。
開発環境については以下の内容です。

開発環境
VSのバージョン VisualStudio 2019
DirectXのバージョン DirectX9


DirectSoundとは

DirectSoundとはDirectXの中の音を専門に扱うAPIです。
APIの構成は単純なので初期化や再生、解放はそこまで難しくなりません。

DirectSoundの構成

DirectSoundは大元であるDirectSoundインターフェースと、
「プライマリバッファ」「セカンダリバッファ」の
二種類のバッファによって構成されています。

directx_0178

DirectSoundインターフェース

DirectSoundインターフェースを使用することで二種類のバッファの作成や、
サウンド環境の調査などを行うことができます。

プライマリバッファ

プライマリバッファはスピーカーなどのサウンドデバイス側のバッファです。
各デバイスはこのバッファに書き込まれたデータを使用して、音を鳴らします。

directx_0179

セカンダリバッファ

セカンダリバッファはアプリ側にあるバッファで、BGMやSEの数だけ
用意することができます。
再生の手順は以下の通りです。
  1. バッファ一つ一つに対して再生や停止の命令を行う
  2. 各バッファの音データを合成(ミキシング)する
  3. 合成された音データをプライマリバッファに送信する
  4. プライマリバッファがサウンドデバイスで音を鳴らす
directx_0180

環境設定

DirectSoundを実装するためにはプロジェクトで以下の設定を行います。
  • パス設定
  • libファイル設定

パス設定

DirectXのincludeファイルやlibファイルがどこにあるかの設定が必要です。
設定はメニューの「デバッグ」=>「プロジェクト名のプロパティ」で行います。
	
directx_0168
プロパティウィンドウが表示されるので「構成プロパティ」=> 「VC++ディレクトリ」を
クリックしてインクルードディレクトリとライブラリディレクトリに
パス設定を行います。

directx_0017

共通

パスの設定はまず設定したい項目をクリックすると「↓」アイコンが右端に
表示されるのでそちらをクリックします。

directx_0018

クリック後「編集」と書かれた項目が新しく表示されるので、クリックします。

directx_0019

パス設定をするウィンドウが新たに表示ます。
この画面の上部にある白い部分をダブルクリックすると右端に「…」が
表示されるのでそちらをクリックします。

directx_0020

参照先ディレクトリを決めるためのウィンドウが表示されるので、
DirectXが置かれている場所まで移動してパスを設定します。

インクルードディレクトリ

インクルードディレクトリではDirectXディレクトリの中にある
Includeディレクトリを指定します。

directx_0021

ライブラリディレクトリ

ライブラリディレクトリではDirectXディレクトリの中にある
Libファイルの中の「x86」か「x64」いずれかを指定します。

directx_0022

これはVisualStudio上段に「x86」または「x64」と書かれている項目があるので
そちらを確認して書かれている方を指定してください。

directx_0023

libファイルの設定

パスの設定が完了したら次はlibファイルの設定を以下の方法のどちらかで行います。
  • プロパティ指定
  • コード

プロパティ指定

プロパティ指定はメニューの「デバッグ」=>「プロジェクト名のプロパティ」=>
「リンカ」=>「入力」項目の「追加の依存ファイル」で行います。

directx_0024

ここにDirectSoundでは以下のlibファイルを追加します。
  • dinput8.lib
  • dxguid.lib
  • winmm.lib

directx_0162

コード指定

コード指定の場合はファイルに以下のコードを追加します。

#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "winmm.lib")

初期化

DirectSoundの初期化は以下の手順で行います。
  1. DirectSoundインターフェースの生成
  2. 協調レベルの設定
プライマリバッファやセカンダリバッファも初期化の範囲ですが、
プライマリバッファは音を再生することを目的としたこの記事では
わざわざ作成する必要ははないと判断して、作成していませんが、
プライマリバッファを開発側で作成していない場合は自動で作成されます。

セカンダリバッファはサウンドファイル読み込み処理のタイミングで作成します。

DirectSoundインターフェースの生成

DirectSoundインターフェースの生成は「DirectSoundCreate8関数」を使用して行います。

directx_0163
// DirectSoundの生成
if (FAILED(DirectSoundCreate8(
	NULL,			// GUID
	&g_SoundInterface,	// 生成したDirectSound保存先
	NULL)))			// NULL固定
{
	return false;
}

協調レベルの設定

インターフェースの生成が成功したら協調レベルの設定をします。
協調レベルは三種類あり、プライマリバッファの設定に対して
ほとんど固定になっていて変更できないレベルや、
細かい設定ができるレベルがあります。
  • 標準協調レベル(DSSCL_NORMAL)
  • 優先協調レベル(DSSCL_PRIORITY)
  • 書き込み優先レベル(DSSCL_WRITEPRIMARY)
この記事では再生することを目的としているので
「標準協調レベル(DSSCL_NORMAL)」を使用しています。

標準協調レベル(DSSCL_NORMAL)

標準協調レベルでは以下のプライマリバッファを使用します。
  • 22KHz
  • ステレオ
  • 8ビットサンプリング
この設定は固定となっているので、プログラム中で変更はできません。

優先協調レベル(DSSCL_PRIORITY)

優先協調レベルは標準協調レベルよりもプライマリバッファに対して
設定できる項目が増えています。
サンプリングレートと深度ビットをアプリ側で設定可能です。

書き込み優先レベル(DSSCL_WRITEPRIMARY)

書き込み優先レベルはプライマリバッファに直接アクセスできるようになりますが、
セカンダリバッファを使用することができなくなるので、
サウンドに対して、ある程度の知識と技術を求められます。

協調レベル設定の実装

協調レベルの設定はIDirectSound8の「SetCooperativeLevel関数」で行います。

directx_0164
// 協調レベルの設定
if (FAILED(g_SoundInterface->SetCooperativeLevel(
	FindWindow(WINDOW_CLASS_NAME, nullptr),		// ウィンドウハンドル
	DSSCL_NORMAL)))					// 協調レベル
{
	return false;
}

セカンダリバッファの作成

セカンダリバッファの作成は以下の手順で行います。
  1. サウンドファイルの読み込み
  2. バッファ作成
  3. サウンドデータの反映

サウンドファイルの読み込み

セカンダリバッファの役割は保存されたサウンドデータを使って音を鳴らすことです。
その為には、バッファにサウンドデータを保存させなければいけないので、
バッファ作成手順の一番最初にサウンドファイルの読み込みを設定しています。

しかし、サウンドファイルの読み込みはDirectSoundではサポートしていないので
開発側でファイルを読み込まなければいけません。

更にバッファとして使用できるのはWAVファイルのPCM形式なので、
WAVファイルを読み込むか、MP3やOGGをデコードしてPCM形式にする必要があります。
グラフィックで例えるとBMPしか使えないので、BMPを読み込むか、
JPGやPNGの圧縮を解凍してBMP形式として扱う必要があるということです。

WAVファイルの読み込み方法はこちらの記事で書いています。

ファイルを読み込む上で、バッファ作成には以下の情報が必要です。
  • サウンドデータ
  • サウンドデータのサイズ(チャンクサイズ)
  • WAVフォーマット情報(サンプリングレートなど)

バッファ作成

バッファの作成は「DSBUFFERDESC構造体」でバッファの設定を行い、
IDirectSoundBufferの「CreateSoundBuffer関数」で作成します。

directx_0169
directx_0170
// バッファ情報の設定
DSBUFFERDESC dsbd;
ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME;
dsbd.dwBufferBytes = dwWavSize;
dsbd.guid3DAlgorithm = DS3DALG_DEFAULT;
dsbd.lpwfxFormat = &wfex;

// セカンダリバッファ作成
if (FAILED(g_SoundInterface->CreateSoundBuffer(
	&dsbd,				// バッファ情報
	&g_SoundBufferList[file_id],	// 作成されたバッファの保存先
	NULL)))
{
	// 作成失敗
	return false;
}

DSBUFFERDESC構造体の説明からします。
設定のポイントとなるメンバは「dwFlags」「dwBufferBytes」「lpwfxFormat」です。
dwFlagsは作成されたバッファが変更できる項目の指定をします。
今回は「DSBCAPS_CTRLPAN」がパンで「DSBCAPS_CTRLVOLUME」がボリュームの
変更設定になっており、作成されたバッファではこれらの項目の変更が可能です。
他にも数多くの設定があるので、必要とする設定を行ってください。

dwBufferBytesはバッファのサイズです。
このサイズはサウンドデータのサイズを指しており、
サウンドファイルの読み込みで取得できます。

最後のlpwfxFormatはWAVフォーマット情報です。
WindowsでWAVファイルを取り扱うmmioというAPIがあるのですが、
そのAPIで用意されているWAVEFORMATEX構造体を指定します。
こちらもサウンドファイルの読み込みで取得できます。
また、WAVEFORMATEX構造体の変数を全て開発側で設定しても
設定が有効なら問題ありません。

CreateSoundBuffer関数は特に難しいことはありません。
作成するバッファの情報と作成されたバッファを保存する変数を指定するだけです。

サウンドデータの反映

セカンダリバッファの作成したら、バッファにサウンドデータの反映をします。
バッファの反映はIDirectSoundBufferの「Lock関数」でアクセス許可を得て、
データを反映し、「Unlock関数」でアクセス許可を解除します。
この二つの関数はセットになっているのでLockしたらUnlockを忘れないでください。

directx_0186
directx_0187
void* buffer;
DWORD buffer_size;
// 波形データを書き込むためにセカンダリバッファをロックする
if (FAILED(g_SoundBufferList[file_id]->Lock(
	0,		// オフセット
	wav_data.Size,	// ロックするバッファサイズ
	&buffer,	// ロックされたバッファパート1の保存先
	&buffer_size,	// ロックされたバッファパート1のサイズ
	NULL,		// ロックされたバッファパート2の保存先
	NULL,		// ロックされたバッファパート2のサイズ
	0)))		// ロックオプション
{
	// ロック失敗
	delete[] wav_data.SoundBuffer;
	return false;
}

// バッファにデータを反映
memcpy(buffer, wav_data.SoundBuffer, buffer_size);

// バッファをアンロックする
g_SoundBufferList[file_id]->Unlock(
	&buffer,	// アンロックするバッファパート1
	buffer_size,	// パート1のバッファサイズ
	NULL,		// アンロックするバッファパート2
	NULL);		// パート2のバッファサイズ

Lock関数はロックするサイズが大きければ、バッファを二つのパートに分けます。
そのため、引数にはロックされたバッファとサイズの情報を保存するための
引数指定項目が二つ用意されています。
ただ、必要が無ければパート2の引数にNULLを指定すれば分割されません。

ロックが完了したら、サウンドデータをバッファに反映します。

反映が完了したらUnlock関数でロックを解除します。
解除にはロックしたバッファ情報を渡すだけです。

これで、セカンダリバッファの作成は完了です。

再生

サウンドの再生はIDirectSoundBufferの「Play関数」で行います。

directx_0165
// 再生
g_SoundBufferList[file_id]->Play(
		0,				// 常に0
		0,				// 優先順位
		DSBPLAY_LOOPING & loop_bit);	// ループ設定


第三引数のフラグ設定はBGMやSEに対してはDSBPLAY_LOOPINGで
ループ設定をするかしないかだけを意識してもらえたら問題ありません。

停止

サウンドの停止はIDirectSoundBufferの「Stop関数」で行います。

directx_0166
// 停止
g_SoundBufferList[file_id]->Stop();

再生位置指定

再生位置の指定はIDirectSoundBufferの「SetCurrentPosition関数」で行います。

directx_0167
// 停止
g_SoundBufferList[file_id]->Stop();

Stopはあくまでバッファの再生を止めるだけです。
その為、バッファの再生位置は変わりません。
もし、音を頭から再生したい場合はSetCurrentPositionで
「0」を指定する必要があります。

解放

DirectSoundインターフェースとサウンドデータは「Release関数」で
不要になったタイミングで必ず解放を行います。

directx_0042
// セカンダリバッファの解放
for (int i = 0; i < SoundFile::SoundFileMax; i++)
{
	if (g_SoundBufferList[i] != NULL)
	{
		g_SoundBufferList[i]->Stop();
		g_SoundBufferList[i]->Release();
		g_SoundBufferList[i] = NULL;
	}
}

// DirectSoundインターフェースの解放
if (g_SoundInterface != NULL)
{
	g_SoundInterface->Release();
	g_SoundInterface = NULL;
}