Retro Challenge 2024 #3 Playbach

Thanks to the detailed documentation of the midi file format at https://www.cs.cmu.edu/~music/cmsip/readings/Standard-MIDI-file-format-updated.pdf, I was able to get a parser for my short test midi files up and running reasonably quickly. The first step was to handle meta events (things like tempo, time signatures, track names and various miscellaneous topics), as most of these have different data sub-structures, with some being free text, some being 24-bit numbers and so on. However, they all have a structure that starts with their length, so it is straightforward to just load in x amount of bytes as specified and then leave them aside for now. I do need to handle tempo and it would be nice to re-use the track names, but most other meta events are not too critical for my purposes.

With the meta events out of the way, it was time to look at the actual note information. I started off with a flexible byte structure because again not all midi events are alike: note on and off take two data bytes, but program change only needs one. This seemed the most flexible option, but in the end I swapped it for simply always loading the delta value (time until the event plays), the status byte (identifying the type of event) and two data bytes. For those cases only using one byte I simply set the second, unused byte to 0xFF and changed the playback code to ignore the data bytes containing that value.

So now I have a loop that steps through the midi file, identifies the event type and skips meta events or loads midi playback events into a stucture that is added to the queue. Once the parser reaches the end of the file, it starts playback, stepping through each event in the queue, taking it off the queue for playback and then waiting the required delta time before sending the note (or other event) to the midi interface.

Type 1

A type 0 file, which only contains a single track, was playable as everything was in the same stream, but type 1 files would play back the first track, then the second, third and so on, so not exactly ideal. The answer is to have a separate midi queue for each track and then the playback loop passes over each track in turn to see if there is a note ready to be played. If all notes played at the same time this would be fine, but would make for some pretty dull music, but of course notes can vary widely in length and start time, so some additional logic was required. I took the idea of a tape playback head as my starting point and sketched out some example notes to see how to best handle the overlapping delta values.

Two yellow notepad pages full of handwritten text and a diagram representing four tracks of note start times.
Left: loop through the 4 tracks showing delta values changing over time. Right: diagram of an example set of note delta times across 4 tracks.

What we have then is a set of events that sit at the virtual tape head that have been taken off the queue for each track. Work out what the shortest delta time is for all of them and take that value away from the delta values currently stored for the “head” events. Now wait for that specified shortest delta time and then playback all the events with zero delta times. Once an event is played back, the next event is taken off the queue for that track and if it also has a delta of zero, then it and any other events behind it with zero deltas (i.e. chords of multiple notes) are also played back. As we run through these, the shortest delta is also recalculated. When all the zero delta events in the head list are played back, we then continue to loop until all events in all the queues are played and the queues are empty.

It may not be the most efficient process, but it seems to work okay, apart from the speed of playback, which I still need to work on, as the tempo event is not yet handled. However, it is all I need for now to move onto the more creative side of this project, which I will go into more detail about in the next post. Until then, here is a test playback of a type 1 midi file (a Bach Canon) through the MU5…

RC2014 playing Bach Canon via MIDI
,
One comment to “Retro Challenge 2024 #3 Playbach”

Leave a Reply

Your email address will not be published. Required fields are marked *