Adding AudioPaths to Jones


Okay, let's have some fun and add AudioPath support to Jones. We can add some serious features that make it easy to create and explore AudioPaths. These include:

  • Creating AudioPaths, both from configuration files as well as the predefined standard AudioPaths

  • Retrieving AudioPath statistics for display

  • Scanning and then displaying for editing many of the objects (such as Tools, DMOs, and 3D) within the AudioPath

  • Playing Segments on AudioPaths

Figure 11-4 shows what Jones with AudioPath support looks like. Notice how Jones has been rearranged to make room for the new AudioPaths panel to the right of the Segments panel.

click to expand
Figure 11-4: Jones with AudioPaths.

Let's walk through this and learn a bit about the code.

Using AudioPaths in Jones

Start by creating a new AudioPath. First, select which type of AudioPath you'd like to create from the drop-down menu below AudioPaths.

The choices are:

  • Configuration: Creates an AudioPath from an AudioPath configuration file. Clicking on Create opens a file dialog, from which you can choose the configuration file (extension .aud).

  • Music: Creates a predefined music AudioPath. This path has two buffers: Dry Stereo and Reverb. Note that multiple instances all share the same two Buffers. All other predefined types have dynamic buffers, where each instance gets a new buffer.

  • 3D: Creates a predefined 3D AudioPath. This path has one mono buffer with DirectSound 3D capabilities.

  • Mono: Creates a predefined Mono AudioPath. This path has one mono buffer.

  • Stereo: Creates a predefined Stereo AudioPath. This path has one stereo buffer.

Make your choice from the menu, and then click on Create. The new AudioPath appears in the list just under the Create button. This list shows all created AudioPaths.

If you'd like, experiment by creating a few paths of different types. You might try the AudioPath configuration file BigPath.aud, which comes with many Buffers and DMOs. It is designed with the Segment AudioPath.sgt in mind.

Click on an AudioPath in the list. This selects it for display as well as playback. Load a Segment and play it via the AudioPath simply by selecting both in their respective lists. Experiment by playing the same Segment in different AudioPaths. Be sure to try AudioPath.sgt.

The box to the right displays the name of the current path at the top along with some statistics about it. The statistics itemize the PChannels, Tools, Buffers, and DMOs that are in the AudioPath.

Below the statistics are lists of components that you can access and edit within the AudioPath. These include the AudioPath volume as well as any Tools, DMOs, and Buffers that exist in the path. Doubleclick on any item in this list. Most items will open a property page that you can then edit. For example, click on Volume to edit the volume in the Volume dialog.

click to expand

If you have a Segment already playing on the AudioPath, you can adjust any of these parameters in real time and hear the change to the music immediately. For example, create a Music path. Select it and start music playing on it. Go to the list and double-click on the Waves Reverb item. This is the reverb DMO used for music playback.

click to expand

While the music plays, you can adjust the reverb parameters and hear the result immediately. Press the Apply button to activate the changes.

Jones AudioPath Structures

We add a new class, CAudioPath, to Jones. Each manages a running instance of an AudioPath. We won't bother with wrapping the AudioPath configuration. For that, we'll load one every time we need it to create an AudioPath. Since the Loader caches file objects, this isn't as expensive as it sounds. CAudioPath keeps track of one running instance of an AudioPath via an IDirectMusicAudioPath interface pointer. This can be used to choose an AudioPath for playback and control its parameters in real time.

CAudioPath also provides a series of methods for directly getting and setting useful parameters, such as Buffer frequency and 3D position. Two methods, GetObjectInfo() and GetObject(), were designed with Jones in mind. They provide a way to iterate through all objects and then access them individually for display. This is not something you would do in a regular application, but it's great for spelunking an AudioPath.

