DirectMusic Symphony In C++
Part One - Using DirectMusic to make music in your games

by Mike "Aldacron" Parker

Introduction

It wasnít too long ago that I was afraid of the Big Bad Wolf. Going by the DirectX documentation, DirectMusic seemed a complicated beast which was too much for me to handle. But when you get down and dirty with it, it really isnít bad at all.

This is the first of a two-part tutorial on using DirectMusic in your games. If youíve ever read Tricks of the Windows Game Programming Gurus by Andreí LaMothe, then you already know the majority of what Iíll be presenting in Part One. This is where we take that cryptic Microsoft documentation and make some sense out of it. Weíll get DirectMusic up and running, load a midi file, and play it.

Part Two will dig a little deeper. There, Iíll show you how to manipulate the music volume as well as the basics of using DirectMusic Notifications and threads to gain more control over your game music.

On to Part OneÖ

Initialization

The first thing to note about DirectMusic is that it differs from the other DirectX components in the way you set it up. With the other components you can get by with just linking to the appropriate import lib (ddraw.lib, dsound.lib) and then calling the set-up routines. The corresponding DLL (ddraw.dll, dsound.dll) will be loaded automatically when the app starts up. DirectMusic adds a bit more work to the mix.

There is no such thing as dmusic.lib. This means that dmusic.dll must be loaded manually. To do so, youíll have to call a couple of COM routines. Now, COM is beyond the scope of this article, but the routines we need to call are not that difficult to follow. Letís look at the process step-by-step.

Step One: Include Header Files

Make sure youíve included the appropriate header files. DirectMusic requires no less than four headers. These are:

#include <dmksctrl.h>
#include <dmusici.h>
#include <dmusicc.h>
#include <dmusicf.h>

Take the time to browse each of those files (you can find them in the /includes directory of the DX SDK) and see what they contain.

Youíll also need to make sure that youíve either #defineíd INITGUID somewhere in your app or that you have linked to dxguid.lib.

Step Two: Initialize COM

Initializing COM requires just one function call to CoInitialize. This is a Win32 API call and must be called before you can manually load any COM components into your program. The only parameter this function accepts is reserved for future use, so for now it should always be NULL. This only needs to be called once in your program, so if you want to manually load multiple COM objects, you can call it somewhere near the beginning of the program and be done with it.

CoInitialize (NULL);

NOTE: MSDN recommends using CoInitializeEx, which takes one extra parameter and could improve performance in some cases. Weíll look at that more in Part Two. See MSDN for more details if youíre curious.

Step Three: Create The Performance

With other DirectX components you have to create things such as an IDirectDraw or IDirectSound object. Not so with DirectMusic. There is an IDirectMusic interface, but it can be created internally so youíll never have to deal with it (although you can if you want to). Instead, youíll use COM to create an IDirectMusicPerformance object.

The Performance is what you will use to handle almost all of your DirectMusic needs, from playing MIDIís to setting up Notifications. You can think of the Performance as the orchestra and yourself as the maestro. You tell the Performance when and how to play. Getting your orchestra ready requires a call to the COM function CoCreateInstance.

I wonít cover all the parameters here. If you absolutely must know what they are, I will refer you once again to MSDN. For now, though, just trust me. This is how you set up a Performance object:

IDirectMusicPerformance *performance;
if (FAILED(CoCreateInstance(CLSID_DirectMusicPerfomance,
                            NULL,
                            CLSCTX_INPROC,
                            IID_IDirectMusicPerformance,
                            (void **)&performance)))
{
  // handle error
}

Step Four: Initialize your Performance

In order for your orchestra to play some tunes, they must have their instruments ready. A call to IdirectMusicPerformance::Init comes next. Now, there is one caveat here. If you are going to use DirectSound and DirectMusic together in the same app, then you need to make sure that DirectSound is initialized first. Why? Read on.

DirectMusic uses a DirectSound object internally and the Performanceís Init function takes a pointer to a DirectSound object as a parameter. If you tried to create your DirectSound object after initializing DirectMusic, I imagine you would get a DSERR_ALLOCATED error. Iíve never tried, so donít take my word for it. So always initialize DirectSound first and pass a pointer to the DirectSound object to IDirectMusicPerformance::Init. Otherwise, if youíre only using DirectMusic (as is the case in this tutorial) then youíll just pass NULL to the Init function.

IDirectMusicPerformance::Init (IDirectMusic** ppDirectMusic,
                               LPDIRECTSOUND pDirectSound,
                               HWND hwnd)

The first parameter you generally donít need to concern yourself with (unless you want to get advanced), the second parameter is discussed above, and the third parameter is the handle to your applicationís window (preferably the top-level window if you are writing a multi-windowed app). So, for our purposes, the call looks like this:

