Using DirectX Audio 8
by Mason "Masonium" Smith

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
  2. Create and initialize the performance object
  3. Create the loader

1. Initialize COM

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:

CoInitialize(NULL);

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:

HRESULT IDirectMusicPerformance8::InitAudio(
  IDirectMusic**    ppDirectMusic,
  IDirectSound**    ppDirectSound,
  HWND              hWnd,
  DWORD             dwDefaultPathType,
  DWORD             wPChannelCount,
  DWORD             dwFlags,
  DMUS_AUDIOPARAMS  pParams);

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
  2. Load the segment
  3. Download the band
  4. Play the segment

1. Create a 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.

HRESULT IDirectMusicLoader8::SetSearchDirectory(
  REFGUID     rguidClass,
  WCHAR*      pwszPath,
  BOOL        fClear);

Chances are, you just want to set the search directory to the directory your program is in. This shows how you would do that

char searchPath[MAX_PATH];
WCHAR wSearchPath[MAX_PATH];

GetCurrentDirectory(MAX_PATH, searchPath);
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:

HRESULT IDirectMusicLoader::LoadObjectFromFile(
  REFGUID   rguidClassID,
  REFIID    iidInterfaceID,
  WCHAR     *pwzFilePath,
  void      **ppObject);

Here's how you would load a file named test.wav:

char filename[MAX_PATH] = "test.wav";
WCHAR wFilename[MAX_PATH];

MultiByteToWideChar(CP_ACP, 0, filename, -1, wFilename, MAX_PATH);

g_loader->LoadObjectFromFile(CLSID_DirectMusicSegment,
     IID_IDirectMusicSegment8,
     wFilename,
     (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:

HRESULT IDirectMusicPerformance8::PlaySegmentEx(
    IUnknown*        pSource,
    WCHAR*           pwzSegmentName,
    IUnknown*        pTransition,
    DWORD            dwFlags,
    __int64          i64StartTime,
    IDirectMusicSegmentState**   ppSegmentState,
    IUnknown*        pFrom, 
    IUnknown*        pAudioPath);

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:

HRESULT IDirectMusicPerformance8::Stop(
    IDirectMusicSegment*       pSegment,
    IDirectMusicSegmentState*  pSegmentState,
    MUSIC_TIME     mtTime,
    DWORD          dwFlags);

HRESULT IDirectMusicPerformance8::StopEx(
    IUnknown*  pObjectTostop,
    __int64    i64StopTime,
    DWORD      dwFlags);

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:

HRESULT IDirectMusicPerformance8::IsPlaying(
IDirectMusicSegment*    pSegment,
IDirectMusicSegmentState*  pSegState);

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.

HRESULT IDirectMusicSegment8::SetRepeats(
  DWORD  dwRepeats);

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.

g_performance->CloseDown();
g_loader->Release();
g_performance->Release();
g_segment->Release();
CoUninitialize();

References:

Hawkins, Kevin; Astle, Dave, OpenGL Game Programming
DirectX Documentation

If you found this tutorial helpful, or you encounter any bugs or questions, please email me at masonium@yahoo.com.

Discuss this article in the forums


Date this article was posted to GameDev.net: 2/6/2002
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
DirectX Audio
Sweet Snippets

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!