Representation of MIDI in the MusicKit

In the MusicKit, MIDI messages are represented as MKNotes, where each MKNote can represent one or more MIDI messages. The MIDI message is represented by a combination of the MKNote's type, tag and parameters.

The MusicKit takes care of verifying the validity of the MIDI stream and throws away invalid messages, such as duplicate noteOffs or noteOffs for inactive Key/Channel pairs. However, you can turn off this feature by enabling "inclusive translation mode" with the function MKSetInclusiveMidiTranslation().

This discussion assumes a familiarity with MIDI. For further details, consult the MIDI Specification. For further information no methods for reading a Standard MIDI File, see the MKScore class description. For further information on processing or sending MIDI in real time, see the MKMidi class description.

Converting MusicKit MKNotes to MIDI

These rules deal with sending MIDI messages to a MKMidi object, which represents the OUT jack on your MIDI interface, as well as writing MKScore objects as Standard MIDI files.

MIDI Channel Voice Messages

MIDI Channel Voice messages are the most common type of message. For all the Channel Voice messages, the MIDI channel is determined as follows:

For writing a MKScore object to a Standard MIDI File, each MKNote is checked for the presence of an MK_midiChan parameter. If one is found, its value ([1-16]) is used. Otherwise, if the MKPart info MKNote has an MK_midiChan parameter, it is used as the default. Otherwise a default of channel 1 is used.

