Loading from a Resource


Loading from a Resource

You can embed audio objects directly in the executable. To do so, compile them into the executable as resources and then use the Loader's ability to read objects from memory pointers.

As you probably know, all buttons, menus, dialogs, and other user interface elements are stored in the program's executable as resources. You create resources in a resource editor that provides tools for visually laying out the program's user interface. The resource editor is part of the development environment. The final stage of program compilation binds the resource data into the executable. The application makes standard calls to access and use the resource data. The mechanism for storing and retrieving resource elements is not limited to menus, icons, and their ilk. You can store just about any data you want in a resource and have your application access it when needed. This provides the convenience of binding all data directly into the executable so there are no extra files to distribute with the program.

To demonstrate this, let's use our familiar HelloWorld example from the previous chapter and embed the wave as a binary resource. Once it uses resources, our HelloWorld application can no longer be a console app (a console application is a simple command-line program that does not make any calls into the Windows libraries). Calls to retrieve resources require the Windows APIs. The biggest change in that regard is that main() is replaced with WinMain(). Then, to insert the wave, use the resource editor's option to import a binary file as a resource (it actually has a specific option for wave files but not for other DirectMusic media types).

To access the wave resource, use the system calls FindResource() to locate it, LoadResource() to load it, and LockResource() to return a pointer to its memory.

Here is the source code:

#include <windows.h>   // We need the Windows headers for Windows calls and structures.
#include "resource.h"  // Resource IDs.
#include <dmusici.h>

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    // The performance manages all audio playback.
    IDirectMusicPerformance8* pPerformance;
    // The loader manages all file I/O.
    IDirectMusicLoader8* pLoader = NULL;
    // Each audio file is represented by a Segment.
    IDirectMusicSegment8* pSegment = NULL;

    // Initialize COM
    CoInitialize(NULL);

    // Create the loader. This is used to load audio objects from files.
    CoCreateInstance(CLSID_DirectMusicLoader, NULL,
       CLSCTX_INPROC, IID_IDirectMusicLoader8,
       (void**)&pLoader);

    // Create the performance. This manages the playback of sound and music.
    CoCreateInstance(CLSID_DirectMusicPerformance, NULL,
       CLSCTX_INPROC, IID_IDirectMusicPerformance8,
       (void**)&pPerformance);

    // Initialize the performance.
    pPerformance->InitAudio(NULL,NULL,NULL,
        DMUS_APATH_DYNAMIC_STEREO,  // Default AudioPath type.
        2,                          // Only two pchannels needed for this wave.
        DMUS_AUDIOF_ALL,NULL);

    // Okay, let's read the wave from an embedded resource...
    DMUS_OBJECTDESC ObjDesc;

    // Find the wave resource in the executable.
    HRSRC hFound = FindResource(NULL," IDR_WAVE", RT_RCDATA);
    // Force it to load into global memory.
    HGLOBAL hRes = LoadResource(NULL, hFound);

    ObjDesc.dwSize = sizeof(DMUS_OBJECTDESC);
    // Must let the loader know what kind of object this is.
    ObjDesc.guidClass = CLSID_DirectMusicSegment;
    // The only valid fields are the class ID and memory pointer.
    ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY;
    // Get the memory pointer from the resource.
    ObjDesc.pbMemData = (BYTE *) LockResource(hRes);
    // Get the memory length from the resource.
    ObjDesc.llMemLength = SizeofResource(NULL, hFound);
    // Now, read the Segment from the resource.
    if (SUCCEEDED(pLoader->GetObject(
        &ObjDesc, IID_IDirectMusicSegment8,
        (void**) &pSegment)))
    {
        // Install the wave data in the synth.
        pSegment->Download(pPerformance);

        // Play the wave.
        pPerformance->PlaySegmentEx(
            pSegment,  // Segment to play.
            NULL,NULL,0,0,NULL,NULL,NULL);

        // Wait eight seconds to let it play.
        Sleep(8000);

        // Unload wave data from the synth.
        pSegment->Unload(pPerformance);

        // Release the Segment from memory.
        pSegment->Release();
    }

    // Done with the performance. Close it down, and then release it.
    pPerformance->CloseDown();
    pPerformance->Release();

    // Same with the loader.
    pLoader->Release();

    // Bye bye COM.
    CoUninitialize();
    return 0;
}
 

