Eric's Homemade Stuff

An infrequent blog of stuff I make, including music.

Core MIDI Measures

Part 6 of a Series

How does MIDI handle measures in music?

Sample MIDI Files

I’ll be using some new MIDI example files in this part of the series in addition to the Bach Invention I have been using.

Core MIDI Measures

The answer to the question, “How does MIDI handle measures in music?” is that it doesn’t. There is no MIDI event for a measure. The MIDI protocol was not meant for music notation. It was only intended as a streaming protocol from one electronic device to another. When we hear a note for a duration of time we have no idea how it was notated in a score or how it should be notated. MIDI mimics playing a musical instrument.

Let’s look at some notation examples to get a better understanding of this. Here are three fragments from an example music score. They are in different time signatures, however they all sound identical when heard.

Sample 1 in 2/4

Sample 2 in 4/4

Sample 3 in 6/8

Here are the first four notes from the treble clef of each sample.

Sample 1 - 2/4
1
2
3
4
timestamp: 0.000 channel: 0, note: 60–C4, duration: 1.000
timestamp: 1.000 channel: 0, note: 62–D4, duration: 1.000
timestamp: 2.000 channel: 0, note: 64–E4, duration: 1.000 <-- Start of new measure
timestamp: 3.000 channel: 0, note: 65–F4, duration: 1.000
Sample 2 - 4/4
1
2
3
4
5
timestamp: 0.000 channel: 0, note: 60–C4, duration: 1.000
timestamp: 1.000 channel: 0, note: 62–D4, duration: 1.000
timestamp: 2.000 channel: 0, note: 64–E4, duration: 1.000
timestamp: 3.000 channel: 0, note: 65–F4, duration: 1.000
...                                                       <-- Start of new measure
Sample 3 - 6/8
1
2
3
4
timestamp: 0.000 channel: 0, note: 60–C4, duration: 1.000
timestamp: 1.000 channel: 0, note: 62–D4, duration: 1.000
timestamp: 2.000 channel: 0, note: 64–E4, duration: 1.000
timestamp: 3.000 channel: 0, note: 65–F4, duration: 1.000 <-- Start of new measure

As you can see, they are all the same. There are no other events around these note events with measure information. The first sample has a measure break after two notes. The second is just one measure. The third sample has a measure break after three notes. There must be something that we can use to determine the measures, and there is. Core MIDI provides us with functions to calculate the measure, beat, and note value of each note. We just need to understand some things about MIDI, C, and Core MIDI to use them.

Pulses Per Quarter note

The first piece of the puzzle is a value associated with a MIDI file, the PPQ. This stands for Pulses Per Quarter note and is sometimes referred to as PPQN. However, Core MIDI uses the term time resolution. This can be confusing when reading the MIDI specs.

Notice that every note in the above samples has a duration of 1.0. This is because these MIDI files were created from notation software. The default nominal value for a quarter note in MIDI is 1.0. It’s easy for notation software to set this since I created these notes as quarter notes.

MIDI is also a live streaming protocol. A musician playing the samples live would not be able play the notes as precisely. Even if the musician had a very steady hand the durations would vary a little. These durations could easily vary from something like 0.9 to 1.1. That’s one of the things that makes music played by a person sound better. Part of making music expressive is varying the timing of notes very slightly for effect. MIDI started as a very light-weight live protocol with very small and predictable data. PPQ is a setting that determines how fine the resolution of this variation will be in a MIDI file. This value is a 2-byte int and frequently ranges from small numbers like 96 up to 960. Core MIDI recommends the value of 480 when creating a MIDI file with the library.

A value of 480 means a quarter note has a resolution of 480 parts. An eighth note will be 240, a sixteenth 120, and so forth. It gets more interesting with things like triplets. With a higher value of 480 for the PPQ, odd time divisions can be more accurate. When a very expressive musician with strong phrasing is playing, the resulting music will sound very natural.


Obtaining the PPQ from a MIDI File

Core MIDI uses this function to determine the PPQ time resolution for a MIDI file. The MusicTrack we use has to be the Tempo Track detailed in part 3. Core MIDI and Core Audio have the concept of properties using functions similar to this. If you’ve done some coding with Core Audio this will seem very familiar.

The MusicTrackGetProperty function
1
2
3
4
5
6
OSStatus MusicTrackGetProperty (
   MusicTrack  inTrack,
   UInt32      inPropertyID,
   void        *outData,
   UInt32      *ioLength
);

The function needs to be used twice to get the property. The first call will retrieve the length of the property. The second call uses the retrieved property length to get the actual property.


Variables

We will need the following two variables for these function calls.

Time Resolution Variables
1
2
UInt32 timeResolution = 0;
UInt32 propertyLength = 0;

First call

