実装方法について
実装にあたって用意したものは2つです。
- XAudio2 でサウンドを再生するためのexe
- ビルドイベントを仕込んだなんかしらのプロジェクト
今回はむかーしむかし作ったDX12のプロジェクトを引っ張り出してきました。
C++プロジェクトでもC#プロジェクトでもビルドイベントが仕込めればなんでもいいです。
実装は至って簡単で以前にMicrosoftが公開していたXAudio2 のサンプルを叩き台にします。
現在、XAudio2 のサンプルプロジェクトのリンクが切れているため
土田さんが公開されているXAudio2 のサンプルプロジェクトを使用します。
中身としてはMicrosoftが公開していたサンプルと同様のものになっています。
こちらの書籍のリンクからサンプルがDL可能です(本も良い本ですので、興味がある方は買ってみるといいかもです)
booth.pm
サンプルをDLして展開するとその中にXAudio2BasicSoundというプロジェクトがあります。
その中にあるXAudio2BasicSound.cppとWAVFileReader.h/cpp を利用します。
WAVFileReader.h/cpp についてはDirectXTKに含まれていますので、こちらから最新版をDLして使用することも可能です。
https://github.com/Microsoft/DirectXTK
新規プロジェクトの作成
まずは新規にC++プロジェクトを作成します。
今回作成するのはコンソールアプリケーションなので、テンプレートからコンソールアプリを指定します。
プロジェクト名はXAudioSandboxとしました。
WAVFileReader.h/cppを使用したいため、プロジェクトに追加します。
XAudio2 の初期化
XAudio2 を利用したサウンドの再生にあたって
XAudio2BasicSound.cpp内にあるPlayWave()とFindMediaFileCch() をそのまま流用するため
コピペで持ってきます。
HRESULT PlayWave(_In_ IXAudio2* pXaudio2, _In_z_ LPCWSTR szFilename);
HRESULT FindMediaFileCch(_Out_writes_(cchDest) WCHAR* strDestPath, _In_ int cchDest, _In_z_ LPCWSTR strFilename);
_Use_decl_annotations_
HRESULT PlayWave(IXAudio2* pXaudio2, LPCWSTR szFilename)
{
}
_Use_decl_annotations_
HRESULT FindMediaFileCch(WCHAR* strDestPath, int cchDest, LPCWSTR strFilename)
{
}
次に、XAudio2を利用するための諸々のincludeを追加します。
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <iostream>
#include <xaudio2.h>
#include <string>
#include <wrl/client.h>
#include "WAVFileReader.h"
#pragma comment(lib,"xaudio2.lib")
ここからXAudio2 を使用してwav再生を行っていきますが
ざっくりと流れを説明すると以下のような流れになります。
XAudio2Create()
↓
CoInitializeEx()
↓
CreateMasteringVoice()
↓
PlayWave()
↓
DestroyVoice()
↓
CoUninitialize()
先ずは先程の流れに沿ってIXAudio2 の初期化処理を追加します。
int main()
{
Microsoft::WRL::ComPtr<IXAudio2> pXAudio2 = nullptr;
UINT32 flags = 0;
HRESULT hr = XAudio2Create(&pXAudio2, flags);
if (FAILED(hr))
{
wprintf(L"Failed to init XAudio2 engine: %#X\n", hr);
return 1;
}
return 0;
}
続いてCOMの初期化、終了処理を追加します。
int main()
{
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
TODO
CoUninitialize();
return 0;
TODOの中ではIXAudio2MasteringVoice の初期化とwavの再生とIXAudio2MasteringVoice の終了処理を行っていきます。
まずはIXAudio2MasteringVoice の初期化と終了処理を追加します。
int main()
{
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
IXAudio2MasteringVoice* pMasteringVoice = nullptr;
if (FAILED(hr = pXAudio2->CreateMasteringVoice(&pMasteringVoice)))
{
wprintf(L"Failed creating mastering voice: %#X\n", hr);
CoUninitialize();
return 1;
}
pMasteringVoice->DestroyVoice();
CoUninitialize();
return 0;
ここまででおおまかなXAudioの初期化と終了処理ができました。
あとはPlaySound() を呼んでwavを再生すれば、ひとまずの音声の再生は完了です。
int main()
{
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
..
if (FAILED(hr = PlayWave(pXAudio2.Get(), )))
{
wprintf(L"Failed creating source voice: %#X\n", hr);
CoUninitialize();
return 1;
}
pMasteringVoice->DestroyVoice();
CoUninitialize();
return 0;
ボイスの再生
新しいフォルダー (2) に含まれる音声データはmp3ですので、適当なツールを使ってwav に変換しておきます。
変換したwav は以下のdirectory構造で配置しました。
+- Root
|
+- XAudioSandbox
| |
| +- Resources
| |
| +- Voice
| |
| +- いいコード書いてる?.wav
| +- ...etc
+- XAudioSandbox.sln
先程のPlayWave内にファイルパスを通してやればひとまずボイスの再生ができることの確認ができるかと思います。
if (FAILED(hr = PlayWave(pXAudio2.Get(), L"Resources\\Voice\\再生したよ~!.wav")))
ここまでのコードを一旦載せておきます。
gist.github.com
次にビルド開始時と終了時で別々のボイスを再生するように処理を分けたいためenumと実行時引数を追加します。
また、実行時の入力引数は1つでよいため指定がない場合や複数指定されている場合はエラーを返すようにします。
enum class BuildType : uint8_t
{
Start,
Finish
};
int main(int argc, char* argv[])
{
if (argc <= 1)
{
wprintf(L"The number of arguments is not enough.\n");
return 1;
}
if (argc > 2)
{
wprintf(L"There are too many arguments, please specify one argument.\n");
return 1;
}
BuildType type = BuildType::Start;
std::wstring file_path = L"Resources\\Voice\\";
std::string option = argv[1];
if (option.compare("start") == 0)
{
TODO
file_path.append(L"いいコード書いてる?.wav");
type = BuildType::Start;
}
else if (option.compare("finish") == 0)
{
TODO
file_path.append(L"コンパイルおわったよ~!.wav");
type = BuildType::Finish;
}
else
{
wprintf(L"Incorrect argument options.\n");
return 1;
}
return 0;
続いて、複数ボイスの中からランダムなボイスの再生について
まずは乱数の生成を行います。
今回は実装がお手軽なメルセンヌ・ツイスタ(mt19937)を使用します。
#include <random>
uint64_t GenerateRandomRange(uint64_t min, uint64_t max)
{
std::random_device device;
static std::mt19937_64 mt64(device());
std::uniform_int_distribution<uint64_t> uniform(min, max);
return uniform(mt64);
}
乱数生成関数はこれだけです。非常にお手軽ですね。
あとは先程のファイルパスの初期化部分で乱数の生成結果に応じて呼び出すファイルを変えてやればランダムなボイスが再生されます。
今回は決め打ちでarrayに突っ込んでいますが、真面目に作るなら特定のdirectory以下のfileからランダム呼び出しを行うなどの変更が必要かなとは思います。
#include <array>
int main(int argc, char* argv[])
{
...
std::array<std::wstring, 5> finishArray
{
L"コンパイルおわったよ~!.wav",
L"コンパイルおわり!その調子!.wav",
L"コンパイルおわり!順調かな?.wav",
L"コンパイルおわり!順調かな?2.wav",
L"コンパイル終わり~!その調子~!.wav",
};
...
else if (option.compare("finish") == 0)
{
type = BuildType::Finish;
file_path.append(finishArray[GenerateRandomRange(0, finishArray.size() - 1)]);
}
...
呼び出すアプリケーションでの設定
アプリケーション側からの設定はVisual Studioのビルド前イベント、ビルド後イベントにexe のpathと実行時引数を与えるだけです。
ビルド前のイベントにはexe へのパスと start の引数を設定しています。
ビルド後のイベントにはexe へのパスと finish の引数を設定しています。
また、ボイスを入れたResourcesフォルダも実行側のプロジェクトから呼び出し可能なdirectoryにコピーしています。
あとは通常通りビルドを行えば音声が再生されます。
最後にコードの全文を載せておきます。
gist.github.com