CAudioPath inherits from the CMyNode base class so that an unlimited list of CAudioPath objects can be easily managed.

 class CAudioPath : public CMyNode { public:     // Constructor.     CAudioPath(IDirectMusicAudioPath *pAudioPath,WCHAR *pzwName);     ~CAudioPath();     // We keep a linked list of CAudioPaths.     CAudioPath *GetNext() { return (CAudioPath *) CMyNode::GetNext(); };     // Access methods.     IDirectMusicAudioPath *GetAudioPath() { return m_pAudioPath; };     char *GetName() { return m_szName; };     // Methods to report stats on the composition of the AudioPath.     DWORD GetPChannelCount();     DWORD GetToolCount();     DWORD GetBufferCount(bool fMixin);     DWORD GetDMOCount();     // Methods to scan and access objects in the path.     bool GetObjectInfo(DWORD dwIndex, char *pszName, AudioPathItemInfo *pItem);     bool GetObject(AudioPathItemInfo *pItem,REFGUID iidInterface,void **ppObject);     // Methods to get and set useful parameters in the AudioPath.     long GetVolume() { return m_lVolume; };     void SetVolume(long lVolume);     bool GetBufferParams(DWORD dwBuffer, DWORD dwStage, DWORD dwPChannel,         long *plVolume, long *plPan, DWORD *pdwFrequency);     bool SetBufferParams(DWORD dwBuffer, DWORD dwStage, DWORD dwPChannel,         long lVolume, long lPan, DWORD dwFrequency);     bool Get3DParams(DWORD dwBuffer, DWORD dwStage,         DWORD dwPChannel, DS3DBUFFER *p3DParams);     bool Set3DParams(DWORD dwBuffer, DWORD dwStage,         DWORD dwPChannel, DS3DBUFFER *p3DParams); private:     bool ClassIDToName(REFGUID rguidClassID,char *pszName);     char m_szName[20];                         // Name, for display     IDirectMusicAudioPath * m_pAudioPath;      // The DirectMusic AudioPath object.     long m_lVolume;                            // Keep track of current volume. }; 

CAudioPathList is based on CMyList and manages a linked list of CAudioPaths.

 class CAudioPathList : public CMyList { public:     // Overrides for CMyList methods.     CAudioPath *GetHead() { return (CAudioPath *) CMyList::GetHead(); };     CAudioPath *RemoveHead() { return (CAudioPath *) CMyList::RemoveHead(); };     // Clear list and release all references.     void Clear(); }; 

CAudio introduces a new field to keep track of the list of AudioPaths.

    CAudioPathList m_AudioPathList;                // List of created AudioPaths. 

It adds two methods for creating AudioPaths.

    CAudioPath *CreateAudioPathFromConfig(WCHAR *pwzFileName);    CAudioPath *CreateStandardAudioPath(DWORD dwType, DWORD dwChannels); 

It also adds a CAudioPath parameter to CAudio::PlaySegment(), so you can now play a Segment on a specific AudioPath.

    void PlaySegment(CSegment *pSegment,CAudioPath *pPath); // Play a Segment. 

As before, we look only at the code implemented in the audio classes. We do not pay attention to the MFC Windows code. You should be able to take these classes and use them equally effectively with any UI framework, including WTL or direct Windows programming.

Creating an AudioPath

CAudio::CreateAudioPathFromConfig() loads the AudioPath configuration from the passed file path and uses it to create an AudioPath. First, it loads the configuration from the file. Then, it retrieves the name of the configuration by QI'ing for the IDirectMusicObject interface and using that to get the object descriptor. This normally wouldn't be required in an application, but we want to display the name of the AudioPath in Jones. Next, CreateAudioPathFromConfig() calls IDirectMusicPerformance8::CreateAudioPath() to create the AudioPath and, assuming the call succeeds, creates a CAudioPath class to wrap the AudioPath. It places the new CAudioPath in its list of AudioPaths, m_AudioPathList.

Note that CreateAudioPathFromConfig() doesn't keep the AudioPath configuration around. Doing so would be redundant, since the Loader keeps a copy in its own cache. The next time we try to create an AudioPath from this configuration, the Loader avoids a file load, so we get the efficiency we need.

 CAudioPath *CAudio::CreateAudioPathFromConfig(WCHAR *pwzFileName) {     CAudioPath *pPath = NULL;     IUnknown *pIConfig;     // First, load the configuration.     if (SUCCEEDED(m_pLoader->LoadObjectFromFile(         CLSID_DirectMusicAudioPathConfig,   // AudioPath config class ID.         IID_IUnknown,                       // No special interface.         pwzFileName,                        // File path.         (void **) &pIConfig)))              // Config returned in pIConfig.     {         // Get the config's name by retrieving the object         // descriptor from the configuration.         DMUS_OBJECTDESC Desc;         Desc.dwSize = sizeof(Desc);         wcscpy(Desc.wszName,L"No Name");         IDirectMusicObject *pIObject = NULL;         pIConfig->QueryInterface(IID_IDirectMusicObject,(void **)&pIObject);         if (pIObject)         {             pIObject->GetDescriptor(&Desc);             pIObject->Release();         }         // Now, use this to create a live AudioPath.         IDirectMusicAudioPath *pIPath = NULL;         m_pPerformance->CreateAudioPath(pIConfig,true,&pIPath);         if (pIPath)         {             // Create a CAudioPath object to manage it.             pPath = new CAudioPath(pIPath,Desc.wszName);             if (pPath)             {                 m_AudioPathList.AddTail(pPath);             }             pIPath->Release();         }         pIConfig->Release();     }     return pPath; } 

The constructor for CAudioPath simply stores the AudioPath and its name. The constructor keeps the name in ASCII format because that's all we need for the UI.

 CAudioPath::CAudioPath(IDirectMusicAudioPath *pAudioPath,WCHAR *pzwName) {     m_pAudioPath = pAudioPath;     pAudioPath->AddRef();     wcstombs(m_szName,pzwName,sizeof(m_szName));     m_lVolume = 0; } 

Getting AudioPath Stats

Now that we have an AudioPath loaded, let's see what we can learn from it. Unfortunately, there are no straightforward DirectMusic methods for reading an AudioPath's capabilities because the assumption is that every AudioPath is designed specifically for the application that uses it, so there would be no reason to interrogate it. However, for an application like Jones, where we can load any arbitrary AudioPath and look at it, we need such a method.

No problem. Ve haff meanss for makink ze AudioPath talk!

PChannel Count

First, we create a method (CAudioPath::GetPChannelCount()) that figures out how many pchannels belong to the AudioPath in a somewhat hokey way. IDirectMusicAudioPath has a method, Convert-PChannel(), that is used to convert any pchannel from the AudioPath virtual pchannel space to the absolute pchannel value in the Performance. We call ConvertPChannel() with virtual pchannel values of 0 and counting, up until it fails. This assumes that the pchannels are 0 based. I've never run into a situation where they aren't, so it's relatively safe, and since this is only used for display, it's not the end of the world if we miss some esoteric non-zero-based pchannel assigments.

 DWORD CAudioPath::GetPChannelCount() {     DWORD dwCount = 0;     if (m_pAudioPath)     {         HRESULT hr = S_OK;         for (;hr == S_OK;dwCount++)         {             DWORD dwResult;             // Normally, we'd use this to convert from the AudioPath's             // pchannel to its equivalent value in the Performance.             hr = m_pAudioPath->ConvertPChannel(dwCount,&dwResult);         }         dwCount--;     }     return dwCount; } 

Tool Count

To find out how many Tools are embedded in the AudioPath, CAudio-Path::GetToolCount() calls GetObjectInPath(), iterating through Tools in the DMUS_PATH_AUDIOPATH_TOOL stage until it fails. This works because the generic class ID, GUID_All_Objects, is used instead of a specific Tool class ID.

 DWORD CAudioPath::GetToolCount() {     DWORD dwCount = 0;     for (dwCount = 0; ;dwCount++)     {         IUnknown *pUnknown;         if (SUCCEEDED(m_pAudioPath->GetObjectInPath(             DMUS_PCHANNEL_ALL,          // Search all pchannels.             DMUS_PATH_AUDIOPATH_TOOL,   // Look in the Tool stage.             0,                          // No buffer!             GUID_All_Objects,           // All Tools.             dwCount,                    // Nth Tool.             IID_IUnknown,               // Generic interface.             (void **)&pUnknown)))         {             // We found another Tool, so continue.             pUnknown->Release();         }         else         {             // No more Tools. Quit.             break;         }     }     return dwCount; } 

Buffer Count

To figure out how many DirectSound buffers are included in the AudioPath, we use a similar technique. CAudioPath::GetBuffer-Count() uses GetObjectInPath() to scan for Buffers. However, it is complicated by the fact that there are two sets of Buffers: Sink-in, which take their input from the synth, and Mix-in, which take their input from other Buffers. To handle this and avoid redundant code, GetBufferCount() receives one parameter, fMixIn, which determines whether to look in the DMUS_PATH_MIXIN_BUFFER or DMUS_ PATH_BUFFER stage. It iterates through the Buffers by calling GetObjectInPath() until it fails.

 DWORD CAudioPath::GetBufferCount(bool fMixin) {     DWORD dwPChannel;     DWORD dwStage;     // If we are searching Mix-in Buffers...     if (fMixin)     {         // Pchannel must be 0.         dwPChannel = 0;         // Set stage to Mix-in Buffers.         dwStage = DMUS_PATH_MIXIN_BUFFER;     }     else     {         // Pchannel must be all channels.         dwPChannel = DMUS_PCHANNEL_ALL;         // Set stage to Sink-in Buffers.         dwStage = DMUS_PATH_BUFFER;     }     DWORD dwCount = 0;     for (dwCount = 0; ;dwCount++)     {         IUnknown *pUnknown;         if (SUCCEEDED(m_pAudioPath->GetObjectInPath(             dwPChannel,         // Look on all pchannels.             dwStage,            // Searching the appropriate stage.             dwCount,            // Nth buffer.             GUID_All_Objects,   // Ignore class ID (can only get Buffers).             0,                  // No index.             IID_IUnknown,       // Generic interface.             (void **)&pUnknown)))         {             // Hah! We found another Buffer!             pUnknown->Release();         }         else         {             // No more Buffers. Quit.             break;         }     }     return dwCount; } 

DMO Count

CAudioPath::GetDMOCount() figures out how many DMOs exist in the AudioPath. It does so by using GetObjectInPath() to scan for DMOs in each of the Sink-in and Mix-in Buffers.

 DWORD CAudioPath::GetDMOCount() {     DWORD dwCount = 0;     DWORD dwBufferCount = GetBufferCount(false);     // There will be two passes. First the Sink-in Buffers (which     // read from the synth). Then, the Mix-in Buffers, which receive     // from other Buffers.     DWORD dwDMOStage[2] = {         DMUS_PATH_BUFFER_DMO,         DMUS_PATH_MIXIN_BUFFER_DMO };     DWORD dwPChannel[2] = { DMUS_PCHANNEL_ALL, 0 };     for (DWORD dwBufferType = 0; dwBufferType < 2; dwBufferType++)     {         DWORD dwBuffer;         for (dwBuffer = 0; dwBuffer < dwBufferCount;dwBuffer++)         {             // Now, for each Buffer, iterate through the DMOs.             for (DWORD dwDMO = 0; ;dwDMO++)             {                 IUnknown *pUnknown;                 if (SUCCEEDED(m_pAudioPath->GetObjectInPath(                     dwPChannel[dwBufferType],   // Search all pchannels.                     dwDMOStage[dwBufferType],   // Buffer DMO stage.                     dwBuffer,                   // Which Buffer to search.                     GUID_All_Objects,           // Search for all object types.                     dwDMO,                      // Index of DMO.                     IID_IUnknown,               // Look for base interface.                     (void **)&pUnknown)))                 {                     // DMO was found! Increment.                     dwCount++;                     pUnknown->Release();                 }                 else                 {                     // No DMO, move on to the next Buffer.                     break;                 }             }         }         // Prepare Buffer count for second pass.         dwBufferCount = GetBufferCount(true);     }     return dwCount; } 

Scanning an AudioPath

Jones needs a way to iterate through all of the objects that were authored into an AudioPath so they can be displayed and double-clicked for editing. These include any DMOs and Tools, as well as the Buffer parameters, including 3D. Normally, this is not particularly useful because an application should know what it needs to access and request them directly. Following that philosophy, DirectMusic's AudioPath API was not designed with this sort of inspection in mind. In other words, there is no direct API that you can call and query for the nth object in the AudioPath. You can sort of do it with GetObjectInPath(), but you must iterate through stages, buffers, and then objects within the buffers.

CAudioPath::GetObjectInfo() does just that, but it gets all the extra work out of the way for you. It iterates through all objects within an AudioPath for which we can subsequently throw up a UI of some sort. It uses a data structure, called AudioPathItemInfo, to return all of the parameters (stage, buffer, index) that were needed to access the nth object in the AudioPath, and it stores an identifier for the type of object (Tool, DMO, Buffer, or 3D) in AudioPathItem-Info. A subsequent call to GetObject() with the AudioPathItemInfo structure can quickly access the specified object.

GetObjectInfo() also returns a name for the object. Unfortunately, none of the objects have a way to return a friendly name. So, GetObjectInfo() improvises as best it can. For DMOs, it looks at the class ID to see if it is one of the familiar DMOs that ships with DirectX and, if so, sticks in the name. Otherwise, it just gives generic names, like "DMO 3," etc.

Here's the code. First, we define the types of objects that we are looking for. These really reflect UI operations that we know we can do in Jones with these particular items.

 typedef enum _AP_ITEM_TYPE {     AUDIOPATH_VOLUME = 1, // Set the AudioPath volume.     AUDIOPATH_TOOL = 2, // Open a Tool's property page.     AUDIOPATH_DMO = 3, // Open a DMO's property page.     AUDIOPATH_BUFFER = 4, // Set Buffer parameters.     AUDIOPATH_3D = 5 // Set 3D parameters. } AP_ITEM_TYPE; 

The AudioPathItemInfo structure records everything needed to find the nth item in the AudioPath:

 typedef struct _AudioPathItemInfo {     AP_ITEM_TYPE    ItemType;   // Which type of object.     DWORD           dwStage;    // Which stage to look in.     DWORD           dwBuffer;   // Which Buffer, if applicable.     DWORD           dwIndex;    // Index into nth object in stage.     DWORD           dwPChannel; // Pchannel, if applicable. } AudioPathItemInfo; 

Finally, here's the big kahuna, GetObjectInfo(). This scans for the nth item (within the set of things it is looking for) in the AudioPath. To do so, it starts at the top and keeps decrementing the passed iterator, dwIndex, until it hits zero, at which point it returns the current item.

 bool CAudioPath::GetObjectInfo(    DWORD dwIndex,            // Nth item to look for.    char *pszName,            // Returned name.    AudioPathItemInfo *pItem) // Returned parameters needed to retrieve item. {    // Initialize the AudioPathItemInfo.    pItem->dwBuffer = 0;    pItem->dwIndex = 0;    pItem->dwStage = 0;    // If this is the very start, return the volume parameter.    if (dwIndex == 0)    {        strcpy(pszName,"Volume");        pItem->ItemType = AUDIOPATH_VOLUME;        return true;    }    dwIndex--;    // Okay, now see if this is a tool.    DWORD dwToolCount = GetToolCount();    if (dwIndex < dwToolCount)    {        // Since Tools don't have accessible names, just        // identify which Tool it is.        sprintf(pszName,"Tool %d",dwIndex+1);        pItem->ItemType = AUDIOPATH_TOOL;        pItem->dwIndex = dwIndex;        pItem->dwStage = DMUS_PATH_AUDIOPATH_TOOL;        return true;    }    dwIndex -= dwToolCount;    // Now, we look at all the items in the Buffers.    // There will be two passes. First the Sink-in Buffers (which    // read from the synth). Then, the Mix-in Buffers, which receive    // from other Buffers.    // Set up the variables that change for the two passes.    // There are two DMO stages in the AudioPath, one for each Buffer type.    DWORD dwDMOStage[2] = {        DMUS_PATH_BUFFER_DMO,        DMUS_PATH_MIXIN_BUFFER_DMO };    // And, there are two Buffer stages.    DWORD dwBufferStage[2] = { DMUS_PATH_BUFFER, DMUS_PATH_MIXIN_BUFFER };    // Pchannels are handled differently for Sink-in vs Mix-in Buffers.    DWORD dwPChannel[2] = { DMUS_PCHANNEL_ALL, 0 };    // Variable to track the two passes.    DWORD dwBufferType;    // Okay, let's do it.    for (dwBufferType = 0; dwBufferType < 2; dwBufferType++)    {        // How many Buffers of this type?        DWORD dwBufferCount = GetBufferCount(dwBufferType == 1);        // Get the pchannel to use for this Buffer type.        pItem->dwPChannel = dwPChannel[dwBufferType];        // For each Buffer (there can easily be more than one...)        pItem->dwBuffer = 0;        for (;pItem->dwBuffer < dwBufferCount;pItem->dwBuffer++)        {            // First, check if we are at the Buffer itself. If so,            // just return success.            if (dwIndex == 0)            {                // Buffers don't have names, so just use a counter.                if (dwBufferType)                {                    sprintf(pszName,"MixBuffer %d",pItem->dwBuffer);                }                else                {                    sprintf(pszName,"SinkBuffer %d",pItem->dwBuffer);            }            pItem->dwStage = dwBufferStage[dwBufferType];            pItem->ItemType = AUDIOPATH_BUFFER;            return true;            }        dwIndex--;        IUnknown *pUnknown;        // Okay, it's not the Buffer. Now, iterate through the DMOs.        for (pItem->dwIndex = 0;;pItem->dwIndex++)        {            if (SUCCEEDED(m_pAudioPath->GetObjectInPath(                pItem->dwPChannel,         // Pchannels.                dwDMOStage[dwBufferType],  // Which Buffer DMO stage.                pItem->dwBuffer,           // Which Buffer are we looking at?                GUID_All_Objects,          // Scan all DMOs.                pItem->dwIndex,            // Look for the Nth DMO.                IID_IUnknown,              // Just the base interface.                (void **)&pUnknown)))            {                // If index is 0, we've found our DMO.                if (dwIndex == 0)                {                    // We'll use IPersist to get the class ID.                    IPersist *pPersist;                    // Put in a default name.                    sprintf(pszName," DMO %d",pItem->dwIndex+1);                    // See if it has an IPersist and, if so, get the class ID.                    if (SUCCEEDED(pUnknown->QueryInterface(IID_IPersist,                        (void **) &pPersist)))                    {                        CLSID clsid;                        pPersist->GetClassID(&clsid);                        // With the class ID, we might recognize one                        // of our standard DMOs.                        ClassIDToName(clsid,pszName);                        pPersist->Release();                    }                    // Fill in the rest.                    pItem->dwStage = dwDMOStage[dwBufferType];                    pItem->ItemType = AUDIOPATH_DMO;                    pUnknown->Release();                    return true;                }                pUnknown->Release();                dwIndex--;            }            else            {                // Ran out of DMOs, so go to next Buffer.                break;                }            }            pItem->dwIndex = 0;            // Check to see if this Buffer supports 3D.            IDirectSound3DBuffer *p3DBuffer;            if (SUCCEEDED(m_pAudioPath->GetObjectInPath(                pItem->dwPChannel,             // Pchannel.                dwBufferStage[dwBufferType],   // Which Buffer stage.                pItem->dwBuffer,               // Which Buffer.                GUID_All_Objects,0,            // Don't care about object class.                IID_IDirectSound3DBuffer,      // Does it have the 3D interface?                (void **)&p3DBuffer)))            {                // If there's a 3D interface, this must be a 3D Buffer.                p3DBuffer->Release();                if (dwIndex == 0)                {                    // Ooh, goody, we got it.                    strcpy(pszName," 3D");                    pItem->dwStage = dwBufferStage[dwBufferType];                    pItem->ItemType = AUDIOPATH_3D;                    return true;                }                dwIndex--;            }         }    }    return false; } 

To use GetObjectInfo(), the application calls it in a for loop, incrementing the iterator dwIndex and retrieving the name and Audio-PathItemInfo for each item found until GetObjectInfo() eventually runs out and returns false. That's exactly what Jones does by building a list of all the items, which it displays in the list on the right. Then, when the user double-clicks on an item in the list, Jones has what it needs to retrieve the item for editing.

Editing AudioPath Object Parameters

Now that we've got this nice list with all the items and their names, retrieving an object is straightforward because we have everything we need to call the AudioPath's GetObjectInPath() method.

Accessing with AudioPathItemInfo

To directly access any generic object using the AudioPathItemInfo structure, we have a convenience function, GetObject(), which is very simple. GetObject() uses the AudioPathItemInfo structure that was filled in by GetObjectInfo(). It also provides the ID of the interface that it is expecting. Together, these are used to call GetObjectInPath() to retrieve the desired interface.

 bool CAudioPath::GetObject(AudioPathItemInfo *pItem,                            REFGUID iidInterface,                            void **ppObject) {     return (SUCCEEDED(m_pAudioPath->GetObjectInPath(         pItem->dwPChannel,  // Use the pchannel that GetObjectInfo returned.         pItem->dwStage,     // And, the stage that GetObjectInfo returned.         pItem->dwBuffer,    // Likewise, use the Buffer.         GUID_All_Objects,   // Get any object type.         pItem->dwIndex,     // Get the Nth item.         iidInterface,       // Caller requested an interface.         ppObject)));        // Returned interface. } 

Accessing Buffer Parameters

CAudioPath has two methods (GetBufferParams() and SetBuffer-Params()) for reading and writing the Buffer volume, pan, and frequency. When you double-click on a Buffer in the list, Jones opens a dialog to edit these three parameters and uses these two methods to retrieve and set the data. Note that these two methods do not use the AudioPathItemInfo structure. GetBufferParams() and SetBuffer-Params() can be used much more broadly, since typically the application already knows specifically what Buffer it needs to access. Therefore, these routines are great examples of using GetObject-InPath() to access and manipulate a specific object in an AudioPath.

 /* CAudioPath::GetBufferParams    GetBufferParams is a convenience function that retrieves the current    volume, pan, and frequency from the Buffer.    The Buffer is defined by Buffer index, pchannel, and stage, since this    could be a Sink-in or Mix-in Buffer. */ bool CAudioPath::GetBufferParams(DWORD dwBuffer,                                  DWORD dwStage,                                  DWORD dwPChannel,                                  long *plVolume,                                  long *plPan,                                  DWORD *pdwFrequency) {    bool fSuccess = false;    // We'll be retrieving an IDirectSoundBuffer interface.    IDirectSoundBuffer *pBuffer = NULL;    // Use GetObjectInPath to retrieve the Buffer.    if (SUCCEEDED(m_pAudioPath->GetObjectInPath(        dwPChannel,         // The pchannel.        dwStage,            // Mix-in or Sink-in stage.        dwBuffer,           // Which Buffer.        GUID_All_Objects,0, // No need for class ID.        IID_IDirectSoundBuffer,        (void **)&pBuffer)))    {        // We got it. Go ahead and read the three parameters.        pBuffer->GetFrequency(pdwFrequency);        pBuffer->GetPan(plPan);        pBuffer->GetVolume(plVolume);        pBuffer->Release();        fSuccess = true;    }    return fSuccess; } /* CAudioPath::SetBufferParams    SetBufferParams is a convenience function that sets the    volume, pan, and frequency for the Buffer.    The Buffer is defined by buffer index, pchannel, and stage, since this    could be a Sink-in or Mix-in Buffer. */ bool CAudioPath::SetBufferParams(DWORD dwBuffer,                                  DWORD dwStage,                                  DWORD dwPChannel,                                  long lVolume,                                  long lPan,                                  DWORD dwFrequency) {    bool fSuccess = false;    // We'll be using an IDirectSoundBuffer interface.    IDirectSoundBuffer *pBuffer = NULL;    // Use GetObjectInPath to retrieve the Buffer.    if (SUCCEEDED(m_pAudioPath->GetObjectInPath(        dwPChannel,         // The pchannel.        dwStage,            // Mix-in or Sink-in stage.        dwBuffer,           // Which Buffer.        GUID_All_Objects,0, // No need for class ID.        IID_IDirectSoundBuffer,        (void **)&pBuffer)))    {        // We got it. Go ahead and set the three parameters.        pBuffer->SetFrequency(dwFrequency);        pBuffer->SetPan(lPan);        pBuffer->SetVolume(lVolume);        pBuffer->Release();        fSuccess = true;    }    return fSuccess; } 

Accessing 3D Parameters

CAudioPath has two methods (Get3DParams() and Set3DParams()) for reading and writing the 3D position of a 3D DirectSound Buffer. Use Set3DParams() to continuously update the position of a 3D object that is being tracked in space by a 3D AudioPath.

 /* CAudioPath::Get3DParams    Get3DParams is a convenience function that retrieves the current    3D parameters from a 3D Buffer. */ bool CAudioPath::Get3DParams(DWORD dwBuffer,                              DWORD dwStage,                              DWORD dwPChannel,                              DS3DBUFFER *p3DParams) {     bool fSuccess = false;     // We'll be using an IDirectSound3DBuffer interface.     IDirectSound3DBuffer *p3DBuffer = NULL;     // Use GetObjectInPath to retrieve the Buffer.     if (SUCCEEDED(m_pAudioPath->GetObjectInPath(         dwPChannel,         dwStage,         dwBuffer,         GUID_All_Objects,0,         IID_IDirectSound3DBuffer,         (void **)&p3DBuffer)))     {         // Okay, we got the Buffer. Read it.         fSuccess = true;         p3DBuffer->GetAllParameters(p3DParams);         p3DBuffer->Release();     }     return fSuccess; } /* CAudioPath::Set3DParams    Set3DParams is a convenience function that writes    3D parameters to a 3D Buffer. */ bool CAudioPath::Set3DParams(DWORD dwBuffer,                              DWORD dwStage,                              DWORD dwPChannel,                              DS3DBUFFER *p3DParams) {     bool fSuccess = false;     // We'll be using an IDirectSound3DBuffer interface.     IDirectSound3DBuffer *p3DBuffer = NULL;     // Use GetObjectInPath to retrieve the Buffer.     if (SUCCEEDED(m_pAudioPath->GetObjectInPath(         dwPChannel,         dwStage,         dwBuffer,         GUID_All_Objects,0,         IID_IDirectSound3DBuffer,         (void **)&p3DBuffer)))     {         // Okay, we got the Buffer. Write to it.         fSuccess = true;         p3DBuffer->SetAllParameters(p3DParams,DS3D_IMMEDIATE);         p3DBuffer->Release();     }     return fSuccess; } 

Accessing Tools and DMOs

CAudioPath doesn't have any special methods for accessing Tools or DMOs because they would be nothing more than very thin wrappers over GetObjectInPath(). Tools and DMOs have four different ways of providing control, and GetObjectInPath() provides access to all. They are:

  • A specific programming interface: Each object provides its own COM interface that allows direct program control over its parameters. For example, the IDirectSoundFXEcho8 interface provides direct access to read and write the Echo parameters.

  • The IMediaParams interface: This interface provides timeline control over each parameter in a standardized way. It includes methods, used in editors, for querying the names and ranges of each parameter, as well as methods for setting parameters with continuously changing curves. DirectMusic supports this via the Parameter Control Track.

  • The IPersistStream interface: This interface provides a standard way to read and write data from a file stream. All DMOs and Tools must support this if they have any data they must read from disk. This can also be used to write data directly into an object, though given the other options, it's typically not the best approach.

  • A property page: Each DMO and Tool has the option of providing a property page, which can be used to edit its parameters in an authoring tool. To do so, the DMO or Tool must provide an ISpecifyPropertyPages interface, which, in turn, leads to the IPropertyPage interface that manages the property page.

Jones takes the last approach, using GetObjectInPath() to access the DMO or Tool's ISpecifyPropertyPages interface to open the property dialog. The code to do this is remarkably simple:

 ISpecifyPropertyPages *pPages; if (SUCCEEDED(pAudioPath->GetObjectInPath(     DMUS_PCHANNEL_ALL,           // Pchannel.     DMUS_PATH_BUFFER_DMO,        // Look at the DMO stage.     0,GUID_All_Objects,          // Any DMO.     dwIndex,                     // Nth DMO.     IID_ISpecifyPropertyPages,   // Get the property page interface.     (void **)&pPages))) {     // Success! It has a property page!     CAUUID PageID;     pPages->GetPages(&PageID);     pPages->Release();     // Use the Windows Property page mechanism to view it.     OleCreatePropertyFrame(m_hWnd,0,0,         L"DMO Editor",1,(IUnknown **) &pPages,1,         PageID.pElems,0,0,0); } 

Accessing Volume

Volume is a special case because the command to adjust the volume is built directly into the IDirectMusicAudioPath interface. For completeness, CAudioPath provides methods for getting and setting the volume.

 void CAudioPath::SetVolume(long lVolume) {      m_pAudioPath->SetVolume(lVolume,0);      m_lVolume = lVolume; } 

GetVolume() simply returns the last volume that was set.

 long GetVolume() { return m_lVolume; }; 

Performing with an AudioPath

Playing a Segment with an AudioPath is simple; just pass the AudioPath as a parameter to PlaySegmentEx(). So, we update CAudio::PlaySegment() to include a CAudioPath as a parameter:

 void CAudio::PlaySegment(CSegment *pSegment,CAudioPath *pPath) {     if (pSegment)     {         IDirectMusicAudioPath *pIPath = NULL;         if (pPath)         {             pIPath = pPath->GetAudioPath();         }         // Is there a transition Segment?         IDirectMusicSegment8 *pTransition = NULL;         if (pSegment->GetTransition())         {             pTransition = pSegment->GetTransition()->GetSegment();         }         DWORD dwFlags = pSegment->GetFlags();         if (pTransition)         {             dwFlags |= DMUS_SEGF_AUTOTRANSITION;         }         IDirectMusicSegmentState8 *pISegState;         if (SUCCEEDED(m_pPerformance->PlaySegmentEx(             pSegment->GetSegment(), // Returns IDirectMusicSegment             NULL,pTransition,       // Use the transition, if it exists.             dwFlags,                // Playback flags.             0,                      // No time stamp.             (IDirectMusicSegmentState **) &pISegState, // Returned SegState.             NULL,                   // No prior Segment to stop.             pIPath)))               // Use AudioPath, if supplied.         {             CSegState *pSegState = new CSegState(pSegment,pISegState);             if (pSegState)             {                 m_SegStateList.AddHead(pSegState);             }             pISegState->Release();         }     } } 

Now that we have a decent understanding of what AudioPaths are and how to use them, we've got the primary pieces of the DirectMusic audio architecture in place. We know how to create an AudioPath, we know how to play music and sound effects on it, and we know how to access components within the AudioPath, so we can alter them in real-time in response to real time events.




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