Playing Audio in XNA using XACT

Audio After a long pause, I'm finally getting back to my series of XNA posts which I started for the XNA contest @ Unive. This time it's about audio in the XNA framework, using the Microsoft Cross-Platform Audio Creation Tool (XACT).

XACT interface
The main XACT interface.

This application, which comes bundled with the XNA Game Studio (latest edition 3.0 is currently in beta), allows you to import audio files, group them together in "sounds" and "cues", editing settings like volume, frequency and doppler pitch, and finally export everything to the binary files that can then be imported into Windows or XBox 360 games.

Sound banks and Cues

All audio files are loaded into one or more Wave banks, simple collections of audio files. Each wave bank will be exported as a .xwb file and will contain the audio data from the imported files. You can set how the audio files are loaded at runtime (either by streaming from the disk for large files like music or loading everything up front for shorter audio clips) and the compression settings (including the format used to store the final audio data, either XMA or PCM) for the whole bank or for individual files.

Since a wave bank really is nothing more than a list of raw audio data, XACT provides a mean to organize the data and create single elements that can be referenced and played by an XNA game. To do this, you must create a Sound bank:

Sound bank editor

First of all, the sound bank editor allows you to create "Sounds": a collection of tracks with individual properties (like 3d positioning, volume, pitch and so on) which play one or more audio file (from one of the wave banks). Each "sound" can be edited as you like, can include multiple waves, set different volumes, delays, and so on...

Finally, in the lower part of the editor, you can create "Cues". These items have an unique name (used at runtime to reference them from your code) and a playlist of "sounds" that will be played sequentially or randomly with a certain probability. For instance, in a fighting game, you could create a cue which is played every time a character is hit, randomly choosing one of the many "humph" sounds. Long and articulated grunts could set to be less probable than shorter sounds.

To sum it up:

The hierarchy of XACT items: Cues > Sounds > Tracks > Waves

All XACT items follow this hierarchy: cues contain one or more sounds, each of which can be composed of one or more tracks with their own properties, which play exactly one of the wave files loaded into a wave bank.

Testing sounds and cues

In order to test the sound banks and the cues you created with XACT, you'll need to connect the tool to an "auditioning server" which will forward the raw audio data to the Windows default audio playback device. To do so, launch the XACT Auditioning Utiliy:

The XACT Auditioning Utiliy console

As soon as you click on "play" in XACT, the tool will connect to the auditioning server and play the cue you selected. XACT will perfectly reproduce all settings as if you played the sound at runtime in an XNA game.

Put audio inside your game

Now that we have an XACT project file (.xap) you can simply add it to the XNA content pipeline. Just remember that all source wave files (XACT supports .wav, .aiff and .xma files) must be available to the pipeline compiler and that XACT references the file with relative paths from where the .xap file is stored.

As soon as you compile the XNA game, the XACT project will be converted correctly into the Windows or the XBox 360 binary files.

To load and play cues from code you must do the following things:

  1. Instantiate an AudioEngine, loading the compiled .xgd file,
  2. Instantiate a WaveBank for each wave bank and a SoundBank for each sound bank you have in your project,
  3. Call AudioEngine.Update() regularly while your game engine runs (this assures that all sounds are correctly played and streamed from disk),
  4. Call SoundBank.PlayCue(string cueName) when you want to play one of the cues referencing them by their name.

The best thing would be to create a separate GameComponent to automatically do all that, while exposing a service interface to play the correct cues when needed.

Sample code

class AudioComponent : GameComponent, IAudioService { public AudioComponent(Game game) : base(game) { game.Services.AddService(typeof(IAudioService), this); } AudioEngine engine; WaveBank waveBank; SoundBank soundBank; public override void Initialize() { //Load audio system try { engine = new AudioEngine(@"Content/Audio/ContestAudio.xgs"); waveBank = new WaveBank(engine, @"Content/Audio/WaveBank.xwb"); soundBank = new SoundBank(engine, @"Content/Audio/SoundBank.xsb"); } catch (Exception) { //Unable to load sounds Enabled = false; } base.Initialize(); } public override void Update(GameTime gameTime) { if (Enabled) { engine.Update(); } base.Update(gameTime); } protected override void Dispose(bool disposing) { if(soundBank != null) soundBank.Dispose(); if(waveBank != null) waveBank.Dispose(); if(engine != null) engine.Dispose(); base.Dispose(disposing); } #region IAudioService Members public void PlayVictory() { if (Enabled) soundBank.PlayCue("VictorySound"); } //whatever... #endregion }