For writing a MKNote to a MKMidi object, if the MKNote is sent via the 0'th MKNoteReceiver of the MKMidi object, each MKNote is checked for the presence of an MK_midiChan parameter. If one is found, its value ([1-16]) is used. Otherwise, the channel of the MKNoteReceiver is used. For example, all MKNotes sent via the 2nd MKNoteReceiver are assigned to MIDI channel 2.

  • A noteOn message is represented by a MKNote object with the noteType MK_noteOn. Additionally, an MK_keyNum parameter provides the key number ([0-127]) and the MK_velocity parameter provides the velocity ([0-127]). If no MK_keyNum is present, the MK_freq parameter is checked and, if it is present, converted to a MIDI key number using the currently installed MKTuningSystem. Finally, a noteTag must be provided so that noteOff MKNotes and polyphonic aftertouch MKNotes can be properly matched.

    Alternatively, you can send a MKNote of type MK_noteDur with a duration supplied, which is equivalent to a noteOn/noteOff pair. In this case, no noteTag is needed, though it may still be supplied if desired to match against polyphonic key pressure MKNotes and other noteOn MKNotes in this phrase.

    The following actions are taken by the MusicKit to make sure MusicKit semantics are properly converted to MIDI semantics:

    1. If two successive noteOn MKNotes have the same noteTag and the same MK_keyNum value, a noteOff message is generated on the same channel and with the same key number as those for the noteOn MKNotes.

    2. If two successive noteOn MKNotes have the same noteTag but different MK_keyNum values, the second MIDI noteOn message is followed by a MIDI noteOff message with the key number of the first noteOn MKNote. This is to accommodate MIDI Mono Mode.

    For example, the following code creates a noteOn note and sends it to MIDI OUT. This is a very simple case with no MKConductor to manage timing.

    #import <MusicKit/MusicKit.h>
    #import <MusicKit/keynums.h>
    
    MKMidi *aMidi = [[MKMidi alloc] init];     /* Create MKMidi object */
    MKNote *aNote = [[MKNote alloc] init];     /* Create a MKNote */
    
    [aMidi run];                               /* Start it up */
    [aNote setNoteTag: MKNoteTag()];           /* Set tag */
    [aNote setPar: MK_keyNum toInt: c4k];      /* Set key number */
    [aNote setPar: MK_velocity toInt: 64];     /* Set velocity */
    
    [aNote setPar: MK_midiChan toInt: 1];      /* MIDI channel */
    [aNote setNoteType: MK_noteOn];            /* Set type */
    [[aMidi noteReceiver] receiveNote: aNote]; /* Send it */
    
    [aMidi close];

  • A noteOff message is represented by a MKNote object with the note type MK_noteOff. You should give the MKNote a noteTag that is the same as you gave the corresponding noteOn MKNote. When you send such a MKNote to the MKMidi object or write it to a Standard MIDI file, the key number is determined by the MKNote's noteTag, which is matched against the MKNote previously sent with the same tag. If there is no active noteTag then and inclusive translation mode has been selected (using the function MKSettInclusiveMidiTranslation()), the MKNote is checked for the presence of an MK_keyNum parameter ([0-127]). Otherwise, or if none is found, the noteOff is omitted. Additionally, an MK_releaseVelocity parameter provides the noteOff velocity. MKNote that if the presence or absence of MK_releaseVelocitydetermines whether the MIDI is sent as a true MIDI noteOff message or as a zero-velocity MIDI noteOn message.

    If you are generating a series of notes in a single phrase (i.e. using MIDI Mono Mode), keep in mind that MusicKit semantics require only a single noteOff MKNote to conclude the phrase, rather than a noteOff MKNote for every noteOn MKNote. For example, if you send a series of MKNotes with the same noteTag that are part of a mono phrase, you should simply send a series of noteOns and one noteOff at the end. The MusicKit does the proper translation to MIDI semantics automatically.

  • A polyphonic aftertouch (aka “key pressure”) message is represented by a MKNote object of type MK_noteUpdate. The key number is determined by the MKNote's noteTag, which is matched against the MKNote previously sent with the same tag. If there is no active noteTag then, if inclusive translation mode has been selected (using the function MKSettInclusiveMidiTranslation()), the MKNote is checked for the presence of an MK_keyNum parameter. If none is found, the polyphonic aftertouch message is omitted. The parameter MK_keyPressure provides the value of the key pressure, which is between 0 and 127.

  • A controller change message is represented by a MKNote object with the parameter MK_controlChange, which provides the value of the controller to be changed ([0-121]), while the parameter MK_controlVal is the value of the controller ([0-127]). If you send such a MKNote to a MKSynthInstrument, its noteType should be set to MK_noteUpdate.

  • A program change message is represented a MKNote object with the parameter MK_programChange, which provides the value of the program change ([0-127]).

  • A channel pressure (aka “after touch”) message is represented by a MKNote object with the parameter MK_afterTouch, which provides the value of the afterTouch. If you send such a MKNote to a MKSynthInstrument, its noteType should be set to MK_noteUpdate. Macros for common controller names are found in MusicKit/midi_spec.h. For example, MIDI_MODWHEEL is defined as 1.

  • A pitch bend message is represented by a MKNote object with the parameter MK_pitchBend, which provides the value of the pitch bend. As in MIDI, pitch bend is a 14-bit signed integer, centered around 0x2000. That is, 0 is maximum negative bend and 0x3fff is maximum positive bend. If you send such a MKNote to a MKSynthInstrument, its noteType should be set to MK_noteUpdate.

For example, the following code plays a note and then does a gradual pitch bend.

#import <MusicKit/MusicKit.h>
#import <MusicKit/keynums.h>

int i;
MKMidi *aMidi = [[MKMidi alloc] init];    /* Create MKMidi object */
MKNote *aNote = [[MKNote alloc] init];    /* Create a Note */
MKNote *pbNote = [[MKNote alloc] init];   /* Another one */

#define SEND(note) [[aMidi noteReceiver] receiveNote: note]

[aMidi run];                              /* Start it up */

[aNote setNoteTag: MKNoteTag()];          /* Set tag */
[aNote setPar: MK_keyNum toInt: c4k];     /* Set key number */
[aNote setPar: MK_velocity toInt: 64];    /* Set velocity */
[aNote setPar: MK_midiChan toInt: 1];     /* MIDI channel */
[aNote setNoteType: MK_noteOn];           /* Set type */

SEND(aNote);

[pbNote setNoteType:MK_noteUpdate];
[pbNote setPar:MK_midiChan toInt:1];     /* MIDI channel */

for (i = 0; i < 0x3fff; i++) {
    [pbNote setPar: MK_pitchBend toInt: i]; /* Bend */
    SEND(pbNote);
    usleep(10000);                       /* Wait 10 milliseconds */
}

