LoaderView Source


LoaderView Source

The CAudio class manages all audio and Loader functionality. The LoaderView UI (CLoaderDlg) makes calls into CAudio to load and play Segments. I wrote the code using MFC (Microsoft Foundation Classes). We do not discuss the UI code here. I do my best to keep the UI code and the audio code separated in all programming examples, so we can focus on just the audio portions. Of course, I include the UI source in the LoaderView source code project in the Unit II\ 9_Loader directory on the companion CD.

CAudio also tracks a table of object descriptors (DMUS_ OBJECTDESC) for the currently selected object type and provides calls to manipulate the list. This is not enormously useful for regular applications, but it is useful for exploring the Loader's caching behavior, which is what LoaderView is all about. We walk through the layout of CAudio in just a second.

Predefined Object Types

Although the Loader is completely format agnostic, for convenience we concentrate on nine familiar DirectX Audio formats. To make communication between the UI and CAudio as simple as possible, we predefine these.

// Define the nine categories of DirectX Audio objects we will track
// in the loader's file list.

enum OBJECT_TYPE
{
    OT_AUDIOPATH = 0,  // AudioPath configuration file.
    OT_BAND = 1,       // Band file.
    OT_CHORDMAP = 2,   // ChordMap file.
    OT_CONTAINER = 3,  // Container - carries a collection of other objects.
    OT_DLS = 4,        // DLS Instrument Collection file.
    OT_SEGMENT = 5,    // Segment file.
    OT_STYLE = 6,      // Style file.
    OT_WAVE = 7,       // Wave file.
    OT_GRAPH = 8       // ToolGraph file.
};
#define OBJECT_TYPES    9
 

CAudio Class

The CAudio class manages the Loader, the Performance, and one Segment. It has routines for initialization and shutdown. It has routines to load and play Segments. It has a bunch of routines dedicated to exploring and managing the Loader's file lists. Since we cover initialization and playback in other chapters, here we focus just on the code for managing the Loader.

Here is the full definition of the CAudio class:

class CAudio
{
public:
    // Initialization and shutdown methods.
    CAudio();
    -CAudio();
    HRESULT Init();
    void Close();
    // Convenience methods for accessing DirectX Audio interfaces.
    IDirectMusicPerformance8 * GetPerformance() { return m_pPerformance; };
    IDirectMusicLoader8 * GetLoader() { return m_pLoader; };
    // Methods for loading, playing, stopping, and removing Segments.
    IDirectMusicSegment8 * LoadSegment(WCHAR *pwzFileName);
    IDirectMusicSegment8 * GetSegment() { return m_pSegment; };
    void PlaySegment();
    void StopSegment();
    void ClearSegment();
    // Methods for managing the loader's file lists and retrieving data from it.
    DWORD SetObjectType(OBJECT_TYPE otSelected);
    void GetObjectInfo(DWORD dwIndex, char *pszName, BOOL *pfCached);
    BOOL GetCacheStatus() { return m_afCacheEnabled[m_otSelected]; };
    void ChangeCacheStatus();
    void ReleaseAll();
    void ReleaseItem(DWORD dwItem);
    void ScanDirectory();
private:
    // DirectX Audio interfaces for performance, loader, and Segment.
    IDirectMusicPerformance8 * m_pPerformance;    // The Performance.
    IDirectMusicLoader8 * m_pLoader;              // The Loader.
    IDirectMusicSegment8 * m_pSegment;            // Currently loaded Segment.
    // Only one object type is active at a time. This reflects the
    // current choice in LoaderView's type drop-down list.
    OBJECT_TYPE m_otSelected;                     // Currently selected object type.
    // We maintain a table of enumerated objects of the current selected type.
    DMUS_OBJECTDESC * m_aObjectTable;             // Table of object descriptors.
    DWORD m_dwObjectCount;                        // Size of table.
    // We use a static array of class IDs to identify class type in calls to loader.
    static const GUID * m_saClassIDs[OBJECT_TYPES];
    // We need a flag for each object type to indicate whether caching is enabled.
    BOOL m_afCacheEnabled[OBJECT_TYPES];
    // Current search directory.
    WCHAR m_wzSearchDirectory[DMUS_MAX_FILENAME];
};
 

