Loading a MIDI Segment
The next step in setting up DirectMusic is creating a IDirectMusicLoader object so that you can load your MIDI files. This is accomplished with a low-level COM call again, but it's not bad.
Creating the Loader
The following code creates the loader:
// the directmusic loader
IDirectMusicLoader*dm_loader = NULL;
// create the loader to load object(s) such as midi file
if (FAILED(CoCreateInstance(
CLSID_DirectMusicLoader,
NULL,
CLSCTX_INPROC,
IID_IDirectMusicLoader,
(void**)&dm_loader)))
{
// error
return(0);
} // end if
Interestingly enough, a number of interfaces have been created internally—including an IDirectMusic object and an IDirectMusicPort object—and you didn't even know. In most cases, you would never need to make calls to the functions of these interfaces, so it's cool, baby.
Loading the MIDI File
To load the MIDI file, you have to tell the loader where to look and what to look for (type of file), and then tell it to create a segment and load the file into it. I have created a function to do this along with some data structure, so I might as well show it to you now. First, the data structure that's going to hold each musical MIDI segment (DirectMusic likes to call data chunks segments) is called DMUSIC_MIDI and is shown here:
typedef struct DMUSIC_MIDI_TYP
{
IDirectMusicSegment *dm_segment; // the directmusic segment
IDirectMusicSegmentState *dm_segstate; // the state of the segment
int id; // the id of this segment
int state; // state of midi song
} DMUSIC_MIDI, *DMUSIC_MIDI_PTR;
This is used to hold each MIDI segment. But you may have more than a few songs for a whole game, so let's make an array of them:
DMUSIC_MIDI dm_midi[DM_NUM_SEGMENTS];
Here, DM_NUM_SEGMENTS is defined as
#define DM_NUM_SEGMENTS 64 // number of midi segments that can be cached in memory
Okay, with that all in mind, take a look at the DMusic_Load_MIDI() function that follows. It's heavily commented, so take your time, and also pay attention to the funky wide character strings that the DirectMusic functions use:
int DMusic_Load_MIDI(char *filename)
{
// this function loads a midi segment
DMUS_OBJECTDESC ObjDesc;
HRESULT hr;
IDirectMusicSegment* pSegment = NULL;
int index; // loop var
// look for open slot for midi segment
int id = -1;
for (index = 0; index < DM_NUM_SEGMENTS; index++)
{
// is this one open
if (dm_midi[index].state == MIDI_NULL)
{
// validate id, but don't validate object until loaded
id = index;
break;
} // end if
} // end for index
// found good id?
if (id==-1)
return(-1);
// get current working directory
char szDir[_MAX_PATH];
WCHAR wszDir[_MAX_PATH];
if(_getcwd( szDir, _MAX_PATH ) == NULL)
{
return(-1);;
} // end if
MULTI_TO_WIDE(wszDir, szDir);
// tell the loader were to look for files
hr = dm_loader->SetSearchDirectory(GUID_DirectMusicAllTypes,
wszDir, FALSE);
if (FAILED(hr))
{
return (-1);
} // end if
// convert filename to wide string
WCHAR wfilename[_MAX_PATH];
MULTI_TO_WIDE(wfilename, filename);
// setup object description
DD_INIT_STRUCT(ObjDesc);
ObjDesc.guidClass = CLSID_DirectMusicSegment;
wcscpy(ObjDesc.wszFileName, wfilename );
ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_FILENAME;
// load the object and query it for the IDirectMusicSegment interface
// This is done in a single call to IDirectMusicLoader::GetObject
// note that loading the object also initializes the tracks and does
// everything else necessary to get the MIDI data ready for playback.
hr = dm_loader->GetObject(&ObjDesc,IID_IDirectMusicSegment,
(void**) &pSegment);
if (FAILED(hr))
return(-1);
// ensure that the segment plays as a standard MIDI file
// you now need to set a parameter on the band track
// Use the IDirectMusicSegment::SetParam method and let
// DirectMusic find the trackby passing -1
// (or 0xFFFFFFFF) in the dwGroupBits method parameter.
hr = pSegment->SetParam(GUID_StandardMIDIFile,-1, 0, 0, (void*)dm_perf);
if (FAILED(hr))
return(-1);
// This step is necessary because DirectMusic handles program changes and
// bank selects differently for standard MIDI files than it does for MIDI
// content authored specifically for DirectMusic.
// The GUID_StandardMIDIFile parameter must
// be set before the instruments are downloaded.
// The next step is to download the instruments.
// This is necessary even for playing a simple MIDI file
// because the default software synthesizer needs the DLS data
// for the General MIDI instrument set
// If you skip this step, the MIDI file will play silently.
// Again, you call SetParam on the segment,
// this time specifying the GUID_Download parameter:
hr = pSegment->SetParam(GUID_Download, -1, 0, 0, (void*)dm_perf);
if (FAILED(hr))
return(-1);
// at this point we have MIDI loaded and a valid object
dm_midi1.dm_segment = pSegment;
dm_midi1.dm_segstate = NULL;
dm_midi1.state = MIDI_LOADED;
// return id
return(id);
} // end DMusic_Load_MIDI
The function isn't too bad. It basically looks for an open slot in your dm_midi[] array to load the new MIDI segment, sets the search path, creates the segment, loads the segment, and bails. The function takes the filename of the MIDI file and then returns an ID to the array index containing the segment in your data structure.
|