So you have this wonderful game. It has everything a game could need: amazing graphics, lightning-quick input response, the works. But there's only one thing missing: SOUND. Quality sound can make or break a game, so you want to have sound system that's booming. So how do you implement it? DirectX Audio!!!
This tutorial will teach you how to initialize, release, and utilize DirectX Audio. You will learn to load and play WAVs and MIDIs. This tutorial does not cover 3D sound, and does not assume any knowledge of sound or sound programming.
Remember to link dsound.lib and include the headers dmusici.h and dsound.h. Initializing DirectX Audio
Before we can actually play sounds with DirectX audio, though, we must initialize it. There are three steps to initializing DirectX Audio (as written in OpenGL Game Programming
1. Initialize COM
- Initialize COM
- Create and initialize the performance object
- Create the loader
Unlike other components of DirectX, DirectAudio is pure COM. This means that you have to create the com objects yourself, mostly using the function CoCreateInstance(). Initializing the actual COM system, however, is very easy:
What, you were expecting something harder? This simple function call allows you to use COM to create the DirectAudio objects. 2. Create and initialize the performance object
First of all, let's talk about the performance object. The performance object handles all data flow from the sound segments to the synthesizer that plays the sounds. Basically, it is the master interface of DirectX Audio. The performance object can be made by creating an IDirectMusicPerformance8 interface object. Then, you must create the object by calling CoCreateInstance.
IDirectMusicPerformacne8* g_performance = NULL;
CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC,
IID_IDirectMusicPerformance8, (void**) &g_performance);
The previous code declares an IDirectMusicPerformance8 object and creates it. Now we have to initialize it. The IDirectMusicPerformance8 interface uses the member function InitAudio to initialize itself. Let's take a look at the function:
Now let's show a working example of this function.
g_performance->InitAudio(NULL, // no interface needed
NULL, // no interface needed
g_hWND, // handle to the window
DMUS_APATH_SHARED_STEREOPLUSREVERB, // default audiopath
64, // 64 channels allocated to the audiopath
DMUS_AUDIOF_ALL, // allow all synthesizer features
NULL); // default audio parameters 3. Create the loader
The loader object, as its name implies, loads all audio content, such as MIDIs and WAVs, into segments that hold the data. The loader is represented in the IDirectMusicLoader8 interface. Creating the object requires a simple call to, you guessed it, CoCreateInstance.
IDirectMusicLoader8* g_loader = NULL;
CoCreateInstance(CLSID_IDirectMusicLoader, NULL, CLSCTX_INPROC,
IID_IDirectMusicLoader8, (void**) &g_loader); Playing Audio
Now that we've gotten DirectX Audio ready to go, we need something to play. Next item on the list: creating a segment. Let's see what we have to do:
1. Create a segment
- Create a segment
- Load the segment
- Download the band
- Play the segment
You might be wondering what a segment is. A segment is an object that encapsulates the sound data that is loaded from a sound file. Any sound that is played in DirectX Audio is in the form of a segment, just about. If you've been following along, you probably already know that the segment object is the form of the IDirectMusicSegment8 interface and is initialized through a COM call.
IDirectMusicSegment8* g_segment = NULL;
CoCreateInstance(CLSID_IDirectMusicSegment, NULL, CLSCTX_INPROC,
IID_IDirectMusicSegment8, (void**) &g_segment); 2. Load the segment
Now that you have a segment, you must load it with sound content. This is where the loader comes in. first, you want to set the search directory for the loader. This can be accomplished using the IDirectMusicLoader8::SetSearchDirectory function.
Chances are, you just want to set the search directory to the directory your program is in. This shows how you would do that
MultiByteToWideChar(CP_ACP, 0, searchPath, -1, wSearchPath, MAX_PATH);
g_loader->SetSearchDirectory(GUID_DirectMusicAllTypes, wSearchPath, FALSE);
The previous code snippet first declares a normal and a wide character array. The GetCurrentDirectory function retrieves the directory which the program is in. The MultiByteToWideChar function converts the regular character array into a WCHAR array. If you use dxutil.h | cpp, the DXUtil_ConvertGenericStringToWide function just uses MultiByteToWideChar, only it looks better. Finally, we call SetSearchDirectory with the predefined GUID that enables all music types to be read. Now that we've set the search path, we want to actually load a file. This can be accomplished with one function call:
Here's how you would load a file named test.wav:
char filename[MAX_PATH] = "test.wav";
MultiByteToWideChar(CP_ACP, 0, filename, -1, wFilename, MAX_PATH);
(void**) &g_segment); 3. Download the band
In order to be able to play the sound, you have to download the sound's band into the synthesizer. This can be done with the IDirectMusicSegment8::Download function.
HRESULT IDirectMusicSegment8::Download(Iunknown *pAudioPath);
That wasn't very exciting, was it? All you have to do is download the band to the performance object, like so:
g_segment->Download(g_performance); 4. Play the segment
Finally, the real stuff. Playing the sound, like most operations in DirectX Audio, only takes one function to complete. You can either use PlaySegment or PlaySegmentEx. Since PlaySegmentEx offers more functionality, we will be using it. Here is the prototype:
Even though it looks a little complicated, it is actually very simple to use. Only a couple of the parameters are of importance to us. pSource is the sound data that you are going to play. In this case, you will be playing g_segment. i64StartTime is the performance time at which the song will start playing. Normally, you sill set this to zero so that the song will play immediately. Notice that this is a function of the IDirectMusicPerformance8 interface. This is because IDirectMusicSegment only handles sound data and playing information. IDirectMusicPerformance8 handles all manipulation of the segment, including playing and stopping. Now let's finally play some music.
g_performance->PlaySegmentEx(g_segment, NULL, NULL, 0, 0, NULL, NULL, NULL); Other functions
Now you can load and play songs with DirectX Audio. Now you need some more control over your music. The only thing as important as starting your music is stopping it. That's simple enough:
You can use either one to stop a sound. For the last two parameters, both have the same functionality. The second to last parameter indicates when to stop the sound. If you want to stop the sound immediately, that value should be 0. dwFlags is when the stop should occur in the segment, which should also be set to 0. But, StopEx offers extra functionality because it can stop a segment or an audiopath. For stopping one sound, we'll use that.
g_performance->StopEx(g_segment, 0, 0);
Simple. What if you want to stop all sounds that are playing? You don't have to keep track of which song is playing. Just use the Stop function.
g_performance->Stop(NULL, NULL, 0, 0);
When you pass NULL as the segment to stop, the function stops all playing sounds. I tried it with StopEx, but the program crashes.
Another point: What if we want to check and see whether a song is playing or not? Use the IsPlaying function:
The function returns S_OK if the segment is still playing.
if (g_performance->IsPlaying(g_segment) == S_OK)
// do something because the song is still playing
The last function I want to talk about before we conclude is SetRepeats.
This function, as the name implies, sets the number of times that the sound repeats. If you're playing background music, for example, and you want it to loop forever, pass DMUS_SEG_REPEAT_INFINITE as the parameter and the sound will loop until you explicitly stop it. Shutting Down
We've had our fun, but now we need to shut down the program. First thing we do is stop all the sounds that are playing with the Stop function mentioned above. Then we call IDirectMusicPerformance8::CloseDown, which takes no parameters. Next, we release all objects with their Release function, which also takes no parameters. Lastly, we shut down COM with CoUninitialize, which takes no paramerters.
Hawkins, Kevin; Astle, Dave, OpenGL Game Programming
If you found this tutorial helpful, or you encounter any bugs or questions, please email me at firstname.lastname@example.org