DirectSoundで同じサウンドデータを重複して再生する方法

概要

最終更新日:2020/03/07

DirectSoundで同じサウンドデータを重複して再生する方法の説明を書いた記事です。
この記事は以下の内容を知りたい方に向けて書いています。
  • 同じサウンドの重複再生の方法が知りたい
  • 重複再生の実装の流れが知りたい
この記事はDirectSoundで再生処理まで実装出来ていることを前提として書いています。
まだ、そこまで実装出来ていない方はこちらの記事で実装を行ってください。

サンプル

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

開発環境
VSのバージョン VisualStudio 2019
DirectXのバージョン DirectX9
説明 最初一度だけ重複しない形で再生が始まり、
その後は輪唱の形で重複させて再生されます。


バッファの複製

IDirectSoundにはDuplicateSoundBufferという関数があります。
この関数はSoundBufferを複製する関数です。
複製されたバッファは、複製元のサウンドデータのメモリを共有しています。

directx_0188

SEなどは、この関数を使用して作られたバッファで音を再生します。

DuplicateSoundBufferを使用する理由

なぜ、SEなどの一部のサウンドデータはDuplicateSoundBufferを使用した
バッファを使わなければいけないかというと、セカンダリバッファ一つでは
音の重複ができないからです。

例えば、爆発のSEが再生するために、爆発のサウンドデータを再生します。
この音が再生されている最中に爆発SEを再度再生しても何も起きません。
セカンダリバッファのサウンドの再生は再生状態では再生処理が無視されるからです。

このために、SEなどの連続して再生するサウンドデータを効果的に
再生することが出来ないため、DuplicateSoundBuffer関数でバッファを複製して、
そのバッファを使用して再生します。
同じサウンドデータを持つバッファを新しく用意して、
そちらで再生をすれば、重複して同じ音を再生することが出来ます。

実装方法

複製は「DuplicateSoundBuffer関数」を使用します。
使用するタイミングはSE等の重複する音を再生するタイミングです。

directx_0189
for (int i = 0; i < MAX_DUPLICATE; i++)
{
	if (g_DuplicateList[i] == NULL)
	{
		// 再生するバッファの複製
		g_SoundInterface->DuplicateSoundBuffer(
			g_SoundBufferList[file_id],
			&g_DuplicateList[i]);

		// 複製したバッファで再生
		g_DuplicateList[i]->Play(0, 0, 0);
		break;
	}
}

上のコードのように複製したバッファを使用してそのまま再生します。

複製バッファの管理

複製バッファを使用して再生を行う場合は管理処理を作る必要があります。
なぜなら、SEなどで大量に複製されたセカンダリバッファが
たまっていくからです。

使い終わったバッファを有効活用する、再生が終了したら削除するなど、
様々な管理方法が考えられます。
この記事では再生したら削除することを前提とした
配列による管理方法を紹介します。

紹介している方法は管理の最適解ということはなく、
難易度的に最も簡単な方法なので、この方法を選びました。
内容も初期化、追加、削除の最低限の機能の実装についてですので、
管理処理のイメージをつけるために読んでいただけたらと思います。

配列ではなくvectorにするなど、本当に色々なやり方があるので、
各プロジェクトにあったやり方を探して実装してみてください。

実装項目

配列による管理の実装すべき項目は以下の内容です。
  • 配列変数を用意する
  • 初期化する
  • 複製と追加
  • 終了判定をして削除する
  • 終了時に削除する

実装

配列による管理の実装方法の説明をします。
まずは配列変数の用意からです。
配列はある程度大きいサイズの配列を用意します。

// 配列の最大数
#define MAX_DUPLICATE (100)
// 複製バッファ配列
LPDIRECTSOUNDBUFFER g_DuplicateList[MAX_DUPLICATE];

変数が用意出来たら、サウンドの初期化処理を行っているところで
配列の初期化も行います。

// 初期化
ZeroMemory(g_DuplicateList, sizeof(LPDIRECTSOUNDBUFFER) * MAX_DUPLICATE);

バッファの複製は再生のタイミングで行い、配列に追加します。
未使用バッファの判定は要素がNULLになっているかどうかで判断します。

// バッファの複製と追加
for (int i = 0; i < MAX_DUPLICATE; i++)
{
	if (g_DuplicateList[i] == NULL)
	{
		// 再生するバッファの複製
		g_SoundInterface->DuplicateSoundBuffer(
			g_SoundBufferList[file_id],
			&g_DuplicateList[i]);

		// 複製したバッファで再生
		g_DuplicateList[i]->Play(0, 0, 0);
		break;
	}
}

複製したバッファの終了判定は毎フレーム行います。
終了判定はIDirectSoundの「GetStatus関数」で状態を取得して、
再生中でなければRelease関数で解放して、配列にNULLを入れて初期化します。

// 削除判定
for (int i = 0; i < MAX_DUPLICATE; i++)
{
	if (g_DuplicateList[i] == NULL)
	{
		continue;
	}

	// 状態取得
	g_DuplicateList[i]->GetStatus(&status);

	if (status & DSBSTATUS_PLAYING)
	{
		continue;
	}
	else
	{
		g_DuplicateList[i]->Release();
		g_DuplicateList[i] = NULL;
	}
}

最後にシーンの終わりやDirectSoundを解放する際に複製バッファを全て解放します。

// 全解放
for (int i = 0; i < MAX_DUPLICATE; i++)
{
	if (g_DuplicateList[i] == NULL)
	{
		continue;
	}

	g_DuplicateList[i]->Stop();
	g_DuplicateList[i]->Release();
	g_DuplicateList[i] = NULL;
}

これで配列による管理の実装は完了です。