クラスまみれのゲームプログラミング入門

33:BGMを再生しよう

 wave形式は波形データがそのままはいっているため、数分だけ再生される音楽だとしても、そのファイルサイズは莫大なものになってしまいます。mp3やaacなどといった圧縮された音楽ファイルでも、waveデータに変換できるのであればDirectSoundでも再生できるのですが、そのための変換処理は自前で用意しなければなりません。そこでBGMの再生には、メディアファイルから直接音楽を再生できるDirectShowを使いたいと思います。

 実はこのメディアファイルを再生するためのAPIは最新バージョンのDirectXでは搭載されなくなり、ウィンドウズ標準のAPIへと移行されています。そのため、DirectShowを利用するためには最新のDirectX SDKではなく、Windows Server 2003以降に対応したWindows SDKをインストールする必要があります(Visual C++ 2008では搭載済み)。

 BGM再生クラスの作成に移りましょう。まずは「dshow.h」をインクルードします……と、言いたいところなのですが、このファイルをインクルードするとなぜかコンパイルエラーになってしまうので、「dshow.h」ファイルからさらにインクルードされているファイルを抜き取って、それらのファイルをインクルードすることでDirectShowを扱えるようにします。ちなみにDirectShowに関するプログラムが収まっているライブラリは「strmiids.lib」です。

define.h
#include <strmif.h>
#include <control.h>
#include <uuids.h>
#pragma comment (lib,"strmiids.lib")

 DirectShowはCOMコンポートネントというプログラムによって成り立っているため、DirectShow関連のインターフェースを作成する前に、COMコンポーネント使用の宣言(CoInitialize)を行います。CoInitializeの引数は予約済みのため、常にNULLを指定します。プログラムが一通り終了したときは、終了の宣言(CoUninitialize)も行わなければいけません。つまり、一連の講座で解説しているグラフィック・効果音・BGMをすべて利用するためには、次のように初期化しなければならないことになります。

Init.cpp
	CGameObject game;
	CoInitialize(NULL);
	game.Initialize(hWnd, hInstance);
	CSound::CreateDirectSound(hWnd);

	/* タスクに追加する処理 */

	ShowWindow(hWnd, nCmdShow);

	MSG msg;
	while(TRUE){
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
			if(msg.message == WM_QUIT) break;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}else{
			game.DoAllTasks();
		}
	}

	game.Uninitialize();
	CSound::ReleaseDirectSound();
	CoUninitialize();

 まずは与えられたメディアファイルを適切な再生データに変換してくれる機能が搭載されているインターフェース「IGraphBuilder」を作成します。

load1.cpp
	HRESULT hr = CoCreateInstance(
		CLSID_FilterGraph,		// COMの識別子
		NULL,					// 特別に取得する情報はないのでNULL
		CLSCTX_INPROC_SERVER,	// このプログラム内でのみ使うことを指定
		IID_IGraphBuilder,		// 取得するインターフェース
		(LPVOID*)&pBuilder);	// 格納先

	if(FAILED(hr)){
		DXTRACE_MSG(_T("IGraphBuilderの作成に失敗しました"));
		return;
	}

 インターフェースの作成に成功したら、再生させたいファイルを指定します。ファイル名はunicodeでしか受け付けないため、ANSIモードでコンパイルする際は文字列を一度unicode形式に変換しなければなりません。

load2.cpp
#ifdef _UNICODE
	pBuilder->RenderFile(filename, NULL);
#else
	wchar_t wfname[MAX_PATH];
	MultiByteToWideChar(CP_ACP, 0, filename, -1, wfname, MAX_PATH);
	pBuilder->RenderFile(wfname, NULL);
#endif

 続いてIGraphBuilderインターフェースから、データ再生を制御するためのインターフェイス「IMediaControl」、再生位置を制御するための「IMediaSeeking」、ボリュームを調整するための「IBasicAudio」を取得します。

load3.cpp
	pBuilder->QueryInterface(IID_IMediaControl, (LPVOID*)&pMediaCtrl);
	pBuilder->QueryInterface(IID_IMediaSeeking, (LPVOID*)&pMediaSeeking);
	pBuilder->QueryInterface(IID_IBasicAudio, (LPVOID*)&pBasicAudio);

 これでメディアファイルを制御するための準備は整いました。ファイルの再生はIMediaControl::Run()、停止はIMediaControl::Stop()で行います。

 以上をふまえて作成したサンプルプログラムはこのようになります。

BGMusic.h
#pragma once
#include "gameobject.h"

#include <strmif.h>
#include <control.h>
#include <uuids.h>
#pragma comment (lib,"strmiids.lib")

class CBGMusic : public CGameObject
{
private:
	IGraphBuilder *pBuilder;
	IMediaControl *pMediaCtrl;
	IMediaSeeking *pMediaSeeking;
	IBasicAudio *pBasicAudio;

	void InitializeBGM();
	void Release();

public:
	CBGMusic();
	CBGMusic(LPCTSTR filename);
	~CBGMusic();

	void Load(LPCTSTR filename);
	void Play();
	void Stop();
};

BGMusic.cpp
#include "BGMusic.h"

void CBGMusic::InitializeBGM()
{
	pBuilder = NULL;
	pMediaCtrl = NULL;
	pMediaSeeking = NULL;
	pBasicAudio = NULL;

	bLoop = FALSE;
	bFading = FALSE;
}

void CBGMusic::Release()
{
	RELEASE(pBasicAudio);
	RELEASE(pMediaSeeking);
	RELEASE(pMediaCtrl);
	RELEASE(pBuilder);
}

CBGMusic::CBGMusic()
{
	InitializeBGM();
}

CBGMusic::CBGMusic(LPCTSTR filename)
{
	InitializeBGM();
	Load(filename);
}

CBGMusic::~CBGMusic()
{
	Release();
}

void CBGMusic::Load(LPCTSTR filename)
{
	Release();

	HRESULT hr = CoCreateInstance(
		CLSID_FilterGraph,		// COMの識別子
		NULL,					// 特別に取得する情報はないのでNULL
		CLSCTX_INPROC_SERVER,	// このプログラム内でのみ使うことを指定
		IID_IGraphBuilder,		// 取得するインターフェース
		(LPVOID*)&pBuilder);	// 格納先

	if(FAILED(hr)){
		DXTRACE_MSG(_T("IGraphBuilderの作成に失敗しました"));
		return;
	}

#ifdef _UNICODE
	pBuilder->RenderFile(filename, NULL);
#else
	wchar_t wfname[MAX_PATH];
	MultiByteToWideChar(CP_ACP, 0, filename, -1, wfname, MAX_PATH);
	pBuilder->RenderFile(wfname, NULL);
#endif

	pBuilder->QueryInterface(IID_IMediaControl, (LPVOID*)&pMediaCtrl);
	pBuilder->QueryInterface(IID_IMediaSeeking, (LPVOID*)&pMediaSeeking);
	pBuilder->QueryInterface(IID_IBasicAudio, (LPVOID*)&pBasicAudio);
}

void CBGMusic::Play(BOOL loop)
{
	if(pMediaCtrl) pMediaCtrl->Run();
}

void CBGMusic::Stop()
{
	if(pMediaCtrl) pMediaCtrl->Stop();
}