BEAT DETECTION
With MIDI, you can detect the beat of the music and do something with it. For example, you could make an annoying enemy in your game that actually dances to the background music, or you could make strobe lights and other things that flash to the beat.
DirectMusic Notification
Detecting beats requires a bit of code. DirectMusic doesn't have a "IsPlayingBeat" function that you can call; instead, it communicates a different way, using what it calls notification events.When something interesting happens (for example, a beat, or a segment loop, or a whole slew of other things), DirectMusic adds an event to its internal queue (sort of like Windows). You pull these events off the queue by using the GetNotificationPMsg method of the performance.
Tip | Remember that you've seen PMsg before: when you set the tempo, you sent the performance a DMUS_TEMPO_PMSG. Now, you're on the receiving side of things. |
Before you do that, however, you first have to tell DirectMusic what kinds of events you're interested in. You can do this by calling the AddNotificationType method of the performance. Each event is a GUID; you pass AddNotificationType the GUID of the event you're interested in (see Table 6.1).
GUID | Event |
---|---|
GUID_NOTIFICATION_CHORD | A chord change has occurred. This is used mainly to tell you that the DirectMusic composer has decided to change the chord that's Playing.. |
GUID_NOTIFICATION_COMMAND | A command event has occurred. |
GUID_NOTIFICATION_MEASUREANDBEAT | This event is sent out on the beat. A value of zero inside dwField1 indicates a new measure. Any other value indicates a beat within that measure. |
GUID_NOTIFICATION_PERFORMANCE | A performance event has occurred. You can look at dwNotificationOption to determine what specifically it was. |
GUID_NOTIFICATION_RECOMPOSE | A track has been recomposed by the DirectMusic composer. |
GUID_NOTIFICATION_SEGMENT | A segment event has occurred. You can look in dwNotificationOption to determine exactly what happened. |
Figure 6.3 illustrates the basic flowchart for using DirectMusic performance events. Essentially you call AddNotificationType during your initialization routine, and then call GetNotificationPMsg periodically (for example, inside your main game loop).