The property ID for the time resolution is kSequenceTrackProperty_TimeResolution. It is part of this enum which is found in the MusicPlayer.h file.

Track Properties
1
2
3
4
5
6
7
8
9
enum {
   kSequenceTrackProperty_LoopInfo             = 0,
   kSequenceTrackProperty_OffsetTime           = 1,
   kSequenceTrackProperty_MuteStatus           = 2,
   kSequenceTrackProperty_SoloStatus           = 3,
   kSequenceTrackProperty_AutomatedParameters  = 4,
   kSequenceTrackProperty_TrackLength          = 5,
   kSequenceTrackProperty_TimeResolution       = 6
};

For the first function call the outData argument must be set to NULL. This causes the function to assign the length of the property to the propertyLength variable.

Retrieve the Length of the Property
1
2
3
4
MusicTrackGetProperty(tempoTrack,
                      kSequenceTrackProperty_TimeResolution,
                      NULL,
                      &propertyLength);

Second Call

With the length retrieved we call the function again replacing NULL with the address of the timeResolution variable.

Retrieve the Time Resolution Property
1
2
3
4
MusicTrackGetProperty(tempoTrack,
                      kSequenceTrackProperty_TimeResolution,
                      &timeResolution,
                      &propertyLength);

Here is a typical result of this process.

Output
1
2
3
4
5
6
printf("propertyLength: %d\n", propertyLength);
printf("timeResolution: %d\n", timeResolution);

//Output:
// propertyLength: 2
// timeResolution: 384

The time resolution needs to be saved for future use.

1
self.timeResolution = timeResolution;

Full method

Here’s all the above code in one method.

determineTimeResolutionWithTempoTrack:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)determineTimeResolutionWithTempoTrack:(MusicTrack)tempoTrack
{
    UInt32 timeResolution = 0;
    UInt32 propertyLength = 0;

    MusicTrackGetProperty(tempoTrack,
                          kSequenceTrackProperty_TimeResolution,
                          NULL,
                          &propertyLength);


    MusicTrackGetProperty(tempoTrack,
                          kSequenceTrackProperty_TimeResolution,
                          &timeResolution,
                          &propertyLength);

    printf("propertyLength: %d\n", propertyLength);
    printf("timeResolution: %d\n", timeResolution);

    self.timeResolution = timeResolution;
}

With the fabled PPQ retrieved we can now calculate measures, beats, and subbeats. This is good.

Bar, Beat, and Subbeat

Next, we need to understand what is meant by the terms bar, beat, and subbeat.

  • Bar means measure. They are numbered in a midi file starting with 1.
  • Beat means the beats in a measure. In music the number of beats in a measure is determined by the time signature. With a time signature of 2/4 there are two beats per measure. For 4/4 there are four beats.
  • Subbeat is a term that is not part of common music terminology. In MIDI it refers to where the note occurs within a beat. The length of a beat is the number we calculated in the previous section, the PPQ.

These terms are illustrated in this annotated score. We’ll focus on measure 2.

measures, beats, subbeats

The first note in the treble clef has these parts:

  • Bar: 2 (the second measure)
  • Beat: 1 (the first beat)
  • Subbeat: 0 (the beginning of the beat)

A common way to show this data in MIDI apps is with this notation.

  • 002:01:000 (bar:beat:subbeat)

The second note we’ll mention is note 2 of the bass clef in this measure. It is an eighth note that takes up the second half of the first beat. Therefore, it’s subbeats will be half of the PPQ, or 192. Here are it’s details.

  • Bar: 2 (the second measure)
  • Beat: 1 (the first beat)
  • Subbeat: 192 (the beginning of the second half of the beat)

And it’s condensed notation:

  • 002:01:192 (bar:beat:subbeat)

Calculating Bar, Beat, and Subbeat

Now we finally have enough information to programmatically determine the bar:beat:subbeat for notes in a midi file. This is not a trivial thing to do and we can be very grateful that Core MIDI does it for us.

The first piece is this struct which is used by several MIDI functions. It is found in CoreAudioClock.h.

CABarBeatTime
1
2
3
4
5
6
7
8
struct CABarBeatTime {
  SInt32               bar;
  UInt16               beat;
  UInt16               subbeat;
  UInt16               subbeatDivisor;
  UInt16               reserved;
};
typedef struct CABarBeatTime CABarBeatTime;

Here’s the function we use:

MusicSequenceBeatsToBarBeatTime
1
2
3
4
5
6
OSStatus MusicSequenceBeatsToBarBeatTime(
   MusicSequence   inSequence,
   MusicTimeStamp  inBeats,
   UInt32          inSubbeatDivisor,
   CABarBeatTime   *outBarBeatTime
);

