Importing a MIDI Score


After a MIDI environment is prepared with a score context and its attendant data structures, a task can import a MIDI score file for playback there. It helps if that MIDI score is tweaked to best fit into the 3DO MIDI environment. It should limit unnecessary programs and voices. It should plan, in its PIMap, to use less complex DSP instruments. The MIDI score should be stored in a Format 0 or Format 1 file, which is often available through a sequencer's or composition tool's Export command.

Declaring a MIDIFileParser Data Structure

Before a task can import a MIDI file, it must first declare a MIDIFileParser data structure. This structure keeps track of the overall score parameters as the MIDI data is translated into a Juggler object. These parameters include values such as the clocks-per-second tempo set in the MIDI score. The MIDIFileParser data structure is defined in the midifile.h header file, and should never be written to directly by a task, the MIDI loading calls use it for their own internal purposes.

Creating a Juggler Object

The next step in importing a MIDI score is to create a Juggler object appropriate for the imported score. If the score is a Format 0 file, you must create a single Juggler sequence to contain the translated score. If the score is a Format 1 file, you must create a Juggler collection that holds one Juggler sequence for each sequence in the score. (The translating call itself creates the Juggler sequences within the collection.)

Loading the Score

With a MIDIFileParser data structure and a Juggler sequence in place, you can load a Format 0 MIDI score file using this call:

int32 MFLoadSequence( MIDIFileParser *mfpptr, char *filename, Sequence *SeqPtr )
The call takes three arguments: *mfpptr, a pointer to the MIDIFileParser data structure used for translating the MIDI score; *filename, a pointer to a character string containing the name of the MIDI score file; and *SeqPtr, a pointer to the Juggler sequence where the translated MIDI score should be stored.

When MFLoadSequence() executes, it opens the MIDI score file, translates its contents using the MIDIFileParser data structure, and stores the translated MIDI messages in the specified sequence as MIDI events. It writes information about the MIDI score in the MIDIFileParser data structure. If successful, MFLoadSequence() returns 0; if unsuccessful, it returns a negative value (an error code).

To load a Format 1 MIDI score file, you must have a Juggler collection in place (instead of a Juggler sequence used for a Format 0 score). The collection should be empty. Use this call to load the file:

int32 MFLoadCollection( MIDIFileParser *mfpptr, char *filename, Collection *ColPtr )
The call takes three arguments: *mfpptr, a pointer to the MIDIFileParser data structure used for translating the MIDI score; *filename, a pointer to a character string containing the name of the MIDI score file; and *ColPtr, a pointer to the Juggler collection where the translated MIDI score should be stored.

When MFLoadCollection() executes, it opens the MIDI score file and translates its contents using the MIDIFileParser data structure. It creates a Juggler sequence for each track in the MIDI score, and stores the translated contents of each track in a separate Juggler sequence. MFLoadCollection() writes information about the MIDI score in the MIDIFileParser data structure.

Note that if MFLoadCollection() processes a Format 0 MIDI file, it treats it as a single-track Format 1 MIDI file and turns it into a single-sequence collection. This collection requires more processing overhead than the same score loaded by MFLoadSequence() as a Juggler sequence. If your MIDI score has only one channel and you can save it as a Format 0 file, then you should always load it as a sequence to save processing overhead.

If successful, MFLoadCollection() returns 0; if unsuccessful, it returns a negative value (an error code).

If you use data streaming to import the image of a MIDI score file, then use this call to turn the file image into a MIDI collection that can be played by the Juggler:

int32 MFDefineCollection( MIDIFileParser *mfpptr, char *Image, int32 NumBytes, Collection *ColPtr)
The call takes four arguments: *mfpptr, a pointer to the MIDIFileParser data structure used for translating the MIDI score; *Image, a pointer to the file image (which should be a string of bytes); NumBytes, the size of the MIDI file image in bytes; and *ColPtr, a pointer to the Juggler collection where the translated MIDI score should be stored.

When MFDefineCollection() executes, it turns the MIDI file image into a collection just as MFLoadCollection() turns a MIDI file into a collection. MFDefineCollection() returns 0 if successful, or a negative value (an error code) if unsuccessful.

Specifying the User Context

Juggler objects can contain a pointer to a user context. This feature is designed specifically for MIDI playback. Once a MIDI score is translated and placed in a Juggler sequence or collection, that sequence or collection must be set with a pointer to the score context used to play back the MIDI score. To do so, you use tag arguments and the SetObjectInfo() call as described in Creating and Playing Juggler Objects."

The code fragment in Example 3 shows how a collection containing an imported MIDI score is set to point to the score context. CollectionPtr is a pointer to the collection; SconPtr is a pointer to the score context.

Example 1: Pointing to a score context.

/* Define TagList */
Tags[0].ta_Tag = JGLR_TAG_CONTEXT;
Tags[0].ta_Arg = (void *) SconPtr;
Tags[1].ta_Tag = TAG_END;
SetObjectInfo(CollectionPtr, Tags);

After the pointer to the score context is set, the object's interpreter procedure can pass the pointer along to other MIDI calls as they execute the MIDI events in the object.

The Interpreter Procedure

When MFLoadSequence() and MFLoadCollection() turn a MIDI score into a Juggler object, all translated sequences contain a pointer to an interpreter procedure. This call is the MIDI interpreter procedure:

int32 InterpretMIDIEvent( Sequence *SeqPtr, MIDIEvent *MEvCur, ScoreContext *scon )
The call accepts three arguments: *SeqPtr, a pointer to the sequence for which it interprets; *MEvCur, a pointer to the current event in the sequence; and *scon, a pointer to the score context used to play the event.

When executed, InterpretMIDIEvent() extracts a MIDI message from the current event. The extracted message is string of one-byte values that can be from one to four bytes long. InterpretMIDIEvent() then calls InterpretMIDIMessage() and passes the MIDI message and score context pointer along to it.

InterpretMIDIMessage() looks at the MIDI message to see if it is one of the messages recognized by the Music library. If it is, InterpretMIDIMessage() calls whichever of these MIDI playback functions is appropriate for carrying out the message: StartScoreNote() for Note On messages, ReleaseScoreNote() for Note Off messages, ChangeScoreControl() for volume or panning messages, ChangeScoreProgram() for program change messages, or ChangeScorePitchBend() for pitch bend change messages. Although these functions are called automatically during score playback, they can be used directly by a task, and are described more fully later in this chapter.