Step 3: Loading the Music Elements

The next step in setting up the DirectMusic functionality of the DMDonuts sample application is to load the music elements—styles, templates, motifs, chordmaps, and bands—from file. Like the previous step, this one is carried out in the InitializeGame function.

First the application creates the loader object:

if (!SUCCEEDED(::CoCreateInstance(
            CLSID_DirectMusicLoader,
            NULL,
            CLSCTX_INPROC, 
            IID_IDirectMusicLoader,
            (void**)&gpLoader
        )))
{
    return CleanupAndExit("Couldn't create a loader object");
}
 

Then it sets the search directory for all object types and enables the object cache. This second call simply confirms the default cache status.

hr = E_FAIL;
 
/ * The GetSearchPath function gets the media directory 
    from the registry and returns it in wszSearchPath. */
 
if (GetSearchPath(wszSearchPath))
{
    hr = gpLoader->SetSearchDirectory(
           GUID_DirectMusicAllTypes, wszSearchPath, FALSE);
}
 
/* If that directory doesn't exist, try the current directory. */
 
if (FAILED(hr))
{
    hr = gpLoader->SetSearchDirectory(GUID_DirectMusicAllTypes,
            L".", FALSE);
}
if (FAILED(hr))
{
    return CleanupAndExit("Couldn't set the search directory \
            for the DirectMusic loader");
}
gpLoader->EnableCache(GUID_DirectMusicAllTypes, TRUE);
 

The following code snippet loads the style named "Donuts", which is in the Donuts.sty file. Note that because the application hasn't called IDirectMusicLoader::ScanDirectory to build a database of objects that can be loaded by internal name, the first call to IDirectMusicLoader::GetObject will fail. The fallback procedure is to identify the object by file name and call GetObject again.

IDirectMusicObject* pObject = NULL;
DMUS_OBJECTDESC ObjectDescript;
 
ObjectDescript.dwSize = sizeof(DMUS_OBJECTDESC);
ObjectDescript.guidClass = CLSID_DirectMusicStyle;
wcscpy(ObjectDescript.wszName, L"Donuts");
ObjectDescript.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_NAME;
 
if (!SUCCEEDED(gpLoader->GetObject(&ObjectDescript,
        IID_IDirectMusicStyle, (void**)&gapStyle[1])))
{
    wcscpy(ObjectDescript.wszFileName, L"Donuts.sty");
    ObjectDescript.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME;
    if (!SUCCEEDED(gpLoader->GetObject(&ObjectDescript,
            IID_IDirectMusicStyle, (void**)&gapStyle[1])))
    {
        return CleanupAndExit("Couldn't load style object 1");
    }
}
 

The InitializeGame function then goes on to load another style and two templates in similar fashion.

The next step is to initialize an array of motifs that will be used to mark certain events in the game. Each motif is contained in a style and is obtained by calling the IDirectMusicStyle::GetMotif method, which creates a segment for the motif. The method must be supplied with the internal name of the motif, as in the following example from Donuts.cpp.

WCHAR awszMotifs[NUM_MOTIFS][64];
 
wcscpy(awszMotifs[MOTIF_BOUNCE], L"Bounce");
.
.
.
hr = gapStyle[0]->GetMotif(awszMotifs[MOTIF_BOUNCE],
        &(gapMotif[0][MOTIF_BOUNCE]));
 

If you look at the complete code, you'll see that the application loads two sets of motifs with the same names, one set from the "Donuts" style and the other from "Donutz." DMDonuts switches between these two styles as the player moves from level to level. When a gapMotif is played, its first index is determined by the value of the global gnCurrentStyle, ensuring that it is the correct motif for that level.

The application now initializes four chordmaps for each style. These are obtained from separate files.

WCHAR awszChordMap[NUM_STYLES][NUM_CHORDMAP][64];
wcscpy(awszChordMap[0][0], L"minaeo.per");
wcscpy(awszChordMap[0][1], L"minfunc.per");
wcscpy(awszChordMap[0][2], L"mipedpt.per");
wcscpy(awszChordMap[0][3], L"tension.per");
wcscpy(awszChordMap[1][0], L"dianoble.per");
wcscpy(awszChordMap[1][1], L"minpedpt.per");
wcscpy(awszChordMap[1][2], L"mippjazz.per");
wcscpy(awszChordMap[1][3], L"minjazz.per");

ObjectDescript.guidClass = CLSID_DirectMusicChordMap;
ObjectDescript.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME;

for (short n = 0; n < NUM_STYLES; n++)
{
    for (short m = 0; m < NUM_CHORDMAP; m++)
    {
        if (hr == S_OK)
        {
            wcscpy(ObjectDescript.wszFileName,
                    awszChordMap[n][m]);
            hr = gpLoader->GetObject(&ObjectDescript,
                    IID_IDirectMusicChordMap,
                   (void**)&gapChordMap[n][m]);
        }
    }
}

if (hr != S_OK)
{
    return CleanupAndExit("Couldn't load a ChordMap");
}
 

The last elements to be retrieved from the styles are the bands. Each style has two different bands: one for when the player's ship isn't shielded and one for when it is. Note that the names of the bands are allocated differently than were the motifs and chordmaps—each is a BSTR rather than a local array of WCHAR—but the effect is the same, because in the Win32® API a BSTR is a pointer to a WCHAR array.

As each band is loaded, it is downloaded to the performance, making available the DLS data for its instruments.

BSTR bstrDefault = SysAllocString(L"Default 2");
BSTR bstrShields = SysAllocString(L"Shields");
 
for (n = 0; n < NUM_STYLES; n++)
{
    if (hr == S_OK)
    {
        hr = gapStyle[n]->GetBand(bstrShields, &gapShieldBand[n]);
    }
    if (hr == S_OK)
    {
        hr = gapShieldBand[n]->Download(gpPerformance);
    }
    if (hr == S_OK)
    {
        hr = gapStyle[n]->GetBand(bstrDefault, &gapDefaultBand[n]);
    }
    if (hr == S_OK)
    {
        hr = gapDefaultBand[n]->Download(gpPerformance);
    }
}
 
SysFreeString(bstrDefault);
SysFreeString(bstrShields);
 

After some error-checking code, the InitializeGame function goes on to create segments from the four bands it has obtained. These segments will be used to "play" the band changes at the appropriate times. Once the segments have been created, the band interfaces are released.

for (n = 0; n < NUM_STYLES; n++)
{
    if (hr == S_OK)
    {
        hr = gapShieldBand[n]->CreateSegment(&gapShieldSegment[n]);
    }
    if (hr == S_OK)
    {
        hr = gapDefaultBand[n]->CreateSegment(&gapDefaultSegment[n]);
    }
    apShieldBand[n]->Release();
    apDefaultBand[n]->Release();
}