Having decided it was time to add sound I began digging through the SDK documentation and quickly realised that a few things had changed since I last looked at DirectSound. As DirectDraw and Direct3D had been combined to form DirectX Graphics, it seemed that DirectSound and DirectMusc had also been combined to form DirectX Audio. Feeling that it was time to get hip with DirectX Audio, I took a deep breath and jumped right in, and before long had a few classes together which nicely game me the 3D sound I wanted in my game.
This tutorial gives a basic explanation of how to use DirectX Audio to create and play 3D sound effects loaded from .WAV files, and also includes the classes that I wrote to use in my game.
[size="5"]Setting Up DirectX Audio
Setting up DirectX Audio begins with creating a few DirectMusic interfaces. These interfaces include a DirectMusic Performance; which is used to play all the sounds and a DirectMusic Loader; which is used to load all the sounds. Having created these interfaces, DirectX Audio must be initialised using the [font="Courier New"][color="#000080"]InitAudio()[/color][/font] method of the Perormance interface. The code to accomplish all this follows:
// the performance and loader interfaces
IDirectMusicPerformance8 *pPerformance = NULL;
IDirectMusicLoader8 *pLoader = NULL;
// Initialize COM
// Create loader interface
CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC,
IID_IDirectMusicLoader8, (void**)&pLoader );
// Create performance interface
CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC,
IID_IDirectMusicPerformance8, (void**)&pPerformance );
// Initialise DirectX Audio for 3D sound
pPerformance->InitAudio(NULL, NULL, NULL,
DMUS_AUDIOF_ALL, NULL );
[size="5"]Creating a 3D Sound
Now that we have DirectX Audio setup, we'd really like to be able to play something with it. This next section describes how to load and create a 3D sound using the previous interfaces.
Strangely enough, to load a sound, we must use the DirectMusic Loader. The loader can be used to load a fair variety of sounds, however we shall be using it to load .WAV files. (If you want to load MP3 sounds then you'll have to use DirectShow, which isn't covered here.)
When the Loader loads a sound, it is put into a DirectMusic Segment. A segment on its own is OK if you just want to be able to play a sound, however if you want to be able to alter the sound in any way (which you must do for 3D sound) then you have to give each segment its own Audiopath. The Audiopath is what the segment is played through, and by altering the Audiopath you alter the way in which a segment is played.
For 3D sounds, each sound needs its own 3D Audiopath, which makes sense since it is a 3D sound. What makes a 3D Audiopaths special, and suitable for 3D sounds, is that they contain a 3D Sound Buffer. The 3D Sound Buffer can be altered to change the position of a sound in space, which is what you want for 3D Sound.
This works fine if you are always listening to your sounds from the origin (0,0,0). However, if you are in a world where you are moving around, then you won't always be listening from the origin. To change where the 3D sound is heard from, you need a 3D Sound Listener which (suprisingly) 'listens' to the 3D Sound. Each 3D sound has its own listener, as the listener is associated with the 3D Audiopath.
To create a 3D sound, we therefore have to load the sound into a segment. Create a 3D Audiopath for that segment. Get access to the 3D Sound Buffer contained in the Audiopath, and get access to the Listener which is also contained in the Audiopath. We can then play the sound, and alter its position in 3D space, as well as the position of the listener of the sound. The code to accomplish this follows:
// points to a zero terminated wide character string specifying the
// filename of the sound to load
// the segment for our sound
IDirectMusicSegment8 *pSegment = NULL;
// the 3d audiopath for the sound
IDirectMusicAudioPath8* p3DAudioPath = NULL;
// the 3d sound buffer for the sound
IDirectSound3DBuffer8* pDSB = NULL;
// the listener for the sound
IDirectSound3DListener8* pListener = NULL;
// load the soundfile into the segment
(LPVOID*) pSegment );
// Download the segment's instruments (this must be done for all .WAV files)
// Create the 3D audiopath with a 3d buffer.
// We can then play this segment through this audiopath (and hence the buffer)
// and alter its 3D parameters.
64, TRUE, &p3DAudioPath);
// Get the 3D Sound Buffer from the 3D audiopath
p3DAudioPath->GetObjectInPath(DMUS_PCHANNEL_ALL, DMUS_PATH_BUFFER, 0,
GUID_NULL, 0, IID_IDirectSound3DBuffer,
// get the listener from the 3d audiopath
0, GUID_All_Objects, 0,
Following this we create a 3D Audiopath for our sound, using the [font="Courier New"][color="#000080"]CreateStandardAudioPath()[/color][/font] method of the Performance interface. We indicate that we want a 3D audiopath and that it should be activated upon creation. We then use the [font="Courier New"][color="#000080"]GetObjectInPath()[/color][/font] method of the Audiopath interface to request an interface pointer to the 3D Sound Buffer and the 3D Listener.
Having achieved this, our sound is ready to be positioned, and played in 3D Space.
[size="5"]Positioning the Sound and Listener
To position the sound or the listener, we must call the SetPosition()method of the appropriate interface. To position the sound, we call the method on the 3D Sound Buffer interface. To position the listener, we call the method on the (yep you guessed it) Listener interface. The following code positions the sound and the llistener.
// the following store the coordinates of the sound and the listener.
float fSoundPosX, fSoundPosY, fSoundPosZ;
float fListenerPosX, fListenerPosY, fListenerPosZ;
// Set the position of sound
pDSB->SetPosition( fSoundPosX, fSoundPosY, fSoundPosZ, DS3D_IMMEDIATE);
// Set the position of the listener
pListener->SetPosition(fListenerPosX, fListenerPosY, fListenerPosZ, DS3D_IMMEDIATE);
Righto, are we ready to hear these sounds or what?
[size="5"]Playing The Sound
The sound itself is played using the DirectMusic Performance interface, where we specify which segment to play and the Audiopath we wish to play it through. We want to play our segment through its own audiopath, so that the changes that we made with the above code (where we moved the sound and its listener) actually take effect. The code to play the sound follows:
// play the segment on the 3D audiopath - play it as a secondary
// segment so that we can play multiple sounds at once.
pPerformance->PlaySegmentEx(pSegment, NULL, NULL, DMUS_SEGF_SECONDARY,
0, NULL, NULL, p3DAudioPath );
So now you can load a sound, set its 3D parameters and play it. All that's left now is to show you how to clean up after yourself.
[size="5"]Cleanup and Housekeeping
You should always clean up your mess, so here's the code to do that.
// release the 3d sound buffer
pDSB = NULL;
// release the listener
pListener = NULL;
// release the 3d audiopath
p3DAudioPath = NULL;
// release the segment
pSegment = NULL;
// release the loader
pLoader = NULL;
// finally close down the performance, and release it
pPerformance = NULL;
// close down COM
One thing I haven't mentioned so far is what you'll have to link your project with and the files you need to include to get all this working. You'll have to link with [font="Courier New"][color="#000080"]dxguid.lib[/color][/font] and include the following precompiler directives:
// the following need to be included - also link with dxguid.lib
One last thing before I go - I've made a couple classes: One ([font="Courier New"][color="#000080"]CDXAudio[/color][/font]) to do the DirectX Audio initialisation and shutdown, and another ([font="Courier New"][color="#000080"]C3DSound[/color][/font]) used to create and destroy 3d sounds, position them and their listener in space and then play them. They are used in the project source, which is an application that creates a sound and plays it in various locations.