MIDI Programming - A Complete Study
Part 2 - Data Structures

Written by Stéphane Richard (Mystikshadows)

INTRODUCTION:

Welcome to the second part of the MIDI Programming Series. As I mentionned in the first part, we'll
see one way of loading up a MIDI file in memory (in structures) so we can manipulate them at will. Although
when we send this MIDI information to the MIDI ports will have to take the different pieces of information of
each MIDI event and put them back into a series of bytes, we'll see here how useful it can be to treat these
informations as independant fields. Now since we won't be playing the song per se, I'll be implementing the functions
so that the give back the information in a printed textual fashion so you can see we actually did read the MIDI file.

There's alot of coding information up ahead in this part of the series, we'll see exactly what information we can
actually get from a MIDI file's header and track information for one thing. Likewise, we'll see exactly what needs
to be done with each type of MIDI events. Some of these Events require 2 parameters to be read while others require
only one parameter. So, how do we deal with all these different types of MIDI related information? We'll see that
right here, right now. So fasten your safety belts as we implement a complete MIDI file loader and information extractor
utility to give us everything that can be extracted from the MIDI file.

OPENING AND LOADING A MIDI FILE:

When you open a MIDI, it's not enough to open it and load it into, say a string variable, and think you're ready
to play it. At least not at the level we'll be doing it here. You need something that can isolate the tracks and
the isolate the midi events themselves. But first, let's declare some constants that will need when reading the midi file. Help make the code clearer in the process.

Now when we read a MIDI event, we'll need somekind of structure to hold the information that
is found in the MIDI event itself. As I explained in the first part of the series,
a regular MIDI. To this effect, take a look at the following two user defined types:

TYPE TimeStructure
Bar AS INTEGER
Beat AS INTEGER
Tick AS INTEGER
END TYPE
TYPE MIDIEventStructure
EventTime AS TimeStructure
EventDeltaTime AS BYTE
StatusByte AS BYTE
ChannelByte AS BYTE
DataByteOne AS BYTE
DataByteTwo AS BYTE
END TYPE

Now we need another structure to encompass the MIDIEventStructure into an array. we'll need one array per track. For the sake of simplicity of this example, I won't use a dynamic array in each track so we won't be needing pointers and linked list but typically that is
what you would use in a case like this since you don't know how long a song (or each of it's composing tracks) are. Here's the TrackStructure:

These three structures is what we're going to use to keep the song in memory. The TimeStructure
holds the standard beat:bar:tick information. The MIDIEventStructure holds the EventTime (a TimeStructure
sub type) and the Event information. The EventDeltaTime tells the system to send the MIDI event right now but
not play the event until DeltaTime has been reached. In MIDI it's a good technique to use to make sure the data
that is to be played is already sent through the MIDI port and ready to play when the time is right. The StatusByte
is the actual MIDI Event Type (refer to the first part of the series for a list of these MIDI Event Types). The ChannelByte
tells which MIDI channel the MIDI event is to be sent through. DataByteOne and DataByteTwo form the parameters to the
current MIDI event itself.

Because the StatusByte and the ChannelByte are essentially the MSB (Most Significant Byte) and LSB (Least Significant Byte) of a one byte value we will need the following 4 support functions
in order to get the MSB and LSB from the byte and store them in the StatusByte and ChannelByte fields of our MIDIEventStructure. The last function allows to get the length of a variable length MIDI
message (such as SysEx).

With all these tools, we're ready to start the loading process. First thing will do is get some variables ready to read information from the file. We'll open that file in binary mode since we'll be reading bytes from it, binary is the only way to open the file in this case.
Note that This is my version of some code I grabbed off the Internet, I altered it considerably for the sake of clarity of code and I commented it to help you better understand.
I can say however, that if I would have created it totally it would have probably looked like this so I saw fit to just alter this code.

Now that we have our variables declared, we can go ahead and open the MIDI file in question. I have include the song 1492 a piece from the Christopher Columbus movie for our needs. Now we will be implementing 2 subroutines here. The first is the MIDIFileFormatProperties() function
and the other is the Actual ReadMIDIFile Function itself. Since ReadMIDIFile makes use of the ReadMIDIFileFormatProperties function we'll implement that last one first.

Quite a function wouldn't you agree? This particular function will be executed in the Track Loop of the ReadMIDIFile() function. Hence it will be able to get this information, if available, for all tracks of a song. Now comes the core function itself. Let us now take
a look at the ReadMIDIFile Function itself.

There you have it This last function does the actual opening of the MIDI file and drives the loading of the MIDI events into
our Structure. Although these are long functions. I think it's fairly clear to see what's going in here. Note that the EventLabel
that gets populated in each of the Event Types is there for informational purposes only. When we actually do something with the MIDI
File (in the next part of the series) the parts of this code that create a string for display will be taken off.

The main part of the program would simply need to call the ReadMIDIFile with the file 1492.mid as it's parameter for the whole system
to load the file into our SongTracks MIDIEvents array of structures. Such as:

' ------------------------------------------------------------
' Call The Read MIDI File to load the song in memory
' and report some information about the song and it's tracks
' ------------------------------------------------------------
MIDIString = ReadMIDIFile("1492.mid")

IN CONCLUSION:

And there you have it, as promised, we now have a system that can effectively load a file into a structure that can be manipulated as you would any other array. The basic reason why SysEx information is treated so differently is simply because
it is not a fixed piece of information. Anything can happen in SysEx (anything that is allowed by the MIDI gear itself that is). The basic idea is to load up the SysEx information in a string and send that information right to the MIDI port and MIDI
channel you want. The MIDI Gear will receive this information and if it can (if one piece of equipment connected to the MIDI network can process the SysEx, it will). Other than that, it's a bunch of parameters to standard MIDI event types.

In the next part of the series, we'll implement the playback engine itself. This is where timing will take it's importance as we will have a program that will load the MIDI file and actually play it in realtime. With that part, all of what you've learned in the past 2 parts of the series
will really fit together as you'll see how everything is connected together to form a complete MIDI player. OF course, if you have questions or comments, you know how to reach me. So by all means please do so so we can make sure everyone is up to par
on what I just did in this part of the series. It will be important to understand what's happening here before we go to the next part of the series. Until then, Happy MIDI programming.
MystikShadows
Stéphane Richardmystikshadows@gmail.com