The following routines manage the Loader:

  • SetObjectType() tells CAudio which object type to view. When the user selects a type from the drop-down list, LoaderView calls this. It builds a table of DMUS_OBJECTDESC structures in m_aObjectTable.

  • GetObjectInfo() retrieves name and caching information from a specific entry in m_aObjectTable.

  • ReleaseItem() tells the Loader to release its cache reference for just one item.

  • ReleaseAll() tells the Loader to release all its cached references.

  • ChangeCacheStatus() tells the Loader to change whether or not it is caching the current object type.

  • ScanDirectory() tells the Loader to scan the current working directory to build a list of available files.

Before we discuss each of these routines, we must initialize the object type to class ID relationship.

Initialization

CAudio's Init() method creates the Loader and Performance and initializes both. It also disables caching for Segments to conserve memory.

HRESULT CAudio::Init()

{
    //Init COM.
    CoInitialize(NULL);

    // Create loader.
    HRESULT hr = CoCreateInstance(
        CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC,
        IID_IDirectMusicLoader8, (void**)&m_pLoader);
    if (SUCCEEDED(hr))
    {
        // Turn off caching of Segments.
        m_pLoader->EnableCache(CLSID_DirectMusicSegment,false);
        // Create performance.
        hr = CoCreateInstance(
            CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC,
            IID_IDirectMusicPerformance8, (void**)&m_pPerformance);
    }
    if (SUCCEEDED(hr))
    {
        // Once the performance is created, initialize it.
        // We'll use the standard music reverb audio path and give it 128 pchannels.
        hr = m_pPerformance->InitAudio(NULL,NULL,NULL,
            DMUS_APATH_SHARED_STEREOPLUSREVERB, // Default AudioPath type.
            128,DMUS_AUDIOF_ALL,NULL);   }
    if (FAILED(hr))
    {
        // No luck, give up.
        Close();
    }
    return hr;
}
 

We also have a static array of class IDs that translate from predefined integers to GUIDS, since it is much more convenient to work with predefined integers because they directly correlate with positions in the UI's drop-down list of object types.

const GUID *CAudio::m_saClassIDs[OBJECT_TYPES] = {
    &CLSID_DirectMusicAudioPathConfig,  // OT_AUDIOPATH
    &CLSID_DirectMusicBand,             // OT_BAND
    &CLSID_DirectMusicChordMap,         // OT_CHORDMAP
    &CLSID_DirectMusicContainer,        // OT_CONTAINER
    &CLSID_DirectMusicCollection,       // OT_DLS
    &CLSID_DirectMusicSegment,          // OT_SEGMENT
    &CLSID_DirectMusicStyle,            // OT_STYLE
    &CLSID_DirectMusicGraph,            // OT_WAVE
    &CLSID_DirectSoundWave              // OT_GRAPH
};
 

SetObjectType()

SetObjectType() is called when a new file class is requested for display. It sets the variable m_otSelected to the new object type and then builds the m_aObjectTable table of DMUS_OBJECTDESC structures by using the Loader's EnumObject() method.

DWORD CAudio::SetObjectType(OBJECT_TYPE otSelected)
{
    // We track the objects in an array, so delete the current array.
    delete [] m_aObjectTable;
    m_aObjectTable = NULL;
    m_dwObjectCount = 0;
    m_otSelected = otSelected;
    HRESULT hr = S_OK;
 

We need to know how many objects the Loader has of the current type before we can allocate the memory for the array. However, the Loader does not provide a routine for directly returning this information. Therefore, we use the enumeration method and scan until it returns S_FALSE, indicating that we passed the range.

// First, find out how many objects there are
// so we can allocate the right size table.
for (;hr == S_OK;m_dwObjectCount++)
{
    DMUS_OBJECTDESC Dummy;
    Dummy.dwSize = sizeof(DMUS_OBJECTDESC);
    hr = m_pLoader->EnumObject(*m_saClassIDs[m_otSelected],m_dwObjectCount,&Dummy);
}
m_dwObjectCount--;
// Now we know how many, so allocate the table.
if (m_dwObjectCount)
{
    m_aObjectTable = new DMUS_OBJECTDESC[m_dwObjectCount];
    if (m_aObjectTable)
    {
 

Once we have the array, filling it is simply a matter of calling EnumObject() a second time — this time iterating through the array.

        // Then fill the table with the object descriptors.
        DWORD dwIndex = 0;
        for (dwIndex = 0;dwIndex < m_dwObjectCount;dwIndex++)
        {
            // Set the .dwSize field so the loader knows how big it really is and
            // therefore which fields can be written to.
            m_aObjectTable[dwIndex].dwSize = sizeof(DMUS_OBJECTDESC);
            m_pLoader->EnumObject(*m_saClassIDs[m_otSelected],
                dwIndex,&m_aObjectTable[dwIndex]);
        }
    }
}
 

Finally, return the number of objects counted. The UI can use this information when displaying the list of objects.

    return m_dwObjectCount;
}
 