Let’s look at the parameters in more detail.

  • MusicSequence inSequence – the MIDI sequence we have open and are working with.
  • MusicTimeStamp inBeats – the timestamp of the note we’re inspecting.
  • UInt32 inSubbeatDivisor – the PPQ or time resolution we determined in the previous section. We will use that value a lot in MIDI apps.
  • CABarBeatTime *outBarBeatTime – the address of a copy of the struct.

Finally, here’s how we use the function.

Using the MusicSequenceBeatsToBarBeatTime() function
1
2
CABarBeatTime barBeatTime;
MusicSequenceBeatsToBarBeatTime(self.sequence, timestamp, self.timeResolution, &barBeatTime);

Put it all into a method and we’re ready to go.

showNoteInformationWithNote:timestamp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)showNoteInformationWithNote:(MIDINoteMessage *)noteMessage
                          timestamp:(MusicTimeStamp)timestamp
{
    CABarBeatTime barBeatTime;
    MusicSequenceBeatsToBarBeatTime(_sequence, timestamp, self.timeResolution, &barBeatTime);

    printf("%03d:%02d:%03d, timestamp: %5.3f, channel: %d, note: %s, duration: %.3f\n",
           barBeatTime.bar,
           barBeatTime.beat,
           barBeatTime.subbeat,
           timestamp,
           noteMessage->channel,
           noteForMidiNumber(noteMessage->note),
           noteMessage->duration
           );
}

The output for our test measure 2 of the sample in 2/4. First the treble clef.

Measure 2 – Treble Clef
1
2
002:01:000, timestamp: 2.000, channel: 0, note: E4, duration: 1.000
002:02:000, timestamp: 3.000, channel: 0, note: F4, duration: 1.000

Then the bass clef.

Measure 2 – Bass Clef
1
2
3
4
002:01:000, timestamp: 2.000, channel: 1, note: G3, duration: 0.500
002:01:192, timestamp: 2.500, channel: 1, note: A3, duration: 0.500
002:02:000, timestamp: 3.000, channel: 1, note: B3, duration: 0.500
002:02:192, timestamp: 3.500, channel: 1, note: C4, duration: 0.500

Full Output

Here is the full output for all three of the sample files that we started with. The notes are all the same, however the time signatures, number of measures, and beat counts are different.

Sample 1 in 2/4
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
Treble Clef:
001:01:000, timestamp: 0.000, channel: 0, note: C4, duration: 1.000
001:02:000, timestamp: 1.000, channel: 0, note: D4, duration: 1.000
002:01:000, timestamp: 2.000, channel: 0, note: E4, duration: 1.000
002:02:000, timestamp: 3.000, channel: 0, note: F4, duration: 1.000
003:01:000, timestamp: 4.000, channel: 0, note: G4, duration: 1.000
003:02:000, timestamp: 5.000, channel: 0, note: A4, duration: 1.000
004:01:000, timestamp: 6.000, channel: 0, note: B4, duration: 1.000
004:02:000, timestamp: 7.000, channel: 0, note: C5, duration: 1.000
005:01:000, timestamp: 8.000, channel: 0, note: C5, duration: 4.000

Bass Clef:
001:01:000, timestamp: 0.000, channel: 1, note: C3, duration: 0.500
001:01:192, timestamp: 0.500, channel: 1, note: D3, duration: 0.500
001:02:000, timestamp: 1.000, channel: 1, note: E3, duration: 0.500
001:02:192, timestamp: 1.500, channel: 1, note: F3, duration: 0.500
002:01:000, timestamp: 2.000, channel: 1, note: G3, duration: 0.500
002:01:192, timestamp: 2.500, channel: 1, note: A3, duration: 0.500
002:02:000, timestamp: 3.000, channel: 1, note: B3, duration: 0.500
002:02:192, timestamp: 3.500, channel: 1, note: C4, duration: 0.500
003:01:000, timestamp: 4.000, channel: 1, note: B3, duration: 0.500
003:01:192, timestamp: 4.500, channel: 1, note: A3, duration: 0.500
003:02:000, timestamp: 5.000, channel: 1, note: G3, duration: 0.500
003:02:192, timestamp: 5.500, channel: 1, note: F3, duration: 0.500
004:01:000, timestamp: 6.000, channel: 1, note: E3, duration: 0.500
004:01:192, timestamp: 6.500, channel: 1, note: D3, duration: 0.500
004:02:000, timestamp: 7.000, channel: 1, note: C3, duration: 0.500
004:02:192, timestamp: 7.500, channel: 1, note: B2, duration: 0.500
005:01:000, timestamp: 8.000, channel: 1, note: C3, duration: 4.000
Sample 2 in 4/4
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
Treble Clef:
001:01:000, timestamp: 0.000, channel: 0, note: C4, duration: 1.000
001:02:000, timestamp: 1.000, channel: 0, note: D4, duration: 1.000
001:03:000, timestamp: 2.000, channel: 0, note: E4, duration: 1.000
001:04:000, timestamp: 3.000, channel: 0, note: F4, duration: 1.000
002:01:000, timestamp: 4.000, channel: 0, note: G4, duration: 1.000
002:02:000, timestamp: 5.000, channel: 0, note: A4, duration: 1.000
002:03:000, timestamp: 6.000, channel: 0, note: B4, duration: 1.000
002:04:000, timestamp: 7.000, channel: 0, note: C5, duration: 1.000
003:01:000, timestamp: 8.000, channel: 0, note: C5, duration: 4.000

