The MaxMidi DLL provides a menagerie of functions to open, read, and write Standard MIDI Files.
It's time to take a look at how these functions are used to read events from an SMF.
The first step is to open the file using the OpenSMF() function. OpenSMF() will open the file, verify that it is a valid Standard MIDI File, and read the header chunk. The function returns three values. The function's return value is an opaque handle to the opened file. If there was an error opening the file, or the file could not be found, the
Page 195
handle will be NULL. The other two values are returned in parameters passed by the function call.
The file format is returned in the integer variable pointed to by the Format parameter. Similarly, the number of tracks in the file is returned in the variable whose address is passed in the nTracks parameter. The file test.mid is opened for reading like this:
int NumOfTracks;
HSMF hSmf;
int format;
hSmf = OpenSMF("test.mid", &format, `r', &NumOfTracks);
If the open call is successful, the handle will be nonzero and the format and the NumOfTracks variables will be set to the values found in the file's header chunk. Once the file is open, MIDI events can be read from the track. It is not necessary to read all of the events from one track before
accessing a different one; events can be read from any track at any time.
MIDI events are read from the file a block at a time. A block can be as small as a single event, or as large as the entire track. The following example reads 512 events and sends them to an already opened MIDI output.
DWORD NumOfEvents;
int wTrack;
LPMIDIEVENT lpMidiBuff;
DWORD dwBuffLen;
DWORD eventCount;
// read the first track wTrack = 0;
// use a 512 event buffer wBuffLen = 512L;
// allocate memory for the buffer
lpMidiBuff = (LPMIDIEVENT)GlobalAlloc(GPTR, dwBuffLen * sizeof(MidiEvent));
// read the SMF and send to Midi out
NumOfEvents = ReadSMF(hSmf, wTrack, lpMidiBuff, dwBuffLen);
while((eventCount < NumOfEvents) && (PutMidiOut (hMidiOut,
(LPMIDIEVENT)(lpMidiBuff + eventCount)) == 0)) eventCount++;
Page 196
The ReadSMF() function reads the specified number of events from the file, storing them in the application's buffer. The dwBuffLen parameter indicates the size of the buffer in MidiEvents.
Any track can be specified by changing the wTrack value, where track 0 corresponds to the first track in the file.
An application based on this example can read events from any track in any order and send those events to different MIDI outputs or combine the events into a single track for output to a single output device. It can then continue to read events, sending them to the proper device until there are no more events to read. Notice that this simple example reads events from the first track of the file, regardless of the SMF format. There may not be any playable MIDI events in the first track of a Format 1 file. In this case it may be more interesting to read events from another track in the file.
Meta events can be read from any track at any time. They are skipped when reading MIDI events, since MidiEvent structures can hold only MIDI messages. Instead, Meta events are retrieved using another function: ReadMetaEvent(). Here's the prototype for the function:
DWORD ReadMetaEvent(HSMF hSMF, int wTrack, BYTE MetaEvent, LPSTR
*EventValue, DWORD *EventSize);
Meta events are not playable MIDI events, so the MaxMidi DLL accesses them as a separate stream.
Internally, the SMF reader keeps a table of the file pointers that locate each possible type of Meta event. When the next instance of a particular event is requested via a call to ReadMetaEvent(), the reader positions the file pointer to the proper location and returns the event. Future calls
requesting the same type of event cause the reader to search for the next instance in the file, starting from the location of the last event. This allows any type of Meta event to be read at any time, as needed by the application. For example, if a program wishes to retrieve all of the lyric events in a track, it can do so without having to read and discard any other events.
Figure 13-5 Reading events
Page 197
The diagram above shows the two event streams used by the SMF reader. Calls to ReadSMF() will retrieve all of the playable MIDI events, either one at a time or as a block of events. Notice that Set Tempo Meta events are automatically returned as playable events, since the MaxMidi playback engine includes a mechanism for handling timestamped tempo changes (i.e., MidiEvent.status set to 0, other MidiEvent bytes set to tempo in microseconds per beat). As the ReadSMF() function progresses through the track, it skips over all other Meta events.
The ReadMetaEvent() function, on the other hand, retrieves particular Meta events from the track. The diagram shows the Track Name event and all of the Lyric events being read. Although Set Tempo events are normally retrieved using the ReadSMF() function, they can also be accessed using ReadMetaEvent().
When ReadMetaEvent() locates the requested Meta event in the specified track, it returns a pointer to a buffer containing the event's data as an array of bytes. This pointer is passed back to the caller through the EventValue parameter. The returned pointer will be NULL if the event is not found. The EventSize variable contains the number of bytes in the data block. The size is 0 if the event exists but has no data. The calling application must copy the data from the temporary buffer returned by the function into its own memory if it wishes to retain the data. The ReadMetaEvent () function reuses (and reallocates) the buffer each time it is called. Disappointment awaits
programs that rely on the existence of the returned buffer across calls. Here is an example code snippet that retrieves the track name (really the sequence name) of the first track.
LPSTR lpTName;
LPSTR lpTrackName;
DWORD NSize;
int wTrack;
// look for and read the track name for the first track wTrack = 0;
if(ReadMetaEvent(hSmf, wTrack, META_NAME, &lpTName, &NSize) != -1) {
// allocate a buffer for the track name
lpTrackName = (LPSTR)GlobalAlloc(GPTR, NSize));
// copy the name into the new buffer lstrcpy(lpTrackName, lpTName);
}
Of course, all opened files must be closed when no longer needed. Do this by calling CloseSMF(), specifying the SMF handle assigned by the OpenSMF() call. Once the file is
Page 198
closed, the handle is invalid. It's a good idea to set the closed handle to 0 to prevent inadvertent use;
null handles are always ignored by the MaxMidi DLLs.
CloseSMF(hSMF);
hSMF = 0; // safety first!