Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
78 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Initialization
 Loading And
 Playing Music


 Source code
 Printable version

 


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!




Next : Loading And Playing Music