The good news is that I got the midi queue up and running, with a pair of functions to enqueue and dequeue one midi event at a time, where an event is a combination of a standard midi event (status byte followed by a certain number of data bytes – usually two) plus a delta time value that is used to identify when in time the note or other event is to be played. The idea being that we take an event off the queue and then wait the given delta time before sending it out through the interface. This was tested with a faily random set of notes across two midi channels, sent to my trusty MU5. Here is a video of it working, though the sound is almost non-existant, so I’ll need to sort that out for future videos, where there might be something that sounds like a tune. In the background the RC rapidly enqueues the various notes, then more slowly plays through them. In the foreground, notes register on midi channels 1 and 3. I could find out very little about timing; the clock() function does not seem granular enough and some other C time-related functions don’t seem to work, so this is just using a wait function to pause action for a short time.
With this next milestone completed, I was keen to start a bit of coding on the midi file import, to leave myself something already started for the next day. That’s where the detour began.
Pointers, Endianness and Print
(Sung to the tune of the Hawkwind classic, perhaps)
It seemed like a simple task: load in the 14-byte midi header block from the midi file and split it into the bits of information needed to parse the rest of the file. First of all 4 bytes that should always spell ‘MThd’, then another 4 for the length of the rest of the header (which should always be the number 6 in type 0 and 1 midi files), then those six last bytes, which contain the file format, number of tracks and resolution in pulses per quarter note (ppqn). So far so good.
I set up a struct to hold those items and then a short bit of test code to open the file, read in the bytes directly to the struct and then print out the individual struct members before closing the file. The output I received was not welcoming, apart from the first 4 bytes, which looked fine. The other numbers bore no relation to the values that I knew should be in the other items.
The file structure documentation about the header is quite clear about the length bytes, stating that the bytes are stored with most significant bytes first. This makes sense; midi files were specified in the 16-bit era, when this was the norm, but the z80 was from a simpler time when every bit was precious and bytes were generally ordered starting with the least significant byte (LSB). I had just stumbled over something I remember hearing about from those days, but until now have never been directly affected by: Endianness.
When everything is big endian or little endian, there is no problem, but I was trying to load a big endian file into a little endian system and so some form of conversion was required. At the most basic level, this requires some bit-shifting, which is relatively fast and easy to do and, after a little digging around, found the <endian.h> library already had a function (or macro) to do just that. So I applied bswap_16() and bswap_32() to the gobbledegook variables and…
… more garbage came out. Well, actually the 2-byte values looked like they could be plausible, but the 4-byte value bore no relation to the 6 I was expecting. This led to me trying to convert the values manually, which again seemed to work for the 16-bit versions, but not the 32-bit versions. If I loaded the bytes individually, I was seeing the four bytes: 00 00 00 06 that were expected, but even building up the 32-bit version byte-by-byte fell over. Maybe I should not have been loading the data directly into the address of the struct? Let‘s go down a rabbit hole of discovery about pointers!
No, let‘s not. Suffice it to say that pointers were one reason I stopped using C back in the 90‘s as I ran around in confused circles with my single C book trying to get my head around them and eventually switching to GFA Basic that could be compiled and written without the headaches. The basic concept of pointers was never the issue, but the syntax for dereferencing them and declaring them seemed to be inconsistent and was certainly confusing. Being able to read a couple of different takes and watch a youtube video on the topic clarified a lot and made me wish I had sought a second opinion back in the day. In short, I had understood pointers in the first place and now I was pretty certain that they were not the problem.
A Happy Endian
In the end, after further online searching and head-scratching, it turns out that the printf() function in the compiler or library implementation I was using has a problem with displaying 32-bit integers. Seriously! There was nothing wrong with my original code except that I was trying to display the results with something that could not handle them. I confirmed this by doing some basic additions and subtractions on large 32-bit numbers that would result in a small number and then converting the result to 16-bit and the result was displayed correctly. Just onvert the 32-bit number to 16-bit before printing and… success! There was my missing 6. Strip out all the intermediate testing and hand-built integers, put back the bswap functions and then only at the output stage, coerce the integer into 16-bit format before output and now I could see the full file header. Okay, now repeat for the first track header. It worked exactly as it should.
So, that was quite the unnecessary detour that ate away at several days of the challenge, but ultimately I am fine with that, since it taught me quite a bit about things I needed to know for the rest of this project as well as clarifying a subject that was my biggest hurdle to date with C.
Next Steps
I will continue through the process of loading the midi file, building a loop to step through the main data part of the file and handle meta events (things like copyright notices, track names, lyrics) by basically ignoring them, since they all contain a length value that allows the event to be skipped over. This is one of the nice parts of the specification that leaves it open for future expansion without breaking older implementations. With meta events to one side for now, next up I will start to parse the actual note and controller events so that they can be passed into the queue. I will handle some of the meta events later, but others are really not relevant to this project.
With that logic in place, I will already have a very basic midi file player functionality working and that is where it starts to get a bit more interesting and creative.
Excellent work. It’s frustrating when something like this holds up progress but that’s a part of development.
I have a strange feeling I’ve had that problem with printf and large numbers before too. I’m glad you spotted that and can get on with the fun bit. It seems like you’re well on your way to getting a MIDI file parser / player working.
Thanks, Shiela!
The printf issue is not too painful if the numbers don‘t use all the bytes, which was the case for my little test midi files, so it paid off to have created those first instead of diving straight in with playing some random 3rd party file.