XACT Code Driven

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

COM Objects

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);

Initialising XACT

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 )

  • creationFlags – flags controlling the creation of the engine. Choices are:
    • XACT_FLAG_API_AUDITION_MODE – means that auditioning will be enabled
    • XACT_FLAG_API_DEBUG_MODE – means that debug mode is enabled – very useful for finding problems
  • engineObject – you pass in the address of a IXACTEngine pointer. If successful the function will create an instance of the engine and point your pointer at it.

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)

  • index – the index of the audio device to query. An index of 0 will give you the default device.
  • details – if successful the function fills this structure with details of the audio device

Example

XACT_RENDERER_DETAILS rendererDetails;
HRESULT hr=xactEngineObject->GetRendererDetails(0,&rendererDetails);

The structure contains the following:

  • renderId – an array containing a unique id for the audio device renderer
  • displayName – the name of the device e.g. on my machine it is: ‘SB Audigy 2 ZS Audio [8000]’
  • defaultRenderer – true if this is the default renderer else false

The initialise function takes a structure containing initialisation parameters.

HRESULT Initialize(const XACT_RUNTIME_PARAMETERS *params)

  • params – a XACT_RUNTIME_PARAMETERS structure containing data on how you want XACT initialised

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:

  • lookAheadTime – determines how far ahead the XACT engine will look when determining when to transition to another sound. The value is in milliseconds. For most cases the XACT_ENGINE_LOOKAHEAD_DEFAULT define is fine (250).
  • pRendererId – a pointer to the id of the audio device renderer to use. This is the value we obtained above from calling GetRenderDetails()

Example

XACT_RUNTIME_PARAMETERS params;
memset(&params,0,sizeof(XACT_RUNTIME_PARAMETERS));
params.lookAheadTime = XACT_ENGINE_LOOKAHEAD_DEFAULT;
params.pRendererID = rendererDetails.rendererID;

HRESULT hr=xactEngineObject->Initialize(&params);

All being well you have now initialised the XACT engine for use in your game.

Preparing to play sounds with XACT

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 )

  • flags – for the PC options are XACT_FLAG_UNITS_MS that indicates we will be working with units in milliseconds or  XACT_FLAG_UNITS_SAMPLES that means we will be working with units of samples
  • filename – the path and filename of a WAVE (.wav) file. This is the only format currently supported.
  • packetSize – if this is 0 then the wave is loaded into memory. Other values define the pack size in KB and must be defined in 2 KB increments (since a DVD sector is 2KB). Note that a DVD block is 16 sectors long and is therefore a good size to use. If streaming you need to call GetState to check when the file is prepared before starting to play.
  • alignment – the alignment in bytes of the wave data on the disk. It must be 2048 or a multiple of 2048. 4096 is common (the reason it must be a multiple of 2048 is because that is the alignment of a DVD sector).
  • playOffset – where to start playing the wave. Data is in milliseconds or samples depending on the flags field above
  • loopCount– how many times we want the sound to loop
  • wave – the address of a pointer to an ICAXTWave object. If the function is successful an IXACTWave object is created and your pointer set to point at it.

Example

xactEngineObject->PrepareWave(XACT_FLAG_UNITS_MS, filename.c_str(), 0,  2048, 0, 0, &wave);

Wave Interfaces

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:

Play

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!

Pausing

A sounds playback can be paused using:

HRESULT IXACTWave::Pause(bool paused)

  • paused – true to pause or false to resume

Stopping

You can stop a sound playing at any time using:

HRESULT IXACTWave::Stop(DWORD flags)

  • flags – choice of two:
    • XACT_FLAG_STOP_RELEASE – waits until the sound has played fully then stops
    • XACT_FLAG_STOP_IMMEDIATE – stops the sound immediately

Changing Pitch

You can change the pitch (frequency) of the sound in real time by calling:

HRESULT IXACTWave::SetPitch(XACTPITCH pitch)

  • pitch – a XACTPITCH (short) value ranging from XACTPITCH_MIN (-1200) to XACTPITCH_MAX  (1200)

