Eric's Homemade Stuff

An infrequent blog of stuff I make, including music.

MIDI Files and Tracks

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.

MIDI 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.

Music Tracks

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
1
#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.

Create the Music Sequence
1
2
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
1
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
1
2
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
1
2
3
4
5
6
7
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
1
2
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
1
2
MusicEventIterator iterator;
NewMusicEventIterator(tempoTrack, &iterator);

We’ll need these variables next to work with the events as we loop through the track.

Housekeeping Variables
1
2
3
4
5
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
MusicEventIteratorHasCurrentEvent(iterator, &hasNext);
while (hasNext) {
    MusicEventIteratorGetEventInfo(iterator,
                                   &timestamp,
                                   &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 the eventType 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 the hasNext variable will be set to NO 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//
//  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,
                                       &timestamp,
                                       &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


















Comments