This all works fine and dandy if the resource you are loading is completely self contained, which is the case of our example wave file. However, if the Segment references other files, they too need to be prepared as resources and handed to the Loader using SetObject() prior to the call to GetObject(). Follow exactly the same steps to prepare the referenced resources to initialize the resource and descriptor as before, but call SetObject() instead of GetObject(). SetObject() just tells the Loader where the file object is placed in memory so that the Loader can pull it in later when it needs to.

Loading from a resource is not the only way to load from memory. You can read anything you want into a chunk of memory and then tell the Loader to read it. However, be very careful; after the GetObject() call has completed, the Loader may continue to need access to the memory. This is particularly true with waves and DLS instruments that DirectMusic does not actually read until the application instructs it to download a Segment that uses a wave or DLS instrument from these files. If you call GetObject() with a memory pointer and then release the memory, you could get a nasty crash later when you call download on a Segment that references it. To be safe, wait until all download calls have occurred and you are 100 percent confident that there will be no more. Then, clear the Loader's reference with a call to SetObject() with the same DMUS_OBJECTDESC descriptor but with NULL in pbMemData.

Here is some sample code that plays a wave file that has been placed in memory (*pbData). Notice how it calls SetObject() after downloading the wave data to ensure that the data cannot be pulled from memory later.

IDirectMusicPerformance8 *g_pPerformance;
IDirectMusicLoader *g_pLoader;

IDirectMusicSegment8 *PlayMemWave(BYTE *pbData,DWORD dwLength)

{
    IDirectMusicSegment8 *pSegment = NULL;

    DMUS_OBJECTDESC ObjDesc;

    ObjDesc.dwSize = sizeof(DMUS_OBJECTDESC);
    // Must let the loader know what kind of object this is.
    ObjDesc.guidClass = CLSID_DirectMusicSegment;
    // The only valid fields are the class ID and memory pointer.
    ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY;
    // Assign the memory pointer.
    ObjDesc.pbMemData = pbData;
    // Get the memory length from the resource.
    ObjDesc.llMemLength = dwLength;
    // Now, read the Segment from the resource.
    if (SUCCEEDED(g_pLoader->GetObject(
        &ObjDesc, IID_IDirectMusicSegment8,
        (void**) &pSegment)))
    {
        // At this point, the Segment data has been read, but any wave
        // data is still pending reading from the file via the download
        // command. If we free the memory before this next call, it
        // will cause a crash.
        pSegment->Download(g_pPerformance);
        // Both DLS and wave downloads cause a copy of the memory to
        // be made, so at this point we can free the resource memory.
        // However, this does not apply to streamed waves, which always
        // read from the source every time they play.
        // To be safe, make sure the loader can't find the memory any more.
        ObjDesc.pbMemData = NULL;
        g_pLoader->SetObject(
        &ObjDesc, IID_IDirectMusicSegment8,
        (void**) &pSegment)))
        // Play the wave.
        g_pPerformance->PlaySegmentEx(
            pSegment, // Segment to play.
            NULL,NULL,0,0,NULL,NULL,NULL);
    }
    // Return the Segment. Be sure to unload and release it later.
    return pSegment;
}
 

With a better understanding of how the Loader manages files, you are better equipped to avoid some of the pitfalls and downright enigmatic behavior that might test your wits. Although we covered a great deal in this chapter, we still have not touched on the reverse story — how you can replace the Loader with one of your own. For some applications (in particular games), it can be useful to do this because you can then completely control the delivery of game audio resources and even compress or encrypt them in the process. Although we do not cover it in this book, there is a great article and programming example on MSDN. Go to http://msdn.microsoft.com and search for "Custom Loading in DirectMusic."