Bass Clef:
001:01:000, timestamp: 0.000, channel: 1, note: C3, duration: 0.500
001:01:192, timestamp: 0.500, channel: 1, note: D3, duration: 0.500
001:02:000, timestamp: 1.000, channel: 1, note: E3, duration: 0.500
001:02:192, timestamp: 1.500, channel: 1, note: F3, duration: 0.500
001:03:000, timestamp: 2.000, channel: 1, note: G3, duration: 0.500
001:03:192, timestamp: 2.500, channel: 1, note: A3, duration: 0.500
001:04:000, timestamp: 3.000, channel: 1, note: B3, duration: 0.500
001:04:192, timestamp: 3.500, channel: 1, note: C4, duration: 0.500
002:01:000, timestamp: 4.000, channel: 1, note: B3, duration: 0.500
002:01:192, timestamp: 4.500, channel: 1, note: A3, duration: 0.500
002:02:000, timestamp: 5.000, channel: 1, note: G3, duration: 0.500
002:02:192, timestamp: 5.500, channel: 1, note: F3, duration: 0.500
002:03:000, timestamp: 6.000, channel: 1, note: E3, duration: 0.500
002:03:192, timestamp: 6.500, channel: 1, note: D3, duration: 0.500
002:04:000, timestamp: 7.000, channel: 1, note: C3, duration: 0.500
002:04:192, timestamp: 7.500, channel: 1, note: B2, duration: 0.500
003:01:000, timestamp: 8.000, channel: 1, note: C3, duration: 4.000
Sample 3 in 6/8
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
Treble Clef:
001:01:000, timestamp: 0.000, channel: 0, note: C4, duration: 1.000
001:03:000, timestamp: 1.000, channel: 0, note: D4, duration: 1.000
001:05:000, timestamp: 2.000, channel: 0, note: E4, duration: 1.000
002:01:000, timestamp: 3.000, channel: 0, note: F4, duration: 1.000
002:03:000, timestamp: 4.000, channel: 0, note: G4, duration: 1.000
002:05:000, timestamp: 5.000, channel: 0, note: A4, duration: 1.000
003:01:000, timestamp: 6.000, channel: 0, note: B4, duration: 1.000
003:03:000, timestamp: 7.000, channel: 0, note: C5, duration: 1.000
003:05:000, timestamp: 8.000, channel: 0, note: C5, duration: 4.000

Bass Clef:
001:01:000, timestamp: 0.000, channel: 1, note: C3, duration: 0.500
001:02:000, timestamp: 0.500, channel: 1, note: D3, duration: 0.500
001:03:000, timestamp: 1.000, channel: 1, note: E3, duration: 0.500
001:04:000, timestamp: 1.500, channel: 1, note: F3, duration: 0.500
001:05:000, timestamp: 2.000, channel: 1, note: G3, duration: 0.500
001:06:000, timestamp: 2.500, channel: 1, note: A3, duration: 0.500
002:01:000, timestamp: 3.000, channel: 1, note: B3, duration: 0.500
002:02:000, timestamp: 3.500, channel: 1, note: C4, duration: 0.500
002:03:000, timestamp: 4.000, channel: 1, note: B3, duration: 0.500
002:04:000, timestamp: 4.500, channel: 1, note: A3, duration: 0.500
002:05:000, timestamp: 5.000, channel: 1, note: G3, duration: 0.500
002:06:000, timestamp: 5.500, channel: 1, note: F3, duration: 0.500
003:01:000, timestamp: 6.000, channel: 1, note: E3, duration: 0.500
003:02:000, timestamp: 6.500, channel: 1, note: D3, duration: 0.500
003:03:000, timestamp: 7.000, channel: 1, note: C3, duration: 0.500
003:04:000, timestamp: 7.500, channel: 1, note: B2, duration: 0.500
003:05:000, timestamp: 8.000, channel: 1, note: C3, duration: 4.000

Wrap Up

Parsing measures from a MIDI file is a big part of whole MIDI file puzzle. We can get a lot done with what we have so far, but we haven’t started on the more subtle parts of the modern MIDI spec.

Just keep coding,

-Eric




Comments