Jones Implementation


Jones doesn't really use notifications in the same way that you would for a game or other interactive application. Normally, you would want to use notifications to trigger the application to run some code or set a state variable. Instead, Jones just displays the text. But it's important that the notification system built into Jones as part of CAudio be something that you can easily rip out and put to good use in a more useful way. So, the design focuses on making it easy to control, capture, and retrieve notifications. What you do with the notification information is up to you.

We introduce the CNotificationManager class to control notifications. It handles all the busywork so that the tasks of enabling and processing notifications are as simple as possible. CNotification-Manager can:

  • Enable and disable specific classes of notifications, including lyrics

  • Capture the notifications and store their information in an easy-to-read-and-use format

  • Provide easy retrieval of the notifications so the application can act on them with a minimum of additional code

Let's walk through the design.

Enabling Notifications

DirectMusic's mechanism for classifying the notification categories relies on GUIDs, which are a bit unwieldy. In order to avoid that complexity throughout our code and also integrate lyric support in a clean way, we represent each notification category with a bit flag. We add one last bit flag to indicate a lyric. This allows us to have one DWORD represent the on/off state of all notification categories, including lyrics.

 #define NOTIFYENABLE_PERFORMANCE    1    // GUID_NOTIFICATION_PERFORMANCE #define NOTIFYENABLE_SEGMENT        2    // GUID_NOTIFICATION_SEGMENT #define NOTIFYENABLE_TIMESIG        4    // GUID_NOTIFICATION_MEASUREANDBEAT #define NOTIFYENABLE_CHORD          8    // GUID_NOTIFICATION_CHORD #define NOTIFYENABLE_COMMAND        0x10 // GUID_NOTIFICATION_COMMAND #define NOTIFYENABLE_RECOMPOSE      0x20 // GUID_NOTIFICATION_RECOMPOSE #define NOTIFYENABLE_LYRIC          0x40 // Lyric pmsg 

CNotificationManager has two routines that take these flags to enable and disable notifications. They are called, appropriately, EnableNotifications() and DisableNotifications().