Figure 6.3: A flowchart of the basic process for using DirectMusic performance events.
GetNofiticationPMsg returns S_OK if it gets a message, and S_FALSE if there are no messages to get. So, to process all of the messages that occurred between now and the last time GetNotificationPMsg was called, write code like the following:
DMUS_NOTIFICATION_PMSG* pPmsg;
while (m_Performance->GetNotificationPMsg(&pPmsg) == S_OK) {
// do something interesting with pPmsg here
// free it when you're done
m_Performance->FreePMsg((DMUS_PMSG*)pPmsg);
}
GetNotificationPMsg takes a pointer to a DMUS_NOTIFICATION_PMSG structure you want it to fill. The DMUS_NOTIFICATION_PMSG structure contains everything you want to know about the message, including its type (guidNotificationType), optional subtype (dwNotificationOption), and any optional parameters (dwField1 and dwField2).
Caution | Once you're finished with the notification message you got from GetNotificationPMsg, you must delete it by calling the FreePMsg method of the performance. Failure to do so will result in memory leaks! |
Building Notifications into the Audio Engine
Now that you have a basic understanding of DirectMusic's notification/event scheme, it's time to integrate it into the audio engine.The biggest issue with this revolves around the method you use to communicate events to the code that's using the audio engine. Different games are going to want to listen for different events, and respond to them in different ways. You need a way for the audio engine code to easily tell the game code that something happened.In C, most programmers solve this by using function pointers. The client code supplies the address of a function they want called when, say, a beat occurs, and the audio framework simply calls that function pointer when the time comes. This is a completely acceptable C solution to the problem.C++ programmers, however, go one step further, and use a class instead of function pointers. This class contains virtual functions for every type of event that could occur. The audio engine contains a pointer to that class, and when the time comes, the audio engine uses that pointer to call methods on that class.For example, consider a class called CNotificationHandler. This class contains virtual functions for every type of event that you might want to react to:
class CNotificationHandler
{
public:
CNotificationHandler() { }
virtual ~CNotificationHandler() { }
// this function is called when a beat occurs
virtual void OnBeat(DMUS_NOTIFICATION_PMSG &msg) { }
// this function is called when a new measure is encountered
virtual void OnMeasure(DMUS_NOTIFICATION_PMSG &msg) { }
// etc..
};
Notice that all of the functions are virtual, and do nothing. Now, imagine that the audio engine has a pointer to this class called m_NotificationHandler. By default, m_NotificationHandler is NULL, but the game code can change that. When an event occurs, the audio engine uses the m_NotificationHandler pointer to call the appropriate method:
void CAudioManager::DispatchNotificationMessages()
{
DMUS_NOTIFICATION_PMSG* pPmsg;
while (m_NotificationHandler &&
m_Performance->GetNotificationPMsg(&pPmsg) == S_OK)
{
if (pPmsg->guidNotificationType ==
GUID_NOTIFICATION_MEASUREANDBEAT) {
// if it's a beat, call OnBeat... otherwise call OnMeasure.
if (pPmsg->dwField1 == 0) {
m_NotificationHandler->OnMeasure(*pPmsg);
}
else {
m_NotificationHandler->OnBeat(*pPmsg);
}
}
// don't forget to free the message when you're done with it!
m_Performance->FreePMsg((DMUS_PMSG*)pPmsg);
} // while
} // method
The beauty of this design is that now the game code can derive a new class from CNotificationHandler and override the virtual functions to do something useful. Then, the code can set the m_NotificationHandler pointer to point to an instance of that derived class, so it can do whatever it wants in response to an event.As you've probably guessed by now, this is how I wrote the audio manager. The manager has two new accessor methods, SetNotificationHandler and GetNotificationHandler, which enable you to set and retrieve the m_NotificationHandler pointer. The main game calls DispatchNotificationMessages periodically, and DispatchNotificationMessages uses the m_NotificationHandler pointer to dispatch events by calling the OnMeasure, OnBeat, and other virtual methods.The sample program for this chapter illustrates how this system works. In Ch6p1_MIDIPlayback is a definition for a class called CCh6p1StupidNotificationHandler. This class is derived from CNotificationHandler, but has different method implementations:
class CCh6p1StupidNotificationHandler : public CNotificationHandler {
virtual void OnBeat(DMUS_NOTIFICATION_PMSG &msg) {
cout << "..Beat!..";
}
virtual void OnMeasure(DMUS_NOTIFICATION_PMSG &msg) {
cout << endl << "Measure!..";
}
};
As you can see, it lives up to its name—the stupid class just prints out the messages. Smarter classes would do something more interesting, but for the purposes of the example program, this works fine.
Multithreading Event Detection
If you're disgusted by the idea of having to call the audio manager's DispatchNotificationMessages method all the time, there is another option: multithreading! You can create a separate thread whose sole job is to watch for and respond to DirectMusic events.Of course, you don't want to continuously loop in this separate thread—that would burn precious CPU time. What you want is to have this thread sleep until a message comes in, dispatch that message, and then go back to sleep until the next message comes in.Fortunately, DirectMusic provides a way to do this. You can give it a handle to a Windows event primitive, and it will signal that event when a new message is generated. To do this, create the event using the Win32 API function Create Event, then tell DirectMusic that you'd like it to use the event you created by passing it to the SetNotificationHandle method of the performance.
Caution | Make sure that when you create your event, you make it auto-resetting, or that you reset it yourself once it's signaled. Otherwise, you'll never go back to sleep after getting your first message!To make an event reset automatically, specify false for the second parameter to CreateEvent. For more information, see the Win32 API documentation for CreateEvent. |
Now, you can code your thread using WaitForSingleObject, passing it the same event handle you gave to DirectMusic, and it will automatically sleep until DirectMusic signals the event. Once it awakens, the thread can use GetNotificationPMsg to grab the message and do whatever it needs to with it. Then, it can free the message via a call to FreePMsg, and loop until the next event comes in.The DirectX documentation has an example of this code. You can find it (along with a more detailed explanation of how it works) at DirectX Audio\Using DirectX Audio\Playing Sounds\Notification and Event Handling.The audio engine does some of this work for you. It creates an event and tells DirectMusic about it. You can access this event through the GetNotificationEvent method of CAudioManager. Then, write your own code using WaitForSingleObject, and don't bother calling DispatchNotificationMessages until you get a message!I didn't include multithreaded sample code, but it should be fairly easy to piece together given what I've already explained. Whoo-hoo, an exercise for the reader!