GetObjectInfo()

Once the UI has called SetObjectType() to create the table of object descriptors, it needs to populate its list view of those same objects. It calls GetObjectInfo() to iterate through the table and retrieve the name and cache status of each object.

void CAudio::GetObjectInfo(DWORD dwIndex, char *pszName, BOOL *pfCached)
{
    if (m_aObjectTable && (dwIndex < m_dwObjectCount))
    {
        // If the wszName field is valid, use it.
        if (m_aObjectTable[dwIndex].dwValidData & DMUS_OBJ_NAME)
        {
            // Convert from Unicode to ASCII.
            wcstombs(pszName,m_aObjectTable[dwIndex].wszName,DMUS_MAX_NAME);
        }
        // Else if the file name is available, use it.
        else if (m_aObjectTable[dwIndex].dwValidData & DMUS_OBJ_FILENAME)
        {
            // First, get the Unicode name.
            WCHAR wzTemp[DMUS_MAX_FILENAME];
            wcscpy(wzTemp,m_aObjectTable[dwIndex].wszFileName);
            // Then, remove the path.
            WCHAR *pwzStart = wcsrchr(wzTemp,'\\');
            if (pwzStart) pwzStart++;
            else pwzStart = wzTemp;
            // Convert to ASCII.
            wcstombs(pszName,pwzStart,DMUS_MAX_NAME);
        }
        // Uh oh. No name.
        else
        {
            strcpy(pszName,"<No Name>");
        }
        // The DMUS_OBJ_LOADED flag indicates that the resource is currently cached.
        *pfCached = (m_aObjectTable[dwIndex].dwValidData & DMUS_OBJ_LOADED) && TRUE;
    }
}
 

ReleaseItem()

ReleaseItem() tells the Loader to release the selected item from its cache. The item will continue to be listed, but the Loader will no longer point to it and have it AddRef()'d. Should the item be requested a second time, the Loader will create a new instance and load it.

void CAudio::ReleaseItem(DWORD dwItem)

{
    IDirectMusicObject *pObject;
    if (SUCCEEDED(m_pLoader->GetObject(&m_aObjectTable[dwItem],
        IID_IDirectMusicObject,
        (void **) &pObject)))
    {
        m_pLoader->ReleaseObject(pObject);
        pObject->Release();
    }
}

 

ReleaseAll()

ReleaseAll() tells the Loader to clear all objects of the currently selected type from its cache. All objects not currently AddRef()'d by the application will go away. This is the same as calling ReleaseItem() for every item in the list.

void CAudio::ReleaseAll()
{
    m_pLoader->ClearCache(*m_saClassIDs[m_otSelected]);
}
 

ChangeCacheStatus()

You can tell the Loader to enable or disable caching on a class-by-class basis. ChangeCacheStatus() tells the Loader to flip the caching for the currently selected type. It does so by tracking the cache state with an array of Booleans, one for each object type. It flips the state and calls the Loader's EnableCache() method to enable or disable the caching.

void CAudio::ChangeCacheStatus()

{
    // Flip the state in the m_afCacheEnabled table.
    m_afCacheEnabled[m_otSelected] = !m_afCacheEnabled[m_otSelected];
    // Call EnableCache with the new state.
    m_pLoader->EnableCache(*m_saClassIDs[m_otSelected],m_afCacheEnabled[m_otSelected]);
}
 

ScanDirectory()

ScanDirectory() tells the Loader to look in the current working directory and read all files of the currently selected type. To do so, it calls the Loader's ScanDirectory() method and passes it the wildcard * extension. This tells the Loader to open every single file in the directory and attempt to parse it with the object defined by the class ID. If the object cannot parse the file, the Loader does not include it in the list. For example, the Segment object knows how to parse Segment, MIDI, and wave files. It fails with all other files, and so the Loader does not include the failed files in the resulting list. There is one case where multiple types can parse the same file. Both the CLSID_DirectSoundWave and CLSID_DirectMusicSegment objects know how to parse a wave file.

void CAudio::ScanDirectory()
{
    m_pLoader->ScanDirectory(*m_saClassIDs[m_otSelected],L"*",NULL);
}
 

That concludes our tour of the LoaderView application. One last thing before we go