Changing Volume

You can alter the volume of the sound by calling:

HRESULT IXACTWave::SetVolume(XACTVOLUME volume)

  • volume – a XACTVOLUME (float) value ranging from XACTVOLUME_MIN (0) to XACTVOLUME_MAX (FLT_MAX)

Get State

During play back you can query the state of the wave using:

HRESULT IXACTWave::GetState(DWORD *state)

  • state – will be filled with the wave’s current state, one of:
    • XACT_STATE_CREATED
    • XACT_STATE_PREPARING – still preparing to play
    • XACT_STATE_PREPARED – prepared but not yet playing
    • XACT_STATE_PLAYING
    • XACT_STATE_STOPPING
    • XACT_STATE_STOPPED
    • XACT_STATE_PAUSED – Note: can be combined with some of the other states.
    • XACT_STATE_INUSE – object is in use
    • XACT_STATE_PREPAREFAILED – object preparation failed.

Get Properties

Properties of the wave can be determined by calling:

HRESULT IXACTWave::GetProperties(XACT_WAVE_INSTANCE_PROPERTIES *properties)

  • properties – a pointer to a structure that will be filled with the properties of the wave. This structure contains details about the wave like name, duration, loops, if it is being streamed etc. Full details can be found on MSDN here: XACT_WAVE_INSTANCE_PROPERTIES

Shutting Down

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();

Advanced

For more control XACT provides advanced functions for loading files either into memory or streamed. These two functions PrepareInMemoryWave and PrepareStreamingWave are described below:

Advanced : In Memory

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)

  • flags – for the PC options are XACT_FLAG_UNITS_MS that indicates we will be working with units in milliseconds or  XACT_FLAG_UNITS_SAMPLES that means we will be working with units of samples
  • entry – a WAVEBANKENTRY structure describing details of the wave (see below)
  • seekTable – seek table data. Only relevant for XMA files otherwise set to NULL
  • waveData – a pointer to the raw wave data. Note: the entry parameter play region data specifies the size of this wave data.
  • playOffset – where to start playing the wave. Data is in milliseconds or samples depending on the flags field above
  • loopCount– how many times we want the sound to loop
  • wave – the address of a pointer to an ICAXTWave object. If the function is successful an IXACTWave object is created and your pointer set to point at it.

The WAVEBANKENTRY Structure

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.

  • WAVEBANKMINIWAVEFORMAT – describes the format of the wave file. Members include type of wave (PCM or XMA), number of channels, sample rate and bit depth (8 or 16).
  • WAVEBANKREGION – describes a region within the wave that should be played. Members include a byte offset and byte length for the region.
  • General Data – other data in the structure includes the wave duration (in samples – really it should be named something like numSamples as it sounds to me more like a time value) and some flags. For full details see this MSDN link.

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.

Advanced : Streamed

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)

  • flags, entry, seekTable, playOffset, loopCount and wave are the same as described above for PrepareInMemoryWave
  • streamingParams – a structure instance with details on the file to play (see details below)
  • alignment – the alignment of the wave data on disk. It must be 2048 or a multiple of. 4096 is common (the reason it must be a multiple of 2048 is because that is the alignment of a DVD sector).

The XACT_STREAMING_PARAMETERS Structure

You need to create a streaming structure to pass to the call. This structure describes the streaming parameters of the wave:

  • HANDLE file – a Windows file handle. Must be created using the flags FILE_FLAG_OVERLAPPED  and FILE_FLAG_NO_BUFFERING (when calling CreateFile).
  • DWORD offset – offset within the file to start playing. Note: must be DVD sector aligned (see below)
  • DWORD flags – not used. Set to 0.
  • WORD packetSize – the packet size in sectors to use for each stream. Note that a DVD sector is 2048 bytes long. Therefore if you set packetSize to 2 (the minimum allowed) the packet size in bytes will be 4096. The best size is one that is a multiple of 16. Since a DVD block is 16 DVD sectors in size 16 is the optimal size to use.