EnableNotifications() takes one parameter, DWORD dwType-Flags, which indicates which notification categories to turn on. For each bit set, it calls the Performance's AddNotificationType() method with the appropriate GUID. Since lyrics are always on (there is no Performance command to enable or disable lyric support), Enable-Notifications() does not have to do anything for lyrics.

 void CNotificationManager::EnableNotifications(DWORD dwTypeFlags) {     if (dwTypeFlags & NOTIFYENABLE_PERFORMANCE)     {         m_pAudio->GetPerformance()->AddNotificationType(             GUID_NOTIFICATION_PERFORMANCE);     }     if (dwTypeFlags & NOTIFYENABLE_SEGMENT)     {         m_pAudio->GetPerformance()->AddNotificationType(             GUID_NOTIFICATION_SEGMENT);     }     ... and so on for all notification categories, with the exception of     lyrics, which are always on     // Now, set the flags so we remember what we enabled.     m_dwEnabledTypes |= dwTypeFlags; } 

Likewise, CNotificationManager::DisableNotifications() takes a DWORD with bits for all the notifications to turn off, and it calls RemoveNotificationType() with the appropriate GUID for each bit set.

Along the same lines, we need a simpler system for tracking each of the individual notification subtypes. We track the different notifications with our own constants simply because this requires a lot less work than the GUID + subfield organization of the DirectMusic notifications. And, we can add lyric support under the same umbrella.

 typedef enum _NOTIFICATION_TYPE {     NOTIFICATION_NONE          = 0,    // Empty.     NOTIFICATION_SEGSTART      = 1,    // Segment started playback.     NOTIFICATION_SEGEND        = 2,    // Segment finished.     NOTIFICATION_SEGALMOSTEND  = 3,    // Segment about to finish.     NOTIFICATION_SEGLOOP       = 4,    // Segment looped.     NOTIFICATION_SEGABORT      = 5,    // Segment stopped prematurely.     NOTIFICATION_PERFSTARTED   = 6,    // First Segment started.     NOTIFICATION_PERFSTOPPED   = 7,    // Last Segment ended.     NOTIFICATION_PERFALMOSTEND = 8,    // Current primary Segment almost ended.     NOTIFICATION_MEASUREBEAT   = 9,    // Beat marker.     NOTIFICATION_CHORD         = 10,   // Chord changed.     NOTIFICATION_GROOVE        = 11,   // Groove level changed.     NOTIFICATION_EMBELLISHMENT = 12,   // Embellishment played.     NOTIFICATION_RECOMPOSE     = 13,   // Track recomposed.     NOTIFICATION_LYRIC         = 14    // Lyric. } NOTIFICATION_TYPE; 

CNotificationManager has a method, TranslateType(), that reads a pmsg and returns a NOTIFICATION_TYPE constant, which can then be used to track the notification in a more friendly way.

 NOTIFICATION_TYPE CNotificationManager::TranslateType(DMUS_PMSG *pMsg) {     if (pMsg->dwType == DMUS_PMSGT_LYRIC)     {         return NOTIFICATION_LYRIC;     }     else if (pMsg->dwType == DMUS_PMSGT_NOTIFICATION)     {         DMUS_NOTIFICATION_PMSG *pNotify = (DMUS_NOTIFICATION_PMSG *) pMsg;         DWORD dwNotificationOption = pNotify->dwNotificationOption;         if (pNotify->guidNotificationType == GUID_NOTIFICATION_PERFORMANCE)         {             if (dwNotificationOption == DMUS_NOTIFICATION_MUSICSTARTED)             {                 return NOTIFICATION_PERFSTARTED;             }             else if (dwNotificationOption == DMUS_NOTIFICATION_MUSICALMOSTEND)             {                 return NOTIFICATION_PERFALMOSTEND;             }             else if (dwNotificationOption == DMUS_NOTIFICATION_MUSICSTOPPED)             {                 return NOTIFICATION_PERFSTOPPED;             }         }         else if (pNotify->guidNotificationType == GUID_NOTIFICATION_SEGMENT)         {             if (dwNotificationOption == DMUS_NOTIFICATION_SEGSTART)             {                 return NOTIFICATION_SEGSTART;             }             else if (dwNotificationOption == DMUS_NOTIFICATION_SEGALMOSTEND)             {                 return NOTIFICATION_SEGALMOSTEND;             }             else if (dwNotificationOption == DMUS_NOTIFICATION_SEGEND)             {                 return NOTIFICATION_SEGEND;             }             else if (dwNotificationOption == DMUS_NOTIFICATION_SEGABORT)             {                 return NOTIFICATION_SEGABORT;             }             else if (dwNotificationOption == DMUS_NOTIFICATION_SEGLOOP)             {                 return NOTIFICATION_SEGLOOP;             }         }         else if (pNotify->guidNotificationType ==             GUID_NOTIFICATION_MEASUREANDBEAT)         {             return NOTIFICATION_MEASUREBEAT;         }         else if (pNotify->guidNotificationType == GUID_NOTIFICATION_CHORD)         {             return NOTIFICATION_CHORD;         }         else if (pNotify->guidNotificationType == GUID_NOTIFICATION_COMMAND)         {             if (dwNotificationOption == DMUS_NOTIFICATION_GROOVE)             {                 return NOTIFICATION_GROOVE;             }             else if (dwNotificationOption == DMUS_NOTIFICATION_EMBELLISHMENT)             {                 return NOTIFICATION_EMBELLISHMENT;             }         }         else if (pNotify->guidNotificationType == GUID_NOTIFICATION_RECOMPOSE)         {             return NOTIFICATION_RECOMPOSE;         }     }     return NOTIFICATION_NONE; } 

Capturing Notifications

To capture both notifications and lyrics, CNotificationManager implements an IDirectMusicTool interface so it can insert itself directly in the Performance and intercept DMUS_NOTIFICATION_PMSG and DMUS_LYRIC_PMSG messages. To make this possible, we base the CNotificationManager class on the IDirectMusicTool interface. CNotificationManager then provides its own implementation of all the IDirectMusicTool methods.

 class CNotificationManager : public CMyList, public IDirectMusicTool { public:     // IUnknown methods.     STDMETHODIMP QueryInterface(const IID &iid, void **ppv);     STDMETHODIMP_(ULONG) AddRef();     STDMETHODIMP_(ULONG) Release();     // IDirectMusicTool methods.     STDMETHODIMP Init(IDirectMusicGraph* pGraph) ;     STDMETHODIMP GetMsgDeliveryType(DWORD* pdwDeliveryType) ;     STDMETHODIMP GetMediaTypeArraySize(DWORD* pdwNumElements) ;     STDMETHODIMP GetMediaTypes(DWORD** padwMediaTypes,                                DWORD dwNumElements) ;     STDMETHODIMP ProcessPMsg(IDirectMusicPerformance* pPerf,                                DMUS_PMSG* pPMSG) ;     STDMETHODIMP Flush(IDirectMusicPerformance* pPerf,                                DMUS_PMSG* pPMSG,                                REFERENCE_TIME rtTime) ; 

Most of the methods handle the process of setting up the Tool. When it is installing the Tool, DirectMusic calls Init(), GetMsgDelivery-Type(), GetMediaTypeArraySize(), and GetMediaTypes(). Depending on what the Tool needs to do, it can implement these as needed.

Init() allows the Tool to initialize its internal structures at the time DirectMusic places the Tool in a Graph. For our purposes, there is nothing to do here, so the method simply returns S_OK.

 HRESULT CNotificationManager::Init(IDirectMusicGraph* pGraph) {     // Don't need to do anything at initialization.     return S_OK; } 

GetMsgDeliveryType() tells DirectMusic when to deliver the messages. In this case, the Tool wants the messages delivered at queue time.

 HRESULT CNotificationManager::GetMsgDeliveryType(DWORD* pdwDeliveryType) {     // This Tool should process just a tad before the time stamp.     *pdwDeliveryType = DMUS_PMSGF_TOOL_QUEUE;     return S_OK; } 

DirectMusic also needs to know which message types to send through this Tool. Although a Tool can elect to simply ignore this and receive all pmsgs, doing so is a bad idea for two reasons. First, receiving all PMsgs incurs extra overhead (admittedly minimal, but still) to process everything and not just the messages we care about. Second, it causes all messages to be held up by this delivery time before they can proceed to the next stage. With a delivery of DMUS_PMSGF_TOOL_QUEUE, the timing is right up against the edge. If the delivery option becomes DMUS_PMSGF_TOOL_ ATTIME, we are in trouble because note and wave messages end up delivered to the DirectMusic Synth too late, resulting in horribly stuttered timing.

Therefore, it is best to implement the pair of methods GetMediaTypeArraySize(), which specifies the number of message types, and GetMediaTypes(), which then copies over the list of message types.

 HRESULT CNotificationManager::GetMediaTypeArraySize(DWORD* pdwNumElements) {     // We have two media types to process: lyrics and notifications.     *pdwNumElements = 2;     return S_OK; } HRESULT CNotificationManager::GetMediaTypes(DWORD** padwMediaTypes,                                     DWORD dwNumElements) {     // Return the two types we handle.     DWORD *pdwArray = *padwMediaTypes;     pdwArray[0] = DMUS_PMSGT_LYRIC;     pdwArray[1] = DMUS_PMSGT_NOTIFICATION;     return S_OK; } 

As we have seen several times, the heart of a Tool is really its ProcessPMsg() method. This is the code that receives the pmsg and does something with it. In this case, it doesn't do too much. Our code turns around and calls an internal method, InsertNotification(), and then tells DirectMusic to discard the pmsg. We discuss Insert-Notification() next.

 HRESULT CNotificationManager::ProcessPMsg(IDirectMusicPerformance* pPerf,                                   DMUS_PMSG* pPMSG) {     // This should always be a lyric or notification,     // but check just to be safe.     if ((pPMSG->dwType == DMUS_PMSGT_LYRIC) ||         (pPMSG->dwType == DMUS_PMSGT_NOTIFICATION))     {         InsertNotification(pPMSG);     }     // No need to send this on down the line.     return DMUS_S_FREE; } 

InsertNotification() provides the glue to convert the message into a notification event that can be retrieved asynchronously by the main loop. It takes the pmsg and uses it to create a CNotification object, which it places in its queue. Since the queue itself can be accessed either from the Tool processing thread or the main loop thread, this code must be protected with a critical section.

 void CNotificationManager::InsertNotification(DMUS_PMSG *pMsg) {     // TranslateType reads the pmsg and figures out the     // appropriate NOTATION_TYPE for it.     NOTIFICATION_TYPE ntType = TranslateType(pMsg);     // If this is a lyric, it still might not be enabled     // for capture, so check first.     if ((ntType != NOTIFICATION_LYRIC) ||         (m_dwEnabledTypes & NOTIFYENABLE_LYRIC))     {         // Allocate a CNotation structure to store the         // notification information.         CNotification *pNotification = new CNotification;         if (pNotification)         {             // The Init() routine fills the CNotification with             // name, type, and optional object pointer.             pNotification->Init(pMsg,ntType);             // Since this can be called from a different thread,             // we need to be safe with a critical section.             EnterCriticalSection(&m_CriticalSection);             AddTail(pNotification);             LeaveCriticalSection(&m_CriticalSection);         }     } } 

The CNotification class tracks one instance of a notification that has been captured on playback. CNotification is based on the class CMyList, so it can be managed in a linked list. CNotification stores several parameters that it fills when it translates the original pmsg:

  • A name, m_szName: If a lyric, this stores the actual lyric text. If a performance notification, it stores a description of the notification.

  • The notification type, m_ntType: This is an identifier from the enumerated set of NOTIFICATION_TYPEs.

  • An optional pointer to an object, m_pObject: Some performance notifications include an IUnknown pointer to an object. This maintains its own internal reference to the object and releases it when deleted.

  • An optional data field, m_dwData: If a performance notification uses one of the data fields in the message, it is copied here.

  • The time stamp of the notification, m_rtTimeStamp: This is the exact time that the action that the notification represents will occur. Since the notifications are delivered slightly ahead of time, retaining the exact intended time is important.

 class CNotification : public CMyNode { public:     CNotification();     ~CNotification();     CNotification* GetNext() { return (CNotification*)CMyNode::GetNext();}     void Init(DMUS_PMSG *pMsg,NOTIFICATION_TYPE ntType);     char *GetName() { return m_szName; };     void GetTimeStamp(REFERENCE_TIME *prtTime) { *prtTime = m_rtTimeStamp; };     NOTIFICATION_TYPE GetType() { return m_ntType; };     IUnknown * GetObject() { return m_pObject; }; // Note: This doesn't AddRef!     DWORD GetData() { return m_dwData; }; private:     char                m_szName[40];   // Name for notification.     NOTIFICATION_TYPE   m_ntType;       // Notification type.     REFERENCE_TIME      m_rtTimeStamp;  // Time the notification occured.     IUnknown *          m_pObject;      // Optional object pointer.     DWORD               m_dwData;       // Data field, borrowed from pmsg. }; 

The only substantial method, Init(), takes a DMUS_PMSG that is either a lyric or notification and uses it to initialize its fields. All of the other methods return data from the fields.

Retrieving Notifications

As the notifications are being captured by the CNotificationManager, it stores them in its internal list. The application can then call CNotificationManager::GetNextNotification() to retrieve the oldest CNotification in the queue. When done with a retrieved CNotification, the application must delete it.

 CNotification *pNotification = pManager->GetNextNotification(); if (pNotification) {     // Do something.     delete pNotification; } 

Now, let's look and see how CAudio integrates CNotification-Manager.

CAudio Integration

For notification support, CAudio adds a pointer to a CNotification-Manager and two methods to access it:

 public:     // Methods for managing notifications.     CNotification *GetNextNotification();   // Returns next notification in queue.     void EnableNotifications(DWORD dwType,bool fEnable); private:     // CNotificationManager captures and queues notifications.     CNotificationManager *      m_pNotifications; 

CAudio does not create the CNotificationManager by default. Since it is only needed in the event that the application wants to track notifications, CAudio creates its CNotificationManager when the application calls EnableNotifications() the first time. At that point, it creates an instance of CNotificationManager and places it as a Tool in the Performance's Tool Graph. The CNotificationManager object can be manipulated as a Tool because it exposes the IDirectMusicTool interface.

Once CAudio has installed CNotificationManager, calls to CAudio::EnableNotifications() simply pass through to CNotification-Manager::EnableNotifications() and CNotificationManager:: DisableNotifications().

Note that since CNotificationManager is managed as a COM object, the last object to Release() it causes it to go away. Even CAudio's pointer to CNotificationManager is treated as a reference.

 void CAudio::EnableNotifications(DWORD dwType, bool fEnable) {     if (!m_pNotifications)     {         IDirectMusicAudioPath *pPath;         if (SUCCEEDED(m_pPerformance->GetDefaultAudioPath(&pPath)))         {             IDirectMusicGraph *pGraph = NULL;             pPath->GetObjectInPath(0,                 DMUS_PATH_PERFORMANCE_GRAPH,     // The Performance Tool Graph.                 0,                 GUID_All_Objects,0,              // Only one object type.                 IID_IDirectMusicGraph,           // The Graph interface.                 (void **)&pGraph);             if (pGraph)             {                 m_pNotifications = new CNotificationManager(this);                 if (m_pNotifications)                 {                     // Insert it in the Graph. It will process all lyric and                     // notification pmsgs that are played on this Performance.                     pGraph->InsertTool(                         static_cast<IDirectMusicTool*>(m_pNotifications),                         NULL,0,0);                 }                 pGraph->Release();             }             pPath->Release();         }     }     // Okay, hopefully we have a CNotificationManager.     if (m_pNotifications)     {         // If enabling or disabling, choose the appropriate routine.         if (fEnable)         {             m_pNotifications->EnableNotifications(dwType);         }         else         {             m_pNotifications->DisableNotifications(dwType);         }     } } 

CAudio::GetNextNotification() simply passes through to CNotificationManager::GetNextNotification().

 CNotification *CAudio::GetNextNotification() {     if (m_pNotifications)     {         return m_pNotifications->GetNextNotification();     }     return NULL; } 

Finally, CAudio::Close(), which the application calls when it is finished with CAudio, releases the CNotificationManager.

     if (m_pNotifications)     {         m_pNotifications->Release();         m_pNotifications = NULL;     } 

To use the notifications, Jones works a little differently than a typical application might. Jones displays notifications instead of using notifications to trigger changes in behavior. Jones keeps the most recent eight CNotifications in a queue so that it can continuously display them in its notification display.

The following code is called every frame (in Jones, this is every 30 milliseconds). It reads any new notifications from the Performance and places them in its own queue. It then culls the queue to keep it limited to eight. If the queue changed, it redraws the display.

     bool fChanged = false;     if (m_pAudio)     {         CNotification *pNotif;         // Pull in new notifications and add them to the list.         while (pNotif = m_pAudio->GetNextNotification())         {             m_NotificationList.AddTail(pNotif);             fChanged = true;         }         // We want to keep a list of the last eight notifications for drawing.         // So, if the list is longer than eight, remove the items at the top.         while (m_NotificationList.GetCount() > 8)         {             pNotif = m_NotificationList.RemoveHead();             delete pNotif; // Just delete the CNotification when done with it.             fChanged = true;         }     }     if (fChanged)     {          // We can draw the new list in the display. 

Case Study: Return to Begonia's Amazing Dance Moves

Okay, since Jones' use of CNotificationManager is a little nonstandard, let's take a look at how the Begonia dev team might use it. This is closer to how you might use it in your application.

First, determine which notifications to allow with a call to EnableNotifications().

 m_pAudio->EnableNotifications(NOTIFYENABLE_LYRIC |      // Enable lyrics                              NOTIFYENABLE_PERFORMANCE | // Enable Performance                              NOTIFYENABLE_SEGMENT |     // Enable Segment                              NOTIFYENABLE_TIMESIG,true);// Enable time signature 

This activates capture of just the notification categories that we care about.

Then, we need code to read and process notifications. This code should be called once per frame. It reads the current notifications and responds to them as appropriate by setting global state variables. The code is pretty self explanatory:

     CNotification *pNotif;     while (pNotif = m_pAudio->GetNextNotification())     {         switch (pNotif->GetType())         {         case NOTIFICATION_LYRIC :             if (!strcmp(pNotif->GetName(),"Agony Over"))             {                 g_dwBegoniaMood = BM_UPBEAT;             }             else if (!strcmp(pNotif->GetName(),"Shout"))             {                 g_dwBegoniaMood = BM_WHO_ME;             }             break;         case NOTIFICATION_PERFALMOSTEND :             g_dwMusicState = MS_EXPIRING;             break;         case NOTIFICATION_SEGSTART :             if (pNotif->GetObject() == g_pDanceMusic)             {                 g_dwBegoniaMove = BD_START;             }             break;         case NOTIFICATION_MEASUREBEAT :             if (pNotif->GetData() == 2)             {                 g_dwBegoniaMove = BD_JUMP;             }             else             {                 g_dwBegoniaMove = BD_WIGGLE;             }             break;         }         // Done with the notification.         delete pNotif;     } } 

Until now, we've focused on DirectX Audio techniques for building a sound and music environment that responds effectively and realistically to user and game stimuli.

However, for the best possible experience, the control should go both ways, and in some cases audio should drive gameplay and visuals. With notification support, we have the tools to do so.




DirectX 9 Audio Exposed(c) Interactive Audio Development
DirectX 9 Audio Exposed: Interactive Audio Development
ISBN: 1556222882
EAN: 2147483647
Year: 2006
Pages: 170

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net