- Originally published March 30, 2014.
Part 3 of a Series
Opening a MIDI file and parsing the tracks.
Sample MIDI File
For most of these posts on parsing MIDI files I’ll be using this simple piece of music as an example. It is Bach’s "Two-Part Invention No. 1". Here is a PDF of the sheet music and a MIDI file for the piece.
Tracks
A MIDI file contains a series of chunks of data called tracks.

- The first track is always the MIDI header.
- In modern MIDI files there is always one special track called the Tempo Track. As its name says, events related to tempo will be placed in this track.
- After the Tempo Track we have the tracks with music events, such as notes. There can be many tracks in a MIDI file.
Tracks and Music Parts
After the Tempo Track, MIDI tracks are usually associated with a part in a piece of music. Our example piece is for piano and has a part for each hand.

One part is in the treble clef and the other in the bass clef in a grand staff. These parts are played simultaneously when the music is performed. However, the data for each part are in separate chunks written one after another in the MIDI file. The entire file must be loaded and parsed before the piece can be played.
Loading a MIDI file
To load a MIDI file and parse it with Core MIDI we start by importing the AudioToolbox
library.
Import the Audio Toolbox
#import <AudioToolbox/AudioToolbox.h>
Next we need to declare a variable for our MusicSequence
struct. Core MIDI is a C library and the following code is a common idiom that Apple will use for declaring and creating a struct.
MusicSequence sequence;
NewMusicSequence(&sequence);
Now we need to have a URL to a midi file. This code assumes the file is in the Desktop directory.
Create an NSURL instance to the file
NSURL *midiFileURL = [NSURL fileURLWithPath:@"/Users/<user>/Desktop/bach-invention-01.mid"];
//
This code will load the midi file into the MusicSequence
. The kMusicSequenceLoadSMF_ChannelsToTracks
flag will cause the file to be parsed into separate tracks for the different categories of MIDI events even if the file does not contain have those tracks.
Load the MIDI File
MusicSequenceFileLoad(sequence, (__bridge CFURLRef)midiFileURL, 0,
kMusicSequenceLoadSMF_ChannelsToTracks);
Summary
Here’s what we have so far. This code combines the code in the above steps.
Load the MIDI File
MusicSequence sequence;
NewMusicSequence(&sequence);
NSURL *midiFileURL = [NSURL fileURLWithPath:@"~/eknapp/Desktop/bach-invention-01.mid"];
MusicSequenceFileLoad(sequence, (__bridge CFURLRef)midiFileURL, 0,
kMusicSequenceLoadSMF_ChannelsToTracks);
Extract the Tempo Track from the MIDI File
We’re now ready to split the file into its tracks. We know there is just one Tempo Track so that is an easy first step. We need to use an opaque struct called a MusicTrack
to hold each track. Then we use a C function to get the track.
Extract the Tempo Track
MusicTrack tempoTrack;
MusicSequenceGetTempoTrack(sequence, &tempoTrack);
Next we need an iterator that allows us to loop trough the events in the track. Following the familiar pattern, we declare a struct and then call a Core MIDI function.
Create an Iterator
MusicEventIterator iterator;
NewMusicEventIterator(tempoTrack, &iterator);
We’ll need these variables next to work with the events as we loop through the track.
Housekeeping Variables
Boolean hasNext = YES;
MusicTimeStamp timestamp = 0;
MusicEventType eventType = 0;
const void *eventData = NULL;
UInt32 eventDataSize = 0;
Now we’ll loop through all the events in this track. The way we loop is a little different than classic looping. Here’s the code and I’ll go into detail after it.
Loop through the events
MusicEventIteratorHasCurrentEvent(iterator, &hasNext);
while (hasNext) {
MusicEventIteratorGetEventInfo(iterator,
×tamp,
&eventType,
&eventData,
&eventDataSize);
// Process each event here
printf("Event found! type: %d\n", eventType);
MusicEventIteratorNextEvent(iterator);
MusicEventIteratorHasCurrentEvent(iterator, &hasNext);
}
- Line 1: The
MusicEventIteratorHasCurrentEvent()
function determines if there is an event to process at the current position. MIDI events can be different sizes so a normal indexed loop through an array won’t work. We need to run this function once before we start the loop in case the track is empty. - Line 2: Loop until there are no more events.
- Line 3-7: The
MusicEventIteratorGetEventInfo()
function will get all the data from the current event and populate our variables. We can test theeventType
variable to determine what event we have. - Line 10: We’ll just output that we found an event and its type number.
- Line 12: Attempt to move to the next event with
MusicEventIteratorNextEvent()
. - Line 13: Use the
MusicEventIteratorHasCurrentEvent()
function to determine if there is a current event. If there isn’t one then thehasNext
variable will be set toNO
and the loop will end.
Output
When we run the above code against the bach-invention-01.mid file we see this output.
Event found! type: 5
Event found! type: 3
The types we found are identified in the following enum. Type 5 is kMusicEventType_Meta
, which is a MIDI Meta Event. Type 3 is kMusicEventType_ExtendedTempo
, which gives us the starting tempo of the piece. We’ll explore these types in more detail a later post in the series.
The MusicEventType Enum
enum {
kMusicEventType_NULL = 0,
kMusicEventType_ExtendedNote,
kMusicEventType_ExtendedControl,
kMusicEventType_ExtendedTempo,
kMusicEventType_User,
kMusicEventType_Meta,
kMusicEventType_MIDINoteMessage,
kMusicEventType_MIDIChannelMessage,
kMusicEventType_MIDIRawData,
kMusicEventType_Parameter,
kMusicEventType_AUPreset,
kMusicEventType_Last
};
typedef UInt32 MusicEventType;
Full Code Sample
Here is the full code from the this post. It is all in the main()
function to simplify the code. We’ll add functions to improve the code in the next post.
The Full Parsing Code Sample
//
// main.m
// MIDI Parsing 1
//
// Created by Eric Knapp on 3/30/14.
// Copyright (c) 2014 Eric Knapp. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
int main(int argc, const char * argv[])
{
// Load the MIDI File
MusicSequence sequence;
NewMusicSequence(&sequence);
NSURL *midiFileURL
= [NSURL fileURLWithPath:@"/Users/eknapp/Desktop/bach-invention-01.mid"];
MusicSequenceFileLoad(sequence, (__bridge CFURLRef)midiFileURL, 0,
kMusicSequenceLoadSMF_ChannelsToTracks);
// Get the Tempo Track
MusicTrack tempoTrack;
MusicSequenceGetTempoTrack(sequence, &tempoTrack);
// Create an iterator that will loop through the events in the track
MusicEventIterator iterator;
NewMusicEventIterator(tempoTrack, &iterator);
Boolean hasNext = YES;
MusicTimeStamp timestamp = 0;
MusicEventType eventType = 0;
const void *eventData = NULL;
UInt32 eventDataSize = 0;
// Run the loop
MusicEventIteratorHasCurrentEvent(iterator, &hasNext);
while (hasNext) {
MusicEventIteratorGetEventInfo(iterator,
×tamp,
&eventType,
&eventData,
&eventDataSize);
// Process each event here
printf("Event found! type: %d\n", eventType);
MusicEventIteratorNextEvent(iterator);
MusicEventIteratorHasCurrentEvent(iterator, &hasNext);
}
return 0;
}
Wrap Up
We are now looking at the events in a MIDI file. We’ll start adding to this in the next post. Until then,
Happy coding,
-Eric