XACT3 Demo
XACT3
is a great tool that allows developers to focus on game-play rather
than on audio technology and hardware. XACT3 is so easy to use that all
it takes to get simple sounds up and playing is to invoke the audio by
cue name. At the minimum this could translate to a line of code to play a
sound once it has been loaded. We recommend that you take the time to
open up XACT3 and explore the GUI tool to see what you can do with it.
Once you are comfortable using it, you can do a lot with audio in your
gaming projects. As you become more advanced in game development audio,
you might need to use XAudio2 since it offers a lower level of control
than XACT3 does.
The header file for the
XACT3 API is <xact3.h> and is part of the DirectX SDK, so no
additional setup is necessary to begin coding with XACT. In code you’ll
need three objects: a wave bank, a sound bank, and an audio engine.
The wave bank is represented by the DirectX type IXACT3WaveBank, and the sound bank is represented by IXACT3SoundBank.
When you load wave and sound bank files, you load their contents into
objects of these two types. The audio engine is an object that is
created to process and control everything dealing with XACT3. This
object is of the type IXACT3Engine. For
readers familiar with earlier versions of XACT, the only difference in
these names is the addition of the number 3 after XACT (for example, IXACT3Engine instead of the previous IXACTEngine).
In the XACT demo’s main
source file these three objects are created and defined in the global
section. A structure was created to hold the wave bank and sound bank in
one object along with void pointers. The void pointers will be memory
mapped data pointing to the file’s data, which we’ll discuss later in
this section. The global section from the XACT demo is shown in Listing 1.
Listing 1. The Global Section of the XACT Demo’s Main Source File
struct stXACTAudio { IXACT3WaveBank *m_waveBank; IXACT3SoundBank *m_soundBank; void *m_waveBankData; void *m_soundBankData; };
stXACTAudio g_xactSound; IXACT3Engine *g_soundEngine = NULL;
|
A
few functions are created in the demo to make the setup and loading of
XACT3 and the XACT files straightforward. In the first function, called SetupXACT(), the first step is to initialize COM with a call to CoInitializeEx().COM must be initialized to use COM libraries, which include many DirectX SDK libraries like XACT.
The next step is to create the XACT3 audio engine. This is done with a call to XACT3CreateEngine(), and it takes as parameters the creation flags and a pointer to the IXACT3Engine object that will be created by the function. The creation flags can be 0 to specify no additional flags, or they can be XACT_FLAG_API_AUDITION_MODE to create the audio engine in audition mode or XACT_FLAG_API_DEBUG_MODE to specify debug. You can use the logical OR operator to combine the two flags.
The remainder of the SetupXACT()
function initializes the audio engine and loads the wave and sound
banks. The loading of the wave and sound banks is done in separate
functions that will be discussed later. The initialization of the XACT
audio engine is done by calling the IXACT3Engine object’s Initialize() function. This function takes runtime parameters that are specified by the XACT_RUNTIME_PARAMETERS structure. This structure can be seen as follows.
typedef struct XACT_RUNTIME_PARAMETERS {
DWORD lookAheadTime;
void *pGlobalSettingsBuffer;
DWORD globalSettingsBufferSize;
DWORD globalSettingsFlags;
DWORD globalSettingsAllocAttributes;
XACT_FILEIO_CALLBACKS fileIOCallbacks;
XACT_NOTIFICATION_CALLBACK fnNotificationCallback;
PWSTR pRendererID;
IXAudio2 *pXAudio2;
IXAudio2MasteringVoice *pMasteringVoice;
} XACT_RUNTIME_PARAMETERS, *LPXACT_RUNTIME_PARAMETERS;
Some of the runtime parameters deal with XAudio2. The SetupXACT() function is shown in Listing 2.
Listing 2. The SetupXACT() Function from the XACT Demo
bool SetupXACT(char *waveBank, char *soundBank) { ZeroMemory(&g_xactSound, sizeof(stXACTAudio));
if(FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) return false;
if(FAILED(XACT3CreateEngine(XACT_FLAG_API_AUDITION_MODE, &g_soundEngine))) return false;
if(g_soundEngine == NULL) return false;
XACT_RUNTIME_PARAMETERS xparams = {0}; xparams.lookAheadTime = 250;
if(FAILED(g_soundEngine->Initialize(&xparams))) return false;
if(!LoadWaveBank(waveBank)) return false;
if(!LoadSoundBank(soundBank)) return false;
return true; }
|
The loading of the wave and sound banks is performed in the LoadWaveBank() and LoadSoundBank()
functions defined in the demo’s main source file. To load the file’s
data, the demo uses memory-mapped file handling. This is a fast way to
load memory data into XACT, and the functions are Win32 functions that
are fairly straightforward to understand.
To start, a call to CreateFile()
is used to create a file handle. This function takes the file’s name,
the access flag that specifies the type of object, the shared flag that
specifies how subsequent calls are allowed to access the file while the
file handle is still active, security attributes (which are ignored, so
they’re not used and can be set to NULL), creation flags, attributes flags, and the template file, which is also not used and therefore can be set to NULL.
The next part of the function retrieves the file size with a call to GetFileSize(),
which takes the file handle and a pointer to a variable where the
high-order double-word of the file size is returned. The last parameter
can be set to NULL, and the return value of the function will return the size of the file.
The next step is to create the file mapping by first calling CreateFileMapping(),
which takes as parameters the file’s handle, the optional security
attributes, the file protection with the view, the high order of the
maximum file size, which can be 0, the low order of the maximum file
size, which can be the file size we read before, and the name of the
object, which is optional. The next step in the file mapping is to call MapViewOfFile(),
which takes the file mapping handle, the access flags, the high and low
offsets, and the number of bytes to map. If the number of bytes to map
is 0, then the mapping extends to the end of the file.
With a pointer to the mapped file data, the XACT3 audio engine is ready to load its contents. This is done with a call to CreateInMemoryWaveBank() and CreateInMemorySoundBank()
to create in-memory banks. Both functions take as parameters void
pointers to the mapped file data, the size of the file, a flag that can
be 0 or XACT_FLAG_API_CREATE_MANAGEDATA
to specify that the data is freed when the wave bank is released (which
we must do since we are using mapped data and must unmap it first),
memory buffer allocation attributes, and the out pointer address to the
bank that will be created. The LoadWaveBank() and LoadSoundBank() functions are shown in Listing 3.
Listing 3. The XACT Demo’s LoadWaveBank() and LoadSoundBank()
bool LoadWaveBank(char *fileName) { HANDLE file = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if(file == INVALID_HANDLE_VALUE) return false; DWORD fileSize = GetFileSize(file, NULL);
if(fileSize == -1) { CloseHandle(file); return false; } HANDLE mapFile = CreateFileMapping(file, NULL, PAGE_READONLY, 0, fileSize, NULL);
if(!mapFile) { CloseHandle(file); return false; }
void *ptr = MapViewOfFile(mapFile, FILE_MAP_READ, 0, 0, 0);
if(!ptr) { CloseHandle(mapFile); CloseHandle(file); return false; }
g_xactSound.m_waveBankData = ptr;
if(FAILED(g_soundEngine->CreateInMemoryWaveBank( g_xactSound.m_waveBankData, fileSize, 0, 0, &g_xactSound.m_waveBank))) { CloseHandle(mapFile); CloseHandle(file); return false; }
CloseHandle(mapFile); CloseHandle(file);
return true; }
bool LoadSoundBank(char *fileName) { …
g_xactSound.m_soundBankData = ptr;
if(FAILED(g_soundEngine->CreateSoundBank( g_xactSound.m_soundBankData, fileSize, 0, 0, &g_xactSound.m_soundBank))) { CloseHandle(mapFile); CloseHandle(file); return false; }
CloseHandle(mapFile); CloseHandle(file);
return true; }
|
The last function in the demo is the main()
function. In this function XACT is set up, and the wave and sound banks
are loaded before the audio is played. To play the sound a sound cue is
obtained by calling GetCueIndex() on the sound bank object. This function takes as a parameter the name of the sound cue, which was specified when you created the cue in the XACT GUI tool, and it returns the index of the sound as an XACTINDEX object.
The sound itself is played by calling the sound bank’s Play() function. The Play() function takes as parameters the cue index as an XACTINDEX variable, playback flags, offset start time in milliseconds, and, optionally, a pointer to the address of an IXACTCue object that will be returned by this function.
Alternatively, you can create an
IXACTCue
object and obtain a cue using that. Therefore, to play a sound you just call
Play()
on theIXACTCue
object. To stop it you call
Stop(), and so on.
|
XACT3 requires
frequent updates to the audio engine to work properly. This means that
in the demo we need to specify a loop that keeps calling DoWork() often enough for the audio to play. To accomplish this the XACT demo calls the audio engine’s DoWork()
function inside a loop. Once the sound has finished playing, the loop
breaks, and the application proceeds to exit. To determine if the sound
is playing, we get the audio state of the sound bank by calling GetState(), which returns an unsigned long variable representing the state. We can then test this state for the flag XACT_CUESTATE_PLAYING
to see if the sound bank is playing audio. When it stops playing audio,
the loop terminates, so be sure that inside the XACT3 GUI tool you
don’t specify any sounds to loop infinitely for this demo.
The remainder of the main() function frees all resources and quits. This means the audio engine must be released by first calling the Shutdown() function of the object and the object’s Release() function to free it. It also means we must call CoUninitialize() to uninitialize COM, and we must call UnmapViewOfFile() to unmap the file data that we obtained when we loaded the wave and sound banks. The main() function from the XACT demo is shown in Listing 4.
Listing 4. The XACT Demo’s main() Function
int main(int args, char* argc[])
{
cout << "XACT Demo: Playing clip.wav" << endl << endl;
cout << "Demo will end when the sound is done." << endl << endl;
if(!SetupXACT("Win/Wave Bank.xwb", "Win/Sound Bank.xsb"))
return 0;
XACTINDEX g_clipCue = g_xactSound.m_soundBank->GetCueIndex(
"clip");
unsigned long state = 0;
do
{
g_soundEngine->DoWork();
if(!(state && XACT_CUESTATE_PLAYING))
g_xactSound.m_soundBank->Play(g_clipCue, 0, 0, NULL);
g_xactSound.m_soundBank->GetState(&state);
} while(state && XACT_CUESTATE_PLAYING);