This notes describe how to use XACT to play back sounds without involving the content creation features provided for use with the audio creation tool. The content creation features are covered in the XACT Content Driven notes. For normal in game sounds XACT is not actually the best API to use as it is a bit limited (see note below). Instead consider using DirectSound.
Note: you require the DirectX SDK December 2006 update or later.
You need to include the XACT header file: xact.h
There are a number of COM objects you can create for XACT. For simple play back you need to create the main XACT object of type: IXACTObject and use it to obtain a wave object of type: IXACTWave.
Before using XACT you need to initialise COM. Make sure you first specify this define:
#define _WIN32_DCOM
Then call the COM initialise function:
CoInitializeEx(NULL, COINIT_MULTITHREADED);
To create the main XACT object and initialise the XACT system you need to call XACTCreateEngine. This call will create an instance of and IXACTEngine and point your passed pointer to it.
Note: make sure you always check for error conditions (the returned HRESULT) - I have not included these below only for reasons of clarity. Please see the notes on tracking DirectX bugs.
HRESULT XACTCreateEngine( DWORD creationFlags, IXACTEngine **engineObject )
Example
IXACTEngine* xactEngineObject=0;
HRESULT hr=XACTCreateEngine(0, &xactEngineObject);
Once you have created a XACT engine object you can call any of its interface functions.
The first task is to initialise the engine. To do this we need to call the Initialise function however before doing so we can retrieve information about available audio device renderers using the GetRenderDetails function of the engine object. In particular we need the audio device id to pass into the initialise function.
HRESULT GetRendererDetails(XACTINDEX index, XACT_RENDERER_DETAILS *details)
Example
XACT_RENDERER_DETAILS rendererDetails;
HRESULT hr=xactEngineObject->GetRendererDetails(0,&rendererDetails);
The structure contains the following:
The initialise function takes a structure containing initialisation parameters.
HRESULT Initialize(const XACT_RUNTIME_PARAMETERS *params)
The XACT_RUNTIME_PARAMETERS structure has many members (see this MSDN link for full details) but for simple play back all we need to fill are two of them:
Example
XACT_RUNTIME_PARAMETERS params;
memset(¶ms,0,sizeof(XACT_RUNTIME_PARAMETERS));
params.lookAheadTime = XACT_ENGINE_LOOKAHEAD_DEFAULT;
params.pRendererID = rendererDetails.rendererID;
HRESULT hr=xactEngineObject->Initialize(¶ms);
All being well you have now initialised the XACT engine for use in your game.
There are two ways we can play back sounds using XACT. We can either load the sound into memory or stream it from disk. We can use the API IXACTEngine interface function PrepareWave. This function allows us to place the sound in memory or stream it from disk. For more advanced uses there are individual functions called: PrepareInMemoryWave and PrepareStreamingWave. These are covered at the end of these notes in the advanced section.
Note: it is rather odd but to play sounds again they need to be prepared again. This seems to limit the XACT code driven API quite a lot and why Microsoft could not have provided a means to 'rewind' I do not know. I did ask them and they said you had to prepare it again and it was not meant to be a replacement for DirectSound. I am hoping future upgrades will improve on this.
HRESULT PrepareWave( DWORD flags, PCSTR filename, WORD packetSize, DWORD alignment, DWORD playOffset, XACTLOOPCOUNT loopCount, IXACTWave **wave )
Example
xactEngineObject->PrepareWave(XACT_FLAG_UNITS_MS, filename.c_str(), 0, 2048, 0, 0, &wave);
Once we have created a IXACTWave interface we can call its interface functions. There are functions to control play back of sounds and also to query the wave:
To play back our sound we can simply call the wave file's play function (IXACTWave::Play) e.g.
wave->Play();
You also need to call IXACTEngine::DoWork(). This should be called regularly - I call it just before I do a render. I have noticed that this call is not always needed when playing sounds from memory but the advise from Microsoft is to call it anyway. Note: naming a function so ambiguously (DoWork) is generally not a good idea!
A sounds playback can be paused using:
HRESULT IXACTWave::Pause(bool paused)
You can stop a sound playing at any time using:
HRESULT IXACTWave::Stop(DWORD flags)
You can change the pitch (frequency) of the sound in real time by calling:
HRESULT IXACTWave::SetPitch(XACTPITCH pitch)
You can alter the volume of the sound by calling:
HRESULT IXACTWave::SetVolume(XACTVOLUME volume)
During play back you can query the state of the wave using:
HRESULT IXACTWave::GetState(DWORD *state)
Properties of the wave can be determined by calling:
HRESULT IXACTWave::GetProperties(XACT_WAVE_INSTANCE_PROPERTIES *properties)
When you have finished with a wave you should destroy it e.g.
wave->Destroy();
Just before your program exits, you should shut down the XACT engine like so:
xactEngineObject->ShutDown();
xactEngineObject->Release();
For more control XACT provides advanced functions for loading files either into memory or streamed. These two functions PrepareInMemoryWave and PrepareStreamingWave are described below:
To prepare a sound for play back from memory we use the following function:
HRESULT IXACTEngine::PrepareInMemoryWave(DWORD flags, WAVEBANKENTRY entry, DWORD *seekTable, BYTE *waveData, DWORD playOffset, XACTLOOPCOUNT loopCount, IXACTWave **wave)
The first step is to create a WAVEBANKENTRY structure and fill in some values. The structure is made up of a union of WAVEBANKMINIWAVEFORMAT and WAVEBANKREGION structures along with some general data.
Example
WAVEBANKENTRY entry;
entry.Format.wFormatTag = WAVEBANKMINIFORMAT_TAG_PCM;
entry.Format.wBitsPerSample = WAVEBANKMINIFORMAT_BITDEPTH_16;
entry.Format.nChannels = numChannels;
entry.Format.wBlockAlign = numChannels * (bitDepth / 8);
entry.Format.nSamplesPerSec = samplingRate;
entry.Duration = numSeconds * samplingRate;
entry.LoopRegion.dwStartSample = 0;
entry.LoopRegion.dwTotalSamples = 0;
entry.PlayRegion.dwOffset = 0;
entry.PlayRegion.dwLength = numBytes
entry.dwFlags = 0;
A typical example might have numChannels as 1, bitDepth as 16 and samplingRate as 44100 (CD standard).
This function does not provide a means of loading WAVE (.wav) files for you - you must do it yourself. Notes on the WAVE file format can be found here: WAVE files. The notes show how to get all the info. needed to fill in the above structure and how to load the raw sound data into a buffer required for the PrepareInMemoryWave call.
The second way of playing sounds is to have them streamed off disk. This may be advisable if you are playing long sounds (e.g. music) that would take up too much memory otherwise. To prepare a sound for streaming we use the following function:
HRESULT IXACTEngine::PrepareStreamingWave(DWORD flags, WAVEBANKENTRY entry, XACT_STREAMING_PARAMETERS streamingParams, DWORD alignment, DWORD *seekTable, DWORD playOffset, XACTLOOPCOUNT loopCount, IXACTWave **wave)
You need to create a streaming structure to pass to the call. This structure describes the streaming parameters of the wave: