To play MIDI messages in this internal MIDI environment, the Music library must be able to translate a score file's messages into events that the Juggler can read. The Juggler can then use appropriate software MIDI playback mechanisms for each event. Once the score is translated, the Music library must be able to control score timing, that is, the rate at which it feeds the score's MIDI messages to MIDI playback mechanisms. This is the same type of score timing used as a standard MIDI sequencer sets the tempo according to the number of MIDI clocks it receives.
The following sections describe the MIDI environment set up by the Music library, an environment of Juggler objects and data structures where the MIDI playback functions can go to work.
The virtual MIDI device created by the Music library also changes its state during playback. To keep track of the device's current state, the Music library provides a set of data structures designed to store current setting values. The values within these data structures are maintained internally by the Music library, so a task need not (and should not) write to them directly. Nevertheless, it is useful to know what these data structures are and how they are used because a task playing back a MIDI score needs to provide these data structures for score playback.
When a hardware synthesizer plays notes, it uses one voice for the first note, and then, if a second note begins before the first finishes, it plays the second note on a second voice so the notes sound simultaneously. Whenever a new note starts, it uses an unused voice, unless there are no unused voices available, in which case it steals a used voice to start the new note.
To provide the equivalent of a voice in a dedicated synthesizer, the Music library uses the NoteTracker
data structure, one for each voice. Each NoteTracker
data structure is tied to an audio instrument and keeps track of whether the instrument is playing a note or not. If it is playing a note, and score playback requires another note to be started, the Music library's voice allocation looks for a free NoteTracker
. It then plays the note using that NoteTracker
voice and marks the NoteTracker
data structure to show that a note is playing.
Note that the NoteTracker
data structure is only used internally by Music library functions. A task should not write values directly to a NoteTracker
data structure, just as a task should not touch other internally used data structures.
ScoreChannel
data structure, which holds values that, among other things, give the default program for the channel, the current program setting, the priority of the channel's voices, the number of voices assigned to the channel, the amount of pitch bend set for the channel, and the overall volume of the channel. The ScoreChannel
data structure, like the NoteTracker
data structure, is for Music library internal use only and a task should not write to it directly.
ScoreContext
data structure. This data structure contains, among other things, a pointer to the PIMap
structure used for score playback, the maximum number of voices available (to limit strain on system resources), the maximum volume allowed per voice, the name of a mixer instrument used to accept the output of NoteTracker
voices, the right and left gain values for the mixer, a pointer to an array of NoteTracker
data structures used for playback, a list of free NoteTrackers
available to play a note, and an array of ScoreChannel
data structures, one for each possible channel. The ScoreContext
data structure sets the score context, the overall control for this virtual MIDI device.
The ScoreContext
data structure is another Music library internal data structure that should not be written to directly by a task.
MFLoadCollection()
, which handles MIDI format
1 scores, and MFLoadSequence()
, which handles MIDI format 0
scores. Both of these functions are described in
Importing a MIDI Score.
When MFLoadSequence()
imports a Format 0 score, it turns the score into a single Juggler sequence. When MFLoadCollection()
imports a Format 1 score, it turns the score into a Juggler collection that contains a Juggler sequence for each of the MIDI score's component tracks. Each track within a MIDI score, whether Format 1 or Format 0, is always translated into a single Juggler sequence, whether the sequence stands alone (for a Format 0 score) or is part of an encompassing collection (for a Format 1 score).
Within a Juggler sequence created from a MIDI score, each MIDI message is turned into a MIDIEvent
data structure, a form that the Juggler can handle directly during playback. The MIDIEvent
data structure is defined as follows:
The first element of the structure is the MIDI message's time clock value, which is translated here into a 32-bit unsigned value that gives an equivalent number of audio ticks. For example, a MIDI message with a timing clock value of 328 is assigned an audio tick value of 328. Audio tick values are ticked off by the audio clock to time events during playback by the Juggler.
typedef struct MIDIEvent
{
uint32 mev_Time;
unsigned char mev_Command;
unsigned char mev_Data1;
unsigned char mev_Data2;
unsigned char mev_Data3;
} MIDIEvent;
The second element of the structure is the single-byte MIDI message type. The third, fourth, and fifth elements are single-byte data values that may (or may not) accompany a MIDI message. If a MIDI message type does not require accompanying data or does not require all three bytes of data, the contents of the unused data elements are not relevant and are not read.
InterpretMIDIEvent()
as its interpreter procedure. When the Juggler plays the MIDI score object, its events are passed to InterpretMIDIEvent()
, which strips out the MIDI message embedded in the event and passes it on to InterpretMIDIMessage()
.
InterpretMIDIMessage()
reads the MIDI message and calls an appropriate Music library function to act on that message type. InterpretMIDIMessage()
passes along the data that accompanies the MIDI message. The function that acts on the MIDI message, makes the appropriate Audio folio calls, and updates the appropriate Music library data structures.
BumpJuggler()
loop (a technique described in Creating and Playing Juggler Objects"), the audio clock controls the playback tempo. A MIDI score is usually saved with a particular tempo measured in MIDI clocks per second. The audio clock should be set to the same tempo, measuring audio ticks per second instead of MIDI clocks per second. To do so, the task playing back the score takes ownership of the clock and changes its rate as described in Playing Instruments." Once the clock rate matches the prescribed tempo, the MIDI environment is set for playback.