HRESULT hr;

if (FAILED(hr=performance->Init (NULL,NULL,main_hwnd)))
{
  // handle error
}

Step Five: Add a Port

Your orchestra needs to know where theyíll be playing. This is where the IDirectMusicPort interface comes into play. You can use DirectMusic to enumerate all of the ports available on the machine, but this brings up certain issues you should be aware of.

A port can be a hardware device, a software synthesizer, or a software filter. Thatís a wide variety of ports, and variety amounts to variations in sound. If you enumerate all of the available ports and let the user choose one, thereís no guarantee the music will sound the way you intended it. However, by always using the default Microsoft Software Synthesizer as the port, you canít go wrong. This will ensure that your music will sound the same across all Windows machines with DirectMusic installed. It sounds pretty darn good to boot.

In order to let the Performance know which port we would like to use, we need to make a call to IDirectMusicPerformance::AddPort. It takes one parameter, an IDirectMusicPort pointer. To use the default MS synthesizer, as we are going to do, you just pass NULL as the parameter.

if (FAILED(hr=performance->AddPort(NULL)))
{
  // handle error
}

Step 6: Create the Loader

Okay, this is the last step in initialization and I canít think of a real-world orchestral analogy to throw in here (they donít have music boys who deliver music do they?).

The IDirectMusicLoader interface provides methods to load various data files that can be used in DirectMusic. Of course, weíre only interested in MIDI files. All we need to do is make a call to that handy COM function, CoCreateInstance, and weíll have a Loader object ready to load up some music.

IDirectMusicLoader *loader = NULL;
if (FAILED(CoCreateInstance(CLSID_DirectMusicLoader,
                            NULL,
                            CLSCTX_INPROC,
                            IID_IdirectMusicLoader,
                            (void **)&loader)
{
  // handle error
}

Thatís it for initialization!

Loading And Playing Music

Now comes the fun part. Although it may seem a bit daunting at first, loading a MIDI file via a DirectMusicLoader is much simpler than dealing with the Win32 multi-media functions. Iím not going to detail the code here. Instead, Iíll just give you the gist of what needs to be done. You can look at the source code that accompanies this article and read the comments to learn what the code does.

Loading a MIDI File

The first thing to do is to tell the loader where to look for the files and what type of files to look for. This is accomplished by a call to IDirectMusicLoader::SetSearchDirectory. Next, a DMUS_OBJDESC structure needs to be setup with values appropriate for MIDI files (again, all of this is in the example source). From that point, you just call IDirectMusicLoader::GetObject and pass a pointer to an IDirectMusicSegment object as one of the parameters. Finally, make a couple of calls to the Segment in order to set some parameters and "download" the instruments. Then youíre all done.

So, thereís really not much to it at all. I wish I could go into more detail with the source here, but space is sparse. So please make sure you read the comments in the example source thoroughly.

Playing Loaded Music

Playing a MIDI you have already loaded is quite simple. You do so by making a call to IDirectMusicPerformance::PlaySegment. Just pass a pointer to the IDirectMusicSgement you wish to play. There are several flags you can pass along, as well as a time to start playing. You usually will only want to use these flags if youíre dealing with music you created in DirectMusicProducer, which is a topic for another article. For now, just pass along 0ís.

The final parameter to PlaySegment is a pointer to an IDirectMusicSegmentState. This is important only if you need to track information about the Segment. For our purposes, it can just be NULL.

performance->PlaySegment(Segment_To_Play,0,0,NULL);

There are similar functions for stopping a Segment and to determine if a segment is playing. You can see examples of these functions in the accompanying source.

A Final Step

Okay. Youíve got everything loaded, your music is playing. Now you can quit the program and carry on, right? Wrong. The DirectMusic system must be shutdown.

Just like all DirectX objects, the DirectMusic components each have a Release method that should be called before exiting your application. You need to release the DirectMusicLoader you used, as well as the performance. However, the performance requires that you call IDirectMusicPerformance::CloseDown before releasing it.

Before you release the segments, you must first set a segment parameter called GUID_Unload on each segment you loaded. Again, youíll have to peek at the source to see all of this in action, but itís pretty self-explanatory.

Conclusion

Not all that difficult, right? You will find that after you have learned the basics, more and more of the DirectMusic API will begin to make sense to you. Be sure to study the example source carefully and you will be well on your way. If you wish to compile it, there are no additional libs you need to link to (other than the default libs MSVC sets up for you Ė donít know about other compilers). Just create a new Win32 Application and compile.

Please report any errors you find in the article or the code to me at mdat71@thrunet.com.

Thatís all folks. See you in Part Two.

Discuss this article in the forums


Date this article was posted to GameDev.net: 1/9/2001
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
DirectX Audio

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