Playing the MIDI Score


With the MIDI environment prepared, the MIDI score imported, and the audio clock rate set, you can then use the Juggler to play back the MIDI score. The playback technique is no different than it is for any Juggler object, a process described in the chapter Creating and Playing Juggler Objects." In fact, if you have already created a standard function that plays a Juggler object using the audio clock, you can probably use it to play a MIDI object with little adaptation.

The process is as follows:

The code fragment in Example 5 plays back a MIDI collection:

Example 1: Playing a MIDI collection.

MyCue = CreateItem ( MKNODEID(AUDIONODE,AUDIO_CUE_NODE), NULL );
CueSignal = GetCueSignal ( MyCue );
/* Drive Juggler using audio clock. */
/* Delay start by adding ca. 1/2 second to avoid stutter on startup. 
*/
NextTime = GetAudioTime() + 120;
/* Start playback loop. */
StartObject ( JglPtr , NextTime, NumReps, NULL );
do
{
    /* Request a timer wake up at the next event time. */
    Result = SleepUntilTime( MyCue, NextTime );
    CurTime = NextTime;
    /* Tell Juggler to do its thing. Result > 0 if done. */
    Result = BumpJuggler( CurTime, &NextTime, SignalsGot, 
&NextSignals );
        } while (Result == 0);
DeleteItem( MyCue );

Notice that the fragment includes a delay of approximately 1/2 second, which is put in place to avoid a stutter of voices at the beginning of the score playback. Without the delay, the object is started at the same time that the Juggler is bumped, which can cause voice stuttering.

Dynamic Voice Allocation

As the Juggler plays back a MIDI object, the interpreter procedure turns the MIDI events into MIDI messages. The MIDI messages are passed on to InterpretMIDIMessage(), which is the beginning of a tree of MIDI playback functions designed to act appropriately on each recognizable MIDI message. As these functions play notes, they use a system of dynamic voice allocation, controlled by the calls and the MIDI environment's data structures (such as NoteTracker). This system provides multiple voices in different channels playing different programs. Although the task playing the MIDI score cannot control this process directly, it is useful to know something about how it works so that MIDI scores can be tweaked to work to their best advantage.

When the first note of a score is played by a voice, a MIDI playback function creates an instrument to play that note and connects the instrument's output to the MIDI mixer. The voice's channel determines which instrument template should be used to create the instrument. When the note is released, its instrument is abandoned, that is, it remains in existence, connected to the mixer, but it no longer plays.

When any subsequent note in a score is played, a MIDI playback function looks for an abandoned instrument of the right template for the note. If it finds one, it uses that instrument to play the note. If it does not find one, it looks for an abandoned instrument of another template, frees that instrument, and creates a new instrument using the right template for the note. And if it does not find any abandoned instruments, it creates a new instrument for the note. When the note is finished, its instrument is abandoned so that other notes can use it.

New instrument allocation continues until the number of existing instruments hits the upper limit set in the score context or until the DSP runs out of resources. When that happens, and there are no abandoned instruments to use, a MIDI playback function starting a new note must steal an existing instrument from another note. To do so, it looks through all the channels to find the least prominent voice to steal. It looks for notes of lower priority, released notes that are dying away, or old notes that the listener cannot hear anymore. It then steals its voice, cutting off the note. The function does not steal a voice from any note of a higher priority than the starting note.

To sum up, a MIDI playback function playing a note uses this priority list to look for an instrument:

  1. Adopt an abandoned instrument.

  2. Create a new instrument.

  3. Steal an instrument of lower priority, an instrument used by a released note, or an instrument used by an old note.

Freeing Instruments Created by Voice Allocation

Note that the process of dynamic voice allocation creates instruments as necessary, but never reduces the number of instruments. It recycles existing instruments when possible. This means that if dynamic voice allocation must create a large number of instruments for a part of a score with a large number of multiple voices, the created instruments remain in existence no matter how brief the multiple voice section of the score was. These unused instruments can tie up DSP resources.

To free instruments allocated by dynamic voice allocation, use this call:

Err FreeChannelInstruments( ScoreContext *scon, int32 Channel )
The call accepts two arguments: *scon, a pointer to the score context controlling MIDI playback; and Channel, the number of a channel whose unused instruments should be freed.

FreeChannelInstruments(), when executed, frees all instruments allocated for the specified channel. If this occurs in the middle of score playback and some of those instruments are in use, they are stopped in the middle of a note and then freed. If successful, it returns 0; if unsuccessful, it returns a negative value (an error code).

This call provides a way for a task to clean up allocated instruments and free DSP resources without deleting a score context and ceasing MIDI playback activities.