XAudio2 Tutorial 2

Author: Jay Tennant

A Brief Look at XAudio2: One Buffer, Multiple Voices

XAudio2 is a sound API available on the Windows Vista/7+ and XBox 360 platforms. This tutorial aims at demonstrating in brevity how to play a sound multiple simultaneous times using one wave buffer in XAudio2. Following tutorials will focus on more interesting usages of the API.

The target audience should be at least intermediate level C++ programming, including the use of pointers. Minimal use of Win32 programming is required. Understanding COM and DirectShow is beneficial, but not required. Previous experience with DirectSound will not help as much as you may think. Sorry. :(

In this series, we use the rule: code first, ask questions later. The sound clip is available here and in the Link section at the bottom. So here is the code:

//by Jay Tennant 3/5/12
//A Brief Look at XAudio2: One Buffer, Multiple Voices
//win32developer.com
//this code provided free, as in public domain; score!

#include <windows.h>
#include <xaudio2.h>
#include "wave.h"

const int NUM_VOICES = 5;
const int DELAY = 50;

IXAudio2* g_engine;
IXAudio2SourceVoice* g_source[NUM_VOICES];
IXAudio2MasteringVoice* g_master;

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )
{
	//must call this for COM
	CoInitializeEx( NULL, COINIT_MULTITHREADED );

	//create the engine
	if( FAILED( XAudio2Create( &g_engine ) ) )
	{
		CoUninitialize();
		return -1;
	}

	//create the mastering voice
	if( FAILED( g_engine->CreateMasteringVoice( &g_master ) ) )
	{
		g_engine->Release();
		CoUninitialize();
		return -2;
	}

	//helper class to load wave files; trust me, this makes it MUCH easier
	Wave buffer;

	//load a wave file
	if( !buffer.load( "sfx.wav" ) )
	{
		g_engine->Release();
		CoUninitialize();
		return -3;
	}

	//create the source voices, based on loaded wave format
	for( int i = 0; i < NUM_VOICES; i++ )
	{
		if( FAILED( g_engine->CreateSourceVoice( &g_source[i], buffer.wf() ) ) )
		{
			g_engine->Release();
			CoUninitialize();
			return -(4 + i);
		}
	}

	//start consuming audio in the source voice
	for( int i = 0; i < NUM_VOICES; i++ )
		g_source[i]->Start();

	//simple message loop
	while( MessageBox( 0, TEXT("Do you want to play the sounds?"), TEXT("ABLAX: OBMV"), MB_YESNO ) == IDYES )
	{
		//play the sound with the delay
		for( int i = 0; i < NUM_VOICES; i++ )
		{
			g_source[i]->SubmitSourceBuffer( buffer.xaBuffer() );
			Sleep( DELAY );
		}
	}

	//release the engine, NOT the voices!
	g_engine->Release();

	//again, for COM
	CoUninitialize();

	return 0;
}

The "wave.h" header is available in the first tutorial. This is very similiar to the previous tutorial, the difference being a number of Source voices using the same sound buffer as input. This is where DirectSound and XAudio2 depart.

A Long Time Ago...

Historically, DirectSound defined sound buffers (or secondary buffers) to be allocated objects that both stored and processed wave data. The benefit of this architecture was to allocate the data on sound hardware, allowing hardware accelerated processing and presentation of the sounds.

One side effect of the design was that for streaming sounds, one had to lock, copy over wave data, and unlock these secondary buffers. This is similar to how one updates a vertex buffer, for example, on the graphics card. This action usually resulted in a performance penalty on the application since the CPU had to synchronize with the sound hardware. Of course, the hardware acceleration of the sound more than made up for that loss.

In today's industry, the CPU is drammatically faster than the DirectSound's targeted hardware (which were Pentium 133 MHz machines). With multicore technology the de facto, all sound processing could easily be done on the CPU. And with system RAM now reaching the 8GB standard (I'm sure in five years, this will even look pathetic), the extra capacity for sound buffers is not an issue. The bottleneck now became the synchronizing between the CPU and the sound hardware.

Starting with Window's Vista, the driver model no longer provided for hardware accelerated sound processing, except for low latency pro-audio (such as ASIO), which DirectSound was not designed for. Thus, DirectSound became emulated on the CPU, and was replaced by the CPU-driven XAudio, and upgraded to XAudio2.

In contrast with DirectSound, XAudio2 differentiates between the sound buffer and the sound processor. The sound buffer contains only the wave data, and the processor (such as a Source voice), well, processes the data to another small buffer, pushing it down the stream to the mastering voice. This separation also allows the same Source voice to accept any wave data as input that matches the same wave format.

IMPORTANT! Again, for emphasis, the Source voice can accept any sound buffer as input, as long as it matches the same wave format.

There are other notable differences between DirectSound and XAudio2, such as XAudio2's absence of a 3D concept tied to the buffers. But that is discussed in a later tutorial.

Things to Try

Experiment with the number of source voices and the delay factor.
Set the number of source voices to 2000, and the delay factor to 10. Go ahead. It's alright. If it sounds like the program crashed, wait 20 seconds. Remember, it's stepping through 2000 individual voices, each with 10ms delays between them. So 10ms * 2000 = ? Trust me, your computer can handle it!
Load another simple wave form that's a 16bit PCM wave, 44100Hz sampling rate from http://www.superflashbros.net/as3sfxr/ using the Wave helper class. Change the while loop to:
//simple message loop
int result = 0;
while( (result = MessageBox( 0, TEXT("Press 'Yes' for sound1, 'No' for sound2, and 'Cancel' to quit."), TEXT("ABLAX: OBMV"), MB_YESNOCANCEL )) != IDCANCEL )
{
	//play the sound with the delay
	for( int i = 0; i < NUM_VOICES; i++ )
	{
		g_source[i]->SubmitSourceBuffer( result == IDYES ? buffer.xaBuffer() : buffer2.xaBuffer() );
		Sleep( DELAY );
	}
}

Additional Information

Sound effect used for program: sfx.zip
Cool 8bit sound generator: http://www.superflashbros.net/as3sfxr/
MSDN entry on XAudio2: http://msdn.microsoft.com/en-us/library/hh405049(VS.85).aspx
Gamasutra article published in 2008 about the upcoming XAudio2 API: http://www.gamasutra.com/view/feature/3525/sponsored_feature_an_introduction_.php


Next tutorial

Tutorial 3 - XAudio2: Events and Asynchronous I/O (Part 1/2)