[aNote setNoteType: MK_noteOff];          /* Now stop the note */
SEND(aNote);
[aMidi close];

Note that several messages may be specified in a single MKNote object, where feasible. For example, a noteOn can be combined with an after touch message. An example where this is not possible is to set two controllers in a single MKNote―this is impossible because only one MK_controlChange parameter may be present in a MKNote.

MIDI Channel Mode Messages

MIDI Channel Mode messages are represented by a MKNote object with the parameter MK_basicChan set to the basic channel ([1-16]) and MK_chanMode set to one of the following int values (defined in MusicKit/params.h):

  • MK_resetControllers

  • MK_localControlModeOn

  • MK_localControlModeOff

  • MK_allNotesOff

  • MK_omniModeOff

  • MK_omniModeOn

  • MK_monoMode

  • MK_polyMode

In addition, for monoMode, the parameter MK_monoChans specifies the number of Mono channels ([0-127).

MIDI System Messages

  • A MIDI time code quarter framemessage is represented by a MKNote with the parameter MK_timeCodeQ, with a value as defined in the MIDI Time Code Specification ([0-127)].

  • A song position pointermessage is represented by a MKNote with the parameter MK_songPosition, which is a 14-bit integer ([0-0x3ff]). See the MIDI Specification for details.

  • A song selectmessage is represented by a MKNote with the parameter MK_songSelect with the integer value of the song to be selected ([0-127]).

  • A tune requestmessage is represented by a MKNote with the parameter MK_tuneRequest. The value of the parameter is irrelevant―only its presence is significant.

  • MIDI System Real Time messages are represented by a MKNote object with the parameter MK_sysRealTime set to one of the following int values (defined in MusicKit/params.h):

  • MK_sysClock

  • MK_sysStart

  • MK_sysContinue

  • MK_sysStop

  • MK_sysActiveSensing

  • MK_sysReset

  • MIDI System Exclusive messages are represented by a MKNote object with the parameter MK_sysExclusive with a string value. The string consists of hexadecimal system exclusive bytes separated by any non-digit delimiter. The musickit uses the comma as a delimiter (','). For example, "f0, 8,13,f7". The string may, but need not, begin with f0 and end with f7. If these are missing, they are automatically added. Note that you should not include the conventional C hex prefix "0x". This will be interpreted as a single byte 0!

    Note also that if you want to give each sysex byte a different delay, you need to put it in a separate MKNote object as a separate system exclusive message. The MusicKit does not support system exclusive messages that span MKNote objects.

    For example, the following program sends the system exclusive message f0, 8 13 f7:

    #import <MusicKit/MusicKit.h>
    
    main() {
        MKMidi *aMidi = [[Midi alloc] init];    /* Create MKMidi object */
        MKNote *aNote = [[MKNote alloc] init];  /* Create a MKNote */
    
        [aMidi run];                            /* Start it up */
        [aNote setPar: MK_sysExclusive toString: @"f0,8,13,f7"];
        [[aMidi noteReceiver] receiveNote: aNote]; /* Send it */
        [aMidi close];
    
        exit(0);
    }

Converting MIDI to MusicKit MKNotes

These rules deal with receiving MIDI messages from a MKMidi object that represents the MIDIN jack on your interface, as well as reading Standard MIDI files into MKScore objects.

Note that the MKNotes created by a MKMidi object and sent during a performance should not be stored or modified. The rule here is "copy on modify or store"―to store or modify a MKNote simply send it the copy message to create a new copy.

MIDI Channel Voice Messages

MIDI Channel Voice messages are the most common type of message. For all the Channel Voice messages, the MIDI channel is specified as follows:

For reading a MKScore object from a Standard MIDI File, the way the channel is interpreted depends on the "level" of the MIDI file. See the Standard MIDI File Specification for more information on Standard MIDI Files.

For a level 0 file, each MKPart corresponds to a MIDI channel. Therefore, each MKPart info is given a MK_midiChan parameter that specifies its channel ([1-16]). The MKScore method midiPart: takes a channel argument and returns a MKPart with that channel specified in its info MKNote.

For a level 1 or level 2 file, each MKNote is given a MK_midiChan parameter in the range [1-16]. In addition, the MKPart info is given a MK_midiChan parameter of the first MKNote found in that MKPart, since it is a common case that all MKNotes in a track have the same channel.

For receiving incoming MKNotes from a MKMidi object, the way the channel is specified depends on how the method setMergeInput: was invoked. If you send [midiObj setMergeInput:YES], then each MKNote is sent out the zero'th MKNoteSender with the MIDI channel specified in a MK_midiChan parameter. Keep in mind that this applies only to MIDI Chanel Voice messages. For all other kinds of messages, the MKNotes are sent out the 0'th MKNoteSender. On the other hand, if you send [midiObj setMergeInput:NO], each MKNote is sent out the c'th MKNoteSender, where c is the MIDI channel; in this case, no MK_midiChan is supplied.

  • For each noteOn message, a MKNote object is created and forwarded to the appropriate MKNoteSender, as explained above. It is also given a noteType of MK_noteOn, MK_keyNum parameter that provides the key number ([0-127]) and a MK_velocity parameter that provides the velocity ([0-127]). Additionally, it is given a noteTag ([0-MAXINT]) corresponding to its Key Number and Channel. However, you should not depend on the particular value of the noteTag to derive the Key Number and Channel―use the parameters instead. The noteTag is essential in that it makes it possible to match noteOff and polyphonic key pressure MKNotes with the corresponding noteOn MKNotes.

    Currently multiple noteOns on the same Key Number and Channel are interpreted as rearticulations, rather than as additional voices.

  • For each noteOff message for which an unmatched noteOn was previously received , a MKNote object is created with type MK_noteOff. It is given a noteTag that is the same as you gave the corresponding noteOn MKNote. If inclusive MIDI translation mode is selected (using the function MKSettInclusiveMidiTranslation()), then a MK_keyNum parameter is additionally included ([0-127]) and unmatched noteOffs are passed along to the application, rather than being filtered out.

    If multiple noteOff messages are received (for a particular Channel/Note number) without intervening noteOn messages, only the first noteOff message is converted into a MKNote object. The others are suppressed. However, if inclusive MIDI translation mode is selected, the noteOff is not surpressed and is passed on to the application.

    If a noteOff message has a release velocity of 0, the MK_releaseVelocity parameter in the corresponding MKNote object is omitted.

  • For each polyphonic aftertouch (aka “key pressure”) message for a channel/key number for which a noteOn was received, a MKNote object is created of type MK_noteUpdate, with a noteTag that matches the noteOn received on the same channel/key number pair. The key number is not explicitly included, unless inclusive translation mode is enabled, in which case a MK_keyNum parameter is additionally included. The key pressure is specified in the parameter MK_keyPressure ([0-127]).

  • For each controller change message, a MKNote is created with the parameter MK_controlChange, which specifies the value of the controller to be changed ([0-121]), and the parameter MK_controlVal, which specifies the value of the controller ([0-127]). The noteType is set to MK_noteUpdate.

  • For each program change message, a MKNote is created with the parameter MK_programChange, which provides the value of the program change ([0-127]). The noteType is set to MK_noteUpdate.

  • For each channel pressure (aka “after touch”) message, a MKNote is created with the parameter MK_afterTouch, which provides the value of the afterTouch ([0-127]). The noteType is set to MK_noteUpdate.

  • For each pitch bend message, a MKNote is created with the parameter MK_pitchBend, which provides the value of the afterTouch ([0-0x3fff]). The noteType is set to MK_noteUpdate.

MIDI Channel Mode Messages

Incoming Channel Mode messages are handled by the MKMidi object by fashioning them into MKNote objects and sending them to the 0'th MKNoteSender. Similarly, when a Standard MIDI file containing Channel Mode messages is read, they are turned into MKNotes and added to the MKPart returned by sending the MKScore the message midiPart: with an argument of 0.

MIDI Channel Mode messages are represented by a MKNote object with the parameter MK_basicChan set to the basic channel ([1-16]) and MK_chanMode set to one of the following int values (defined in MusicKit/params.h):

  • MK_resetControllers

  • MK_localControlModeOn

  • MK_localControlModeOff

  • MK_allNotesOff

  • MK_omniModeOff

  • MK_omniModeOn

  • MK_monoMode

  • MK_polyMode

In addition, for monoMode, the parameter MK_monoChans specifies the number of Mono channels ([0-127).

MIDI System Common Messages

Incoming System messages are handled by the MKMidi object by fashioning them into MKNote objects and sending them to the 0'th MKNoteSender. Similarly, when a Standard MIDI file containing System messages are read, they are turned into MKNotes and added to the MKPart returned by sending the MKScore the message midiPart: with an argument of 0.

  • A MIDI time code quarter framemessage is represented by a MKNote with the parameter MK_timeCodeQ, with a value as defined in the MIDI Time Code Specification ([0-127)].

  • A song position pointer message is represented by a MKNote with the parameter MK_songPosition, which is a 14-bit integer ([0-0x3ff]). See the MIDI Specification for details.

  • A song selectmessage is represented by a MKNote with the parameter MK_songSelect with the integer value of the song to be selected ([0-127]).

  • A tune requestmessage is represented by a MKNote with the parameter MK_tuneRequest. The value of the parameter is irrelevant―only its presence is significant.

  • MIDI System Real Time messages are represented by a MKNote object with the parameter MK_sysRealTime set to one of the following int values (defined in MusicKit/params.h):

    • MK_sysClock

    • MK_sysStart

    • MK_sysContinue

    • MK_sysStop

    • MK_sysActiveSensing

    • MK_sysReset

  • MIDI System Exclusive messages are represented by a MKNote object with the parameter MK_sysExclusive with a string value. The string consists of hexadecimal system exclusive bytes separated by a comma. For example, "f0, 8,13,f7". The string begins with f0 and ends with f7.

Reading and Writing Standard MIDI Files

In addition to the conversions described above, a few special rules apply when converting to or from a Standard MIDI file.

Standard MIDI Files come in three varieties: level 0, level 1 and level 2. The MusicKit always writes level 1 files. It reads all three varieties.

When a level 0 file is read, the Channel Voice messages are written into 16 MKParts, one for each channel. Channel Mode and System messages are combined in an additional MKPart. The midi channel of a particular MKPart can be determined by examining the MK_midiChan parameter of the MKPart info MKNote. The special system MKPart has no MK_midiChan parameter.

When a level 1 file is read, each track is written to a separate MKPart. The track number is set in the MKPart info's MK_track parameter. SMTPTE offset is set in the MK_smpteOffset parameter of the MKScore info.

When a level 2 file is read, each track is written to a separate MKPart. The track number is set in the MKPart info's MK_sequence parameter. SMTPTE offset is set in the MK_smpteOffset parameter of the MKPart info.

If an MK_smpteOffset is included, it is encoded as a string consisting of five hex numbers, separated by spaces. Similarly, when writing a Standard MIDI file, the MusicKit expects to see such a string as the value of the MK_smpteOffset parameter. See Standard MIDI file spec for details.

When reading a file, tempo is encoded in the MK_tempo parameter of the MKScore's info and also represented in MKNotes with a MK_tempo parameter. The MKScore class method +setMidifilesEvaluateTempo: controls whether tempo is merely passed along to the application (if the argument to setMidifilesEvaluateTempo: is YES) or whether it is factored into the MKNotes' timestamps. If you want the tempo track to be meaningful, you should set setMidifilesEvaluateTempo:NO. Similarly, when writing, the MKScore info's tempo is written, as are any MKNotes with MK_tempo parameters, and the value set with +setMidifilesEvaluateTempo: determines whether tempo is factored into the time stamps written.

Copyright is represented in the MKScore's info as an MK_copyright parameter with a string value.

The info of each MKPart that corresponds to a track is represented as an MK_instrumentName parameter if a corresponding meta-event appears in the file. This parameter has a string value.

Other MIDI file meta-events such as time signature, lyric, etc. appear as corresponding MKNote parameters in mute MKNotes in the appropriate MKPart. These include MK_lyric, MK_marker, MK_cuePoint, MK_text, MK_timeSignature and MK_keySignature. These all have string values.