Note: A TIME of 1:1:0 is the very beginning of the MIDI file (ie, exactly on the first downbeat of the first measure).
A SMPTE-based TIME is simply the number of frames (since the beginning of the file) when the event is played. The beginning of the MIDI file is at an offset of 0 frames after the SMPTE start time.
In a MIDI file, the events in a track are ordered according to their TIME. The event with the earliest TIME is first. The event with the latest TIME is last. (That would be the End of Track event).
Let's assume you have loaded an existing MIDI file that has two tracks. The first track has 4 events in it, and the second track has 3 events in it. Here's how the two tracks may look if you used the disasm.rex script to convert a MIDI file to a text file, and it didn't use a SMPTE-based time:
Track #0 ****************************************** 1: 1: 0 |On Note | chan= 1 | pitch=E 3 | vol=127 2: 0 |Tempo | BPM=120 | micros\quarter=500000 2: 1: 0 |Off Note | chan= 1 | pitch=E 3 | vol=64 3: 1: 0 |End of track| Track #1 ****************************************** 1: 1: 30 |On Note | chan= 1 | pitch=G#3 | vol=127 3: 0 |Program | chan= 1 | pgm #= 2 30 |End of track|
If you were to (simultaneously) playback both tracks of this MIDI file, the first event that would get played would be the On Note in the first track, because it occurs on the downbeat of the first measure (ie, right at the start of playback). The first event in the second track is not played until 30 clocks later. And the second event in the first track (ie, the Tempo event) doesn't occur until the second downbeat. So the next two events that would playback would be the On Note in the second track, and then the Tempo event in the first track, in that order. The order of the remaining events would be the Program event in the second track. the End of Track event in the second track, the Off Note in the first track, and finally the End of Track event in the first track.
So events are enumerated/played according to their TIME (assuming a track that has been SORTed, as it would normally be unless you inserted events out of order respective to their TIME). Events with earlier times get enumerated/played before events with later times, regardless of in which track the event is found. Of course, if you were to playback only one track, then the events would get enumerated/played in the exact order that they appear within the track.
Basic concepts
MIDIGetEvent() enumerates the events in one or more tracks simultaneously. But MIDIGetEvent() also allows you to filter for only certain types of events, or events only upon certain channels. So even though MIDIGetEvent() enumerates events as described above, it may skip past events that you wish it to ignore.
When MIDIGetEvent() finds an event that matches your criteria, it sets that event as the currently selected event (and also sets its track as the currently selected track). MIDIGetEvent() also sets some variables in your script to certain values. MIDIGetEvent() sets the variables MIDIEvent.!Measure, MIDIEvent.!Beat, and MIDIEvent.!Clock to the measure number, beat number, and clock pulse (number) upon which the event occurs. If the event is on a MIDI channel, then MIDIEvent.!Channel is set to that channel number (1 to 16), or an empty string if the event is not upon any channel. Which data variables get set, and what they get set to, depends upon the event's type.
Setting the "search tracks"
Once before calling MIDIGetEvent(), you should call MIDITrack() to set which tracks you want MIDIGetEvent() to search for the next event matching your criteria. Then, you call MIDIGetEvent() repeatedly to enumerate the events in those tracks, according to their TIME.
Passing '\' to MIDITrack() sets all of the tracks as searchable (and also sets one of the tracks containing data to be the currently selected track). For example, here is how you would list all of the events in a MIDI file according to when they get played. (Assume that the file has already been loaded via MIDIOpenFile()).
/* Set all tracks as searchable */ selected = MIDITrack('\') /* If any tracks have data... */ IF selected \== 0 THEN DO /* Keep going until an error */ DO UNTIL err \== "" /* Enumerate the next event in any one of the search tracks */ err = MIDIGetEvent() /* If an event was found, display its TIME and type */ IF err == "" THEN DO SAY "Time =" MIDIEvent.!Measure ':' MIDIEvent.!Beat ':' MIDIEvent.!Clock SAY "Type =" MIDIEvent.!Type END /* If the error message indicated something * other than no more events, display it */ ELSE IF err \== "No currently selected event" THEN SAY "ERROR:" err END ENDAlternately, we could tell MIDITrack() to set only certain tracks as searchable. For example, here we make only tracks 1 and 10 searchable, and then list all of the events in those tracks according to when they get played:
/* Set only tracks 1 and 10 as searchable */ selected = MIDITrack(1 10) /* If tracks 1 or 10 have data... */ IF selected \== 0 THEN DO /* Keep going until an error */ DO UNTIL err \== "" /* Enumerate the next event in any one of the search tracks */ err = MIDIGetEvent() /* If an event was found, display its TIME and type */ IF err == "" THEN DO SAY "Time =" MIDIEvent.!Measure ':' MIDIEvent.!Beat ':' MIDIEvent.!Clock SAY "Type =" MIDIEvent.!Type END /* If the error message indicated something * other than no more events, display it */ ELSE IF err \== "No currently selected event" THEN SAY "ERROR:" err END ENDFor some operations upon MIDI files, you may want to step through the tracks one at a time. You could pass each possible track number to MIDITrack and see if it has any data, as so:
/* Set each track in turn */ DO i = 1 TO MIDIGetInfo('TRKS') selected = MIDITrack(i) /* If the track has data... */ IF selected \== 0 THEN DO /* Keep going until an error */ DO UNTIL err \== "" /* Enumerate the next event in any one of the search tracks */ err = MIDIGetEvent() /* If an event was found, display its TIME and type */ IF err == "" THEN DO SAY "Time =" MIDIEvent.!Measure ':' MIDIEvent.!Beat ':' MIDIEvent.!Clock SAY "Type =" MIDIEvent.!Type END /* If the error message indicated something * other than no more events, display it */ ELSE IF err \== "No currently selected event" THEN SAY "ERROR:" err END END ENDBut MIDITrack() offers a shortcut to increment between tracks that have events, skipping over those which do not. By passing an empty string to MIDITrack(), it will return the next track containing data.
/* Set each non-empty track in turn */ WHILE MIDITrack("") \== 0 THEN DO /* Keep going until an error */ DO UNTIL err \== "" /* Enumerate the next event in any one of the search tracks */ err = MIDIGetEvent() /* If an event was found, display its TIME and type */ IF err == "" THEN DO SAY "Time =" MIDIEvent.!Measure ':' MIDIEvent.!Beat ':' MIDIEvent.!Clock SAY "Type =" MIDIEvent.!Type END /* If the error message indicated something * other than no more events, display it */ ELSE IF err \== "No currently selected event" THEN SAY "ERROR:" err END ENDOnce you've gone through all tracks that have data (ie, MIDITrack() returns 0), then this increment shortcut will reiterate with the next call to MIDITrack(). (But you can force the increment to restart from the first track containing data by passing a 0 to MIDITrack()).
/* Reset the shortcut increment */ MIDITrack(0) /* Set each non-empty track in turn */ WHILE MIDITrack("") \= 0 THEN DO /* Keep going until an error */ DO UNTIL err \== "" /* Enumerate the next event in any one of the search tracks */ err = MIDIGetEvent() /* If an event was found, display its TIME and type */ IF err == "" THEN DO SAY "Time =" MIDIEvent.!Measure ':' MIDIEvent.!Beat ':' MIDIEvent.!Clock SAY "Type =" MIDIEvent.!Type END /* If the error message indicated something * other than no more events, display it */ ELSE IF err \== "No currently selected event" THEN SAY "ERROR:" err END END
Filtering by event types
MIDIGetEvent() allows you to filter for certain events. For example, if you wish to enumerate only Tempo events, then you can specify either an event name of 'Tempo' or its event ID number of 81. Here we enumerate only the tempo events in the searchable tracks. (Assume that the MIDI file has already been loaded via MIDIOpenFile() and the desired search tracks set with MIDITrack()).
/* Keep going until an error */ DO UNTIL err \== "" /* Enumerate the next Tempo event */ err = MIDIGetEvent('Tempo') /* If an event was found, display information */ IF err == "" THEN DO SAY "Tempo Micros per Quarter =" MIDIEvent.!Data1 SAY "Tempo BPM =" MIDIEvent.!Data2 END /* If the error message indicated something * other than no more events, display it */ ELSE IF err \== "No currently selected event" THEN SAY "ERROR:" err ENDYou can filter for several types of events. You simply list all of the ID numbers or names, each separated by a '|' character. For example, here we enumerate all of the On Note, Off Note, and (Off) Note events, by specifying ID numbers of 128 and 144 (although we could alternately specify those three event names).
/* Keep going until an error */ DO UNTIL err \== "" /* Enumerate the next On/Off/(Off) event */ err = MIDIGetEvent('128 | 144') /* If an event was found, display information */ IF err == "" THEN DO SAY "Type =" MIDIEvent.!Type SAY "Channel =" MIDIEvent.!Channel SAY "Note Number =" MIDIEvent.!Data1 SAY "Velocity =" MIDIEvent.!Data2 END /* If the error message indicated something * other than no more events, display it */ ELSE IF err \= "No currently selected event" THEN SAY "ERROR:" err ENDWhen specifying ID numbers (rather than names), you could simply use a blank space between numbers instead of a '|' character. For example, the following two lines are the same:
err = MIDIGetEvent('128 | 144') err = MIDIGetEvent('128 144')
When using only blank space between numbers, you can also omit the quotes. For example, the following line is the same:
err = MIDIGetEvent(128 144)
But note that if you wish to place a '\' before the ID numbers (in order to instead specify the ID numbers that you do not want to match), then you must quote that character as so:
/* Enumerate the next event, except for On/Off/(Off) events */ err = MIDIGetEvent('\' 128 144)
Filtering by MIDI channel
MIDIGetEvent() also allows you to filter by MIDI channel. For example, if you wish to enumerate only events upon MIDI channel 1, then you can specify that channel as so:
/* Enumerate the next event upon channel 1 */ err = MIDIGetEvent(, 1)You can filter for several channels. You simply list all of the channel numbers:
/* Enumerate the next event upon channel 1 or 10 */ err = MIDIGetEvent(, 1 10)When filtering by channel, those events which do not have a MIDI channel (such as Meta events, Sysex, and various other MIDI messages) are not enumerated. Alterately, to enumerate only those events that do not have a MIDI channel (while ignoring those which do), you can specify a MIDI channel of 0:
/* Enumerate the next event that isn't on a channel */ err = MIDIGetEvent(, 0)
Filtering by event types and channels
You can specify both a type of event to filter as well as a channel. For example, if you wish to enumerate only On/Off/(Off) events upon MIDI channel 10, then you can specify that as so:
err = MIDIGetEvent('On Note|Off Note|(Off) Note', 10)
Cueing to a TIME position
Normally, when you call MIDIGetEvent(), it picks up the search after the currently selected event. And the first time that you call MIDIGetEvent() after setting the search tracks, it starts from the beginning of the track. So, you proceed through the events from the track start to the end.
But if desired, you could start the search from a particular TIME. You should pass a third arg to MIDIGetEvent() -- the desired TIME at which to start. This is expressed in measures, beats, and clocks, with each component separated by a space, or colon (or other non-numeric character).
For example, here we search for the first tempo event on or after the TIME of 2:1:0.
err = MIDIGetEvent('Tempo', , '2:1:0')If you do not supply a TIME on subsequent calls to MIDIGetEvent(), it will pick up the search from after any event returned above.
Errata
Of course, you could use other variables to store your choice of filtering options.
/* Store the channel options in 'MyChannels' */ MyChannels = 1 10 /* Store the type options in 'MyTypes' */ MyTypes = 'On Note | Off Note | (Off) Note' err = MIDIGetEvent(MyTypes, MyChannels)Also, note that the amount of blank space you place between the options is irrelevant. For example, the following two lines are the same options:
err = MIDIGetEvent('On Note|Off Note') err = MIDIGetEvent('On Note | Off Note')To save some typing, you can abbreviate any of the event names to just the first four characters. For example, the following two lines are the same options:
err = MIDIGetEvent('On Note | Off Note | (Off) Note') err = MIDIGetEvent('On N | Off | (Off')
Conclusion
Given the various combinations of filtering possible with MIDIGetEvent(), you should be able to easily parse the data in a MIDI file, and quickly get to the data that interests you.