I used
this tutorial
as a starting point. XAudio2 is much simpler, with one odd exception,
than DirectX. I went from knowing nothing about XAudio2 to playing
audio in two hours.
XAudio2 is available on Win8 systems, it is not supported on Win7.
_WIN32_WINNT must be defined to 0x0602 (or
later) to compile.
The debug build caused problems as the debug version of the XAudio2
functions refer to the debug version of the C runtime (CRT) libraries.
This forces the use of the debug multi-threaded libraries (/MTD) for
the entire project, including my support libraries. I do not like
linking to the debug libraries for a number of reasons, and typically
link the release libraries even for debug builds. Rather than change
my entire debug build to use libraries I don't want, it was easier to
use the release version of the XAudio2 library. This was accomplished
by undefining _DEBUG before including XAudio.h.
The XAudio2 functions are linked in by including XAudio2.lib.
XAudio2 uses a COM interface, so I need to call CoInitializeEx()
and CoUninitialize() in WinMain(). There is some global initialization
that needs to be performed, which I do in SoundMgrCreate().
WinMain.c:
int CALLBACK WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR CmdLine, int CmdShow) {
int Err= ERR_OK;
ghInst= hInst;
Print(PRINT_DEBUG,"Rocket %s [%s]",gVerID.BuildStr,gVerID.BuildTime);
CoInitializeEx(0,COINIT_MULTITHREADED);
SoundMgrCreate();
if(IsErr(Err= MainCreate(&ghMain))) {
Err= Error(Err,"WinMain: Unable to create Main window.");
} else {
MainLoop(ghMain);
MainDestroy(ghMain);
}
SoundMgrDestroy();
CoUninitialize();
return(Err);
}
I created a SoundMgr class that handles the global initializations
and keeps track of the individual sounds.
Sound.cpp:
#define SOUND_MAX 32
class SoundMgr {
public:
static SoundMgr *Ptr(HSOUNDMGR hMgr);
static int Create(HSOUNDMGR *phMgr);
int Destroy(void);
int SoundCreate(HSOUND *phSound, const char *Name);
int SoundDestroy(HSOUND hSound);
int Play(HSOUND hSound, UINT Flags);
int Stop(HSOUND hSound);
//Data
UINT Signature;
private:
SoundMgr(void);
~SoundMgr(void);
int Create2(void);
//Data
IXAudio2 *pEngine;
IXAudio2MasteringVoice *pMaster;
HSOUND SoundList[SOUND_MAX];
};
The SoundMgr initialization is simple, just call XAudio2Create()
to create a sound "engine" and then create the mastering voice.
Sound.cpp:
int SoundMgr::Create2(void) {
int Err= ERR_OK;
HRESULT hErr;
if(!SUCCEEDED(hErr= XAudio2Create(&pEngine))) {
Err= Error(ERR_SYSCREATE,"SoundMgr:Create2: Unable to create XAudio2 engine. [%X]",hErr);
} else if(!SUCCEEDED(hErr= pEngine->CreateMasteringVoice(&pMaster))) {
Err= Error(ERR_SYSCREATE,"SoundMgr:Create2: Unable to create mastering voice. [%X]",hErr);
} else {
//OK
}
return(Err);
}
A Sound class instance is created for each sound effect and they
can be created and destroyed as needed. The sounds are managed by the
SoundMgr.
Sound.cpp:
class Sound {
public:
static Sound *Ptr(HSOUND hSound);
static int Create(HSOUND *phSound,const char *Name,IXAudio2 *pEngine);
int Destroy(void);
int Play(UINT Flags);
int Stop(void);
//Data
UINT Signature;
private:
Sound(void);
~Sound(void);
int Create2(const char *Name,IXAudio2 *pEngine);
//Data
Wave Src;
IXAudio2SourceVoice *pVoice;
bool IsPlaying;
};
int SoundMgr::SoundCreate(HSOUND *phSound, const char *Name) {
int Err= ERR_OK;
UINT n1;
for(n1=0;n1<SOUND_MAX && SoundList[n1];n1++);
if(n1>=SOUND_MAX) {
Err= Warn(ERR_TOO_MANY,"SoundMgr:SoundCreate(%s): Too many sounds [%d]",Name,n1);
} else if(IsErr(Err= Sound::Create(&SoundList[n1],Name,pEngine))) {
Err= Warn(Err,"SoundMgr:SoundCreate(%s): Unable to create sound.",Name);
} else {
*phSound= SoundList[n1];
}
return(Err);
}
int SoundMgr::SoundDestroy(HSOUND hSound) {
int Err= ERR_OK;
UINT n1;
Sound *pSound= Sound::Ptr(hSound);
if(!pSound) {
Err= ERR_BADHANDLE;
} else {
Err= pSound->Destroy();
for(n1=0;n1<SOUND_MAX && SoundList[n1]!=hSound;n1++);
if(n1<SOUND_MAX)
SoundList[n1]= 0;
}
return(Err);
}
int SoundMgr::Play(HSOUND hSound, UINT Flags) {
int Err= ERR_OK;
Sound *pSnd= Sound::Ptr(hSound);
if(!pSnd) {
Err= ERR_BADHANDLE;
} else {
Err= pSnd->Play(Flags);
}
return(Err);
}
int SoundMgr::Stop(HSOUND hSound) {
int Err= ERR_OK;
Sound *pSnd= Sound::Ptr(hSound);
if(!pSnd) {
Err= ERR_BADHANDLE;
} else {
Err= pSnd->Stop();
}
return(Err);
}
I create two sounds: hSndFire for when a bullet is fired, and
hSndThrust that plays continuously while the rocket's engine is "flame
on".
Game.cpp:
int Game::Create2(void) {
int Err2,Err= ERR_OK;
if(IsErr(Err2= SoundCreate(&hSndFire,"Resource\\Fire.wav")))
Warn(Err2,"Game:Create2: Unable to create Fire sound.");
if(IsErr(Err2= SoundCreate(&hSndThrust,"Resource\\Thrust.wav")))
Warn(Err2,"Game:Create2: Unable to create thrust sound.");
if(IsErr(Err= ItemsCreate(&hItems,(HGAME)this))) {
Err= Error(Err,__FUNCTION__": Unable to create Items.");
} else if(IsErr(Err= CreateItems())) {
Err= Error(Err,__FUNCTION__": Unable to create starting items.");
}
return(Err);
}
int Game::Destroy(void) {
int Err= ERR_OK;
SoundDestroy(hSndFire);
SoundDestroy(hSndThrust);
delete this;
return(Err);
}
I play the sounds during the Game update. The Fire sound is
a one-shot sound, so calling Play() while it is already playing will
have no effect and there will be no overlapping sounds. Alternatively,
I could create multiple instances of the fire sound and cycle through
them to create overlapped sound effects.
The Thrust sound is played in a loop for as long as the player
is firing the rocket engine.
The logic behind whether to actually start or stop a sound is
handled inside the Sound class, so there is no harm in stupidly
calling Play() and Stop().
Creating a sound requires first determining data format of the
audio samples, then feeding it to XAudio2. This is actually the most
complex part of getting started. I was surprised that XAudio2 does not
have an API that accepts a file handle to a WAV file. Especially since
XAudio2 CreateSourceVoice() takes the WAVEFORMATEX header verbatim.
Instead, I need to write the code to parse the WAV header, extract the
WAVEFORMATEX chunk, and set the pointers to the beginning of the audio
sample data.
Fortunately, Jay Tennant was kind enough to provide that code in
the Wave.h file. The Wave class expects the audio to be in an external
file; I will need to rewrite it if I want to generate my own waveforms
or read the data from a resource. It is easier to manage the audio
assets as separate files, so this approach makes sense even for
production releases.
Assuming the file parser is already done, creating the Sound
object is easy.
Only 8 or 16 bit, mono or stereo, audio is supported. Calling
CreateSourceVoice() with 32bit audio will fail with a generic
(unhelpful) error code.
Playing the sound is also easy. The only trick here is handling
both one-shot and looping audio. Calling Play() on a one-shot sound
that is still playing (still has at least one pending audio buffer)
will return immediately, allowing the prior sound to complete.
Calling Play() on a looping sound that is still playing will also
return immediately.
There needs to be a matching Stop() for every Start(), so if
IsPlaying is set and there are no pending buffers I call Stop() just
to be polite.
This function assumes the entire sound is found in a single memory
buffer. I create a copy of the original XAUDIO2_BUFFER because I need
to set additional parameters if the sound is looped.
I start the audio voice and submit the buffer. SubmitSourceBuffer()
will return immediately and the audio will play asynchronously.
Stop() is simple, in all cases I simply call pVoice->Stop() and
clear the IsPlaying flag.
That is all there is to it, for basic audio playback. I found it
was fun and easy to record my own sound effects by simply making
noises with my voice and then tweaking the audio.
WebV7 (C)2018 nlited | Rendered by tikope in 36.322ms | 44.220.184.63