Now, I know almost every technical writeup on this blog is obscure, but this is going to be a really weird one. This is about a round of surgery I applied to a set of MIDI files a few years back to get them playing on a device I owned. I started out thinking this was going to be a quick writeup on something I’d already done but, as you’ll see, I encountered a significant plot twist that greatly complicated things. But I get ahead of myself!
This will shock no one who’s read my recent posts, but I love retro music technology. One of my personal prize possessions is a Roland MT-32, a classic MIDI synthesizer from the 80s, and I’ve got an enormous collection of hundreds of MIDIs I listen to. Yes, my life is exactly like the Cinco MIDI organizer sketch.
I especially love listening to MT-32 songs on original hardware because it’s just such an unusual synth. It’s one of a few devices Roland made using their “linear arithmetic synthesis”, which combines traditional synthesis1 with PCM-based samples. The marriage of two very different styles of synthesis gives it an incredibly unique sound unlike pretty much anything else, and I adore hearing what people do with it. On top of its unique style of synthesis, it was completely programmable - unlike pretty much all of the other early PCM-based synthesizers. It allowed musicians to blend its PCM samples in novel ways and to freely program the substractive synthesis portions, which meant that on top of its 128 builtin instruments it was possible to produce an incredibly wide range of different sounds.
There’s a good chance you’ve heard a higher-end version of the sound from the Roland D-50, the MT-32’s older cousin - for example, the main instrument from the backing track to Orinoco Flow. Never heard an MT-32? Here’s a recent song by Bubblegum Octopus, who I consider to be the modern-day master of the MT-32’s sound:
A few years back, I was linked to a set of about 20 MT-32 MIDIs I really wanted to listen to… which, unfortunately, didn’t play back correctly on my hardware. The problem is, MT-32 isn’t really one device but a family of compatible devices. Mostly compatible devices, that is. I have an original-model “old” MT-32, but Roland followed it up with a couple of newer models: a revised model with slightly improved sound quality; and a newer device called the CM-32L, which adds a few extra builtin instruments2. The “new” MT-32 wasn’t marked as a different device from the old one, but unluckily for me it and the CM-32L have one big compatibility difference from the “old” MT-32 that breaks some music for me.
Since the MT-32 has programmable instruments, it obviously needs to be able to accept commands to program them. And since it’s a pure MIDI device, it needs to be able to take those commands over MIDI. To support things like this, MIDI has a feature called system exclusive commands which allows sending MIDI messages with contents that are specific to a particular set of hardware or a particular manufacturer’s products. You can transfer them separately from music, but since they’re just MIDI messages like actual music commands you can actually embed them right in a MIDI file so they’ll be sent as you play it.
The earliest model of MT-32 has a tiny amount of RAM to serve as a buffer that can easily get overwhelmed by sending too many large MIDI commands too quickly. Many sources will give you strict instructions to include at least 40ms of delay between each command, which was widely ignored - especially since the later models all included a larger buffer that didn’t need you to be nearly as strict about it. If you, like me, have an older model MT-32 you may well find that a lot of music composed for the newer models just won’t play back right without tweaks. (I’m very used to the dreaded “Buffer Overflow” and “Exc. Checksum Error” messages on its builtin LCD.)
There are fixes for this if you’re using emulators or certain other retro software environments; DOSBox has a configuration option to enforce 40ms between messages, for example, which will adjust the timing of data on the fly before sending it off to real hardware. None of that helps me when I’ve just got a set of MIDIs I’m listening to on a normal MIDI player, though. This is a bit too specialized a feature to show up in any normal MIDI player, and I’m not far gone enough to be writing my own yet3.
I could have just opened the files in a sequencer and done it by hand, but there were 20 songs in this set and I didn’t really want to be doing that much manual editing. Since this keeps happening to me, I’d rather have some kind of solution that I can use for other music in the future instead of needing to do manual surgery every single time. The plan: come up with some way to programmatically open MIDI data; identify the system exclusive commands; adjust the space between them so there’s always at least 40ms; and then shift the actual music data by the total of all the delays I’d introduced so that the rest of the timing still lines up.
While I was looking around for options, someone suggested I try out a pair of interesting-looking tools: midicsv and csvmidi. The former takes a binary MIDI file and transforms it into a plaintext CSV file, and the latter does the opposite. If it worked, this looked like it would be perfect. It would take no time to write up a short script to manipulate the CSV and transform it back into a MIDI, and it should be pretty easy to apply whatever I figured out here to other files in the future. First, though, a test: if I was going to be round-tripping this through CSV and back again, could I convert it from MIDI to CSV to back again and have it be the same?
1
2
3
4
5
$ sha2 -256 01.mid
SHA-256 (01.mid) = 617a8e8144c1d7da0c2be712635a61cdccbfa49dd3c2c5e50942247dc26a7a6a
$ midicsv 01.mid 01.csv
$ csvmidi 01.csv 01-roundtripped.mid
SHA-256 (01-roundtripped.mid) = 617a8e8144c1d7da0c2be712635a61cdccbfa49dd3c2c5e50942247dc26a7a6a
Yes! The files are byte-identical. I should be able to trust that any changes in the output file are the ones I want.
Now that I know this workflow looks viable, let’s take a look at that CSV output:
1
2
3
4
5
6
0, 0, Header, 0, 1, 384
1, 0, Start_track
1, 0, Tempo, 500000
1, 0, Time_signature, 4, 2, 24, 8
1, 209, Control_c, 1, 10, 64
1, 326, System_exclusive, 12, 65, 0, 22, 64, 6, 0, 0, 0, 64, 0, 58, 247
Pretty simple format. As expected, this is raw MIDI data represented in text rather than binary data. MIDI is, essentially, a series of events which occur on a timeline; each of them is a command like, say, “play a certain note on a certain channel”. Each row in this CSV is an individual command, which makes it very easy for us to find exactly what we want to work with. There’s only really two fields that are relevant to us here for what we want to do: the third field is the name of the event, which lets us identify the System_exclusive messages, while the second field is the absolute time at which this event occurs. Since we don’t need to actually read the content of the system exclusive messages and don’t need to care about any other messages except what timestamp they’re at, this is nearly all the information we need for automated MIDI surgery.
That time field requires a little explanation. Since MIDI is a protocol for sequenced music, not recorded music, it’s not using an absolute time measurement; it’s using a value relative to the song’s time signature. This is measured in “MIDI clocks”, which are defined as 1/24 of a quarter note. We can turn that into absolute time values using the value defined in the tempo command, which is defined as taking the number of microseconds per quarter note. In this case our Tempo 500000 lets us know that each quarter note is 500,000μs (0.5 seconds) and the song plays at a tempo of 120 beats per minute4, so we can derive what our 40ms interval is in MIDI clock units from that. Given these numbers we can determine that at 120bpm, one MIDI clock is about 20.83ms, so we need at least two MIDI clocks between each system exclusive message to be safe. Now that we’ve worked that out, let’s take a look at the actual system exclusive messages in this song. This is from the original, unmodified song and represents MIDI messages 29 through 38 in the file.
1
2
3
4
5
6
7
8
9
10
1, 326, System_exclusive, 12, 65, 0, 22, 64, 6, 0, 0, 0, 64, 0, 58, 247
1, 444, System_exclusive, 255, 65, 16, 22, 66, 5, 0, 0, 0, 0, 24, 50, 12, 0, 1, 0, 0, 1, 24, 50, 12, 0, 1, 0, 0, 2, 24, 50, 12, 0, 1, 0, 0, 3, 24, 50, 12, 0, 1, 0, 0, 4, 24, 50, 12, 0, 1, 0, 0, 5, 24, 50, 12, 0, 1, 0, 0, 6, 24, 50, 12, 0, 1, 0, 0, 7, 24, 50, 12, 0, 1, 0, 0, 8, 24, 50, 12, 0, 1, 0, 0, 9, 24, 50, 12, 0, 1, 0, 0, 10, 24, 50, 12, 0, 1, 0, 0, 11, 24, 50, 12, 0, 1, 0, 0, 12, 24, 50, 12, 0, 1, 0, 0, 13, 24, 50, 12, 0, 1, 0, 2, 6, 24, 50, 12, 0, 1, 0, 0, 15, 24, 50, 12, 0, 1, 0, 0, 16, 24, 50, 12, 0, 1, 0, 0, 17, 24, 50, 12, 0, 1, 0, 0, 18, 24, 50, 12, 0, 1, 0, 0, 19, 24, 50, 12, 0, 1, 0, 0, 20, 24, 50, 12, 0, 1, 0, 0, 21, 24, 50, 12, 0, 1, 0, 0, 22, 24, 50, 12, 0, 1, 0, 0, 23, 24, 50, 12, 0, 1, 0, 0, 24, 24, 50, 12, 0, 1, 0, 0, 25, 24, 50, 12, 0, 1, 0, 0, 26, 24, 50, 12, 0, 1, 0, 0, 27, 24, 50, 12, 0, 1, 0, 0, 28, 24, 50, 12, 0, 1, 0, 0, 29, 24, 50, 12, 0, 1, 0, 0, 30, 24, 50, 12, 0, 1, 0
1, 446, System_exclusive_packet, 10, 0, 31, 24, 50, 12, 0, 1, 0, 49, 247
1, 510, System_exclusive, 255, 65, 16, 22, 66, 5, 2, 0, 0, 32, 24, 50, 12, 0, 1, 0, 0, 33, 24, 50, 12, 0, 1, 0, 0, 34, 24, 50, 12, 0, 1, 0, 0, 35, 24, 50, 12, 0, 1, 0, 0, 36, 24, 50, 12, 0, 1, 0, 0, 37, 24, 50, 12, 0, 1, 0, 0, 38, 24, 50, 12, 0, 1, 0, 0, 39, 24, 50, 12, 0, 1, 0, 0, 40, 24, 50, 12, 0, 1, 0, 0, 41, 24, 50, 12, 0, 1, 0, 0, 42, 24, 50, 12, 0, 1, 0, 0, 43, 24, 50, 12, 0, 1, 0, 0, 44, 24, 50, 12, 0, 1, 0, 0, 45, 24, 50, 12, 0, 1, 0, 0, 46, 24, 50, 12, 0, 1, 0, 0, 47, 24, 50, 12, 0, 1, 0, 0, 48, 24, 50, 12, 0, 1, 0, 0, 49, 24, 50, 12, 0, 1, 0, 0, 50, 24, 50, 12, 0, 1, 0, 0, 51, 24, 50, 12, 0, 1, 0, 2, 5, 24, 50, 12, 0, 1, 0, 2, 18, 24, 50, 12, 0, 1, 0, 0, 54, 24, 50, 12, 0, 1, 0, 0, 55, 24, 50, 12, 0, 1, 0, 0, 56, 24, 50, 12, 0, 1, 0, 0, 57, 24, 50, 12, 0, 1, 0, 0, 58, 24, 50, 12, 0, 1, 0, 0, 59, 24, 50, 12, 0, 1, 0, 0, 60, 24, 50, 12, 0, 1, 0, 0, 61, 24, 50, 12, 0, 1, 0, 0, 62, 24, 50, 12, 0, 1, 0
1, 513, System_exclusive_packet, 10, 0, 63, 24, 50, 12, 0, 1, 0, 119, 247
1, 577, System_exclusive, 255, 65, 16, 22, 66, 5, 4, 0, 1, 0, 24, 50, 12, 0, 1, 0, 1, 1, 24, 50, 12, 0, 1, 0, 1, 2, 24, 50, 12, 0, 1, 0, 1, 3, 24, 50, 12, 0, 1, 0, 1, 4, 24, 50, 12, 0, 1, 0, 1, 5, 24, 50, 12, 0, 1, 0, 1, 6, 24, 50, 12, 0, 1, 0, 1, 7, 24, 50, 12, 0, 1, 0, 1, 8, 24, 50, 12, 0, 1, 0, 1, 9, 24, 50, 12, 0, 1, 0, 1, 10, 24, 50, 12, 0, 1, 0, 1, 11, 24, 50, 12, 0, 1, 0, 1, 12, 24, 50, 12, 0, 1, 0, 1, 13, 24, 50, 12, 0, 1, 0, 1, 14, 24, 50, 12, 0, 1, 0, 1, 15, 24, 50, 12, 0, 1, 0, 1, 16, 24, 50, 12, 0, 1, 0, 1, 17, 24, 50, 12, 0, 1, 0, 1, 18, 24, 50, 12, 0, 1, 0, 1, 19, 24, 50, 12, 0, 1, 0, 1, 20, 24, 50, 12, 0, 1, 0, 1, 21, 24, 50, 12, 0, 1, 0, 1, 22, 24, 50, 12, 0, 1, 0, 1, 23, 24, 50, 12, 0, 1, 0, 1, 24, 24, 50, 12, 0, 1, 0, 1, 25, 24, 50, 12, 0, 1, 0, 1, 26, 24, 50, 12, 0, 1, 0, 1, 27, 24, 50, 12, 0, 1, 0, 1, 28, 24, 50, 12, 0, 1, 0, 1, 29, 24, 50, 12, 0, 1, 0, 1, 30, 24, 50, 12, 0, 1, 0
1, 579, System_exclusive_packet, 10, 1, 31, 24, 50, 12, 0, 1, 0, 7, 247
1, 643, System_exclusive, 255, 65, 16, 22, 66, 5, 6, 0, 1, 32, 24, 50, 12, 0, 1, 0, 1, 33, 24, 50, 12, 0, 1, 0, 1, 34, 24, 50, 12, 0, 1, 0, 1, 35, 24, 50, 12, 0, 1, 0, 2, 19, 24, 50, 12, 0, 1, 0, 1, 37, 24, 50, 12, 0, 1, 0, 1, 38, 24, 50, 12, 0, 1, 0, 1, 39, 24, 50, 12, 0, 1, 0, 1, 40, 24, 50, 12, 0, 1, 0, 2, 17, 24, 50, 12, 0, 1, 0, 1, 42, 24, 50, 12, 0, 1, 0, 2, 11, 24, 50, 12, 0, 1, 0, 1, 44, 24, 50, 12, 0, 1, 0, 1, 45, 24, 50, 12, 0, 1, 0, 1, 46, 24, 50, 12, 0, 1, 0, 1, 47, 24, 50, 12, 0, 1, 0, 2, 9, 24, 50, 12, 0, 1, 0, 1, 49, 24, 50, 12, 0, 1, 0, 1, 50, 24, 50, 12, 0, 1, 0, 1, 51, 24, 50, 12, 0, 1, 0, 1, 52, 24, 50, 12, 0, 1, 0, 1, 53, 24, 50, 12, 0, 1, 0, 1, 54, 24, 50, 12, 0, 1, 0, 2, 13, 24, 50, 12, 0, 1, 0, 2, 10, 24, 50, 12, 0, 0, 0, 2, 12, 24, 50, 12, 0, 1, 0, 2, 16, 24, 50, 12, 0, 1, 0, 2, 4, 24, 50, 12, 0, 1, 0, 2, 8, 24, 50, 12, 0, 1, 0, 2, 2, 24, 50, 12, 0, 1, 0, 2, 7, 24, 50, 12, 0, 1, 0
1, 645, System_exclusive_packet, 10, 2, 1, 12, 50, 12, 0, 0, 0, 64, 247
1, 647, System_exclusive, 5, 65, 16, 22, 69, 247
Taking a look at those gaps, this seemed… less problematic than I expected. This shows gaps between system exclusive messages of at most 118 ticks and at least 2. The two-clock gaps are pretty close to the line, but it looks like everything should be at least above our 40ms target. I tried adjusting the timing to push them out by a minimum of three clocks, but in this case I wasn’t seeing the improvements I was hoping for. The MT-32 was still displaying an “Exc. Checksum Error” as it parsed these messages. So if it’s not just the timing, what else could it be?
Let’s take a look at that system exclusive data again. Something jumps out at me from looking at it, but I’ll need to explain a little bit about how system exclusive data is structured for this to make sense.
Each message starts with an F0 (240) byte identifying the message as a system exclusive message, which midicsv has translated into the System_exclusive header we see here. That’s followed by a byte indicating a count of how many bytes are in the remainder of the message, then the bytes that contain the actual data being sent by the device. It then ends with a checksum byte, which allows the device to validate the integrity of the message, and an F7 (247) byte that indicates that the system exclusive data is finished. The rest of the content between those bytes is specific to a given family of hardware and isn’t relevant to us here.
Now, if we look at that data a little closer, do you notice what’s missing at the end of the System_exclusive messages? That’s right, all of them are missing the F7 termination byte! But if we look at the System_exclusive_packet event that follows each System_exclusive, it’s there instead: we can see that each of those does end with an F7. We can also see that every System_exclusive that is followed by a System_exclusive_packet is stated to be exactly 255 bytes long.
In other words, it looks like what we’re seeing here is a set of system exclusive messages that have been chunked into smaller sizes: each of them has been capped at 256 bytes maximum (255 bytes plus one byte to indicate the length), and if the total size of a single system exclusive message is longer than 255 bytes, then the remaining bytes are sent in a followup message. Looking up documentation online, such as in this Standard MIDI File Format guide, let me confirm that this is unusual but perfectly valid behaviour and that we should be reading the full thing together as a single message. If we still assume we’re seeing some kind of weird timing error with the MT-32, what if we try joining these messages together into a single system exclusive message? In other words, we take these two messages:
1
2
1, 509, System_exclusive, 255, 65, 16, 22, 66, 5, 0, 0, 0, 0, 24, 50, 12, 0, 1, 0, 0, 1, 24, 50, 12, 0, 1, 0, 0, 2, 24, 50, 12, 0, 1, 0, 0, 3, 24, 50, 12, 0, 1, 0, 0, 4, 24, 50, 12, 0, 1, 0, 0, 5, 24, 50, 12, 0, 1, 0, 0, 6, 24, 50, 12, 0, 1, 0, 0, 7, 24, 50, 12, 0, 1, 0, 0, 8, 24, 50, 12, 0, 1, 0, 0, 9, 24, 50, 12, 0, 1, 0, 0, 10, 24, 50, 12, 0, 1, 0, 0, 11, 24, 50, 12, 0, 1, 0, 0, 12, 24, 50, 12, 0, 1, 0, 0, 13, 24, 50, 12, 0, 1, 0, 2, 6, 24, 50, 12, 0, 1, 0, 0, 15, 24, 50, 12, 0, 1, 0, 0, 16, 24, 50, 12, 0, 1, 0, 0, 17, 24, 50, 12, 0, 1, 0, 0, 18, 24, 50, 12, 0, 1, 0, 0, 19, 24, 50, 12, 0, 1, 0, 0, 20, 24, 50, 12, 0, 1, 0, 0, 21, 24, 50, 12, 0, 1, 0, 0, 22, 24, 50, 12, 0, 1, 0, 0, 23, 24, 50, 12, 0, 1, 0, 0, 24, 24, 50, 12, 0, 1, 0, 0, 25, 24, 50, 12, 0, 1, 0, 0, 26, 24, 50, 12, 0, 1, 0, 0, 27, 24, 50, 12, 0, 1, 0, 0, 28, 24, 50, 12, 0, 1, 0, 0, 29, 24, 50, 12, 0, 1, 0, 0, 30, 24, 50, 12, 0, 1, 0
1, 512, System_exclusive_packet, 10, 0, 31, 24, 50, 12, 0, 1, 0, 49, 247
And transform them into this:
1
1, 509, System_exclusive, 265, 65, 16, 22, 66, 5, 0, 0, 0, 0, 24, 50, 12, 0, 1, 0, 0, 1, 24, 50, 12, 0, 1, 0, 0, 2, 24, 50, 12, 0, 1, 0, 0, 3, 24, 50, 12, 0, 1, 0, 0, 4, 24, 50, 12, 0, 1, 0, 0, 5, 24, 50, 12, 0, 1, 0, 0, 6, 24, 50, 12, 0, 1, 0, 0, 7, 24, 50, 12, 0, 1, 0, 0, 8, 24, 50, 12, 0, 1, 0, 0, 9, 24, 50, 12, 0, 1, 0, 0, 10, 24, 50, 12, 0, 1, 0, 0, 11, 24, 50, 12, 0, 1, 0, 0, 12, 24, 50, 12, 0, 1, 0, 0, 13, 24, 50, 12, 0, 1, 0, 2, 6, 24, 50, 12, 0, 1, 0, 0, 15, 24, 50, 12, 0, 1, 0, 0, 16, 24, 50, 12, 0, 1, 0, 0, 17, 24, 50, 12, 0, 1, 0, 0, 18, 24, 50, 12, 0, 1, 0, 0, 19, 24, 50, 12, 0, 1, 0, 0, 20, 24, 50, 12, 0, 1, 0, 0, 21, 24, 50, 12, 0, 1, 0, 0, 22, 24, 50, 12, 0, 1, 0, 0, 23, 24, 50, 12, 0, 1, 0, 0, 24, 24, 50, 12, 0, 1, 0, 0, 25, 24, 50, 12, 0, 1, 0, 0, 26, 24, 50, 12, 0, 1, 0, 0, 27, 24, 50, 12, 0, 1, 0, 0, 28, 24, 50, 12, 0, 1, 0, 0, 29, 24, 50, 12, 0, 1, 0, 0, 30, 24, 50, 12, 0, 1, 0, 0, 31, 24, 50, 12, 0, 1, 0, 49, 247
That retains all of the data from the two events except that it strips the length indicator byte from the System_exclusive_packet before joining the rest of its bytes to the previous message. So I tried playing that, and, lo and behold: no more “Exc. Checksum Error” on the MT-32’s display! The file now plays 100% correctly, with no issues. It seems like we’ve finally gotten to the bottom of it.
Meanwhile, as I was finishing up, I started reaching out to people who owned the newer models to get them to test the original, unaltered MIDIs for me. I’d known all along that these files were composed for a CM-32L, but I’d never actually confirmed whether they played back correctly on the newer models. For all I knew, maybe these files had actually been bad on all models all along and the entire first half of this blog post had been resting on a bad assumption. (Which it was, as it turns out, just not the one I was worried about that this point.) Reports from owners of a CM-64 and a CM-32L came back just as I’d have imagined: the original files played back correctly, and the fixed files weren’t necessary to get them working.
Then came an unexpected message. NMIError from the ScummVM Discord also had an original model MT-32, same as me, and tried the unaltered files on that. They worked correctly.
I started to dig in to figure out what was different. Other than the fact we were using the same model MT-32, our software and hardware setup was pretty different; they were running Windows 11 with a USB ESI M4U eX MIDI interface and Falcosoft Midi Player, while I’m running macOS with a Yamaha MD-BT01 Bluetooth MIDI interface and Sweet MIDI Player Pro. But it didn’t take us long to work out what was at fault here: I had a hunch that my MIDI player was the bad link in the chain here, and that was it. When they tried Sweet MIDI Player for Windows, they saw the same error as me; when I tried alternate players for Mac, I was able to get it to work5.
At this point it’s become clear my original premise and my second premise were both wrong, but it seems we’re finally closing in on a real explanation. Checking that MIDI documentation I linked earlier, we see something that helps explain the error that my MIDI player was exhibiting:
The first packet begins with an F0, but it doesn’t end with an F7. The subsequent packets don’t start with an F0 nor end with F7. The last packet doesn’t start with an F0, but does end with the F7. So, between the first packet’s opening F0 and the last packet’s closing F7, there’s 1 SYSEX message there. […] Of course, since a delay is needed inbetween each packet, you need to store each packet as a separate event with its own time in the MTrk. Also, you need some way of knowing which events shouldn’t begin with an F0 (ie, all of them except the first packet). So, the MIDI file redefines a midi status of F7 (normally used as an end mark for SYSEX packets) as a way to indicate an event that doesn’t begin with F0. If such an event follows an F0 event, then it’s assumed that the F7 event is the second “packet” of a series. In this context, it’s referred to as a SYSEX CONTINUATION event.
Like I mentioned earlier, an F7 byte typically indicates the end of a system exclusive message, but it can also represent the start of a continuation event. Looking at the raw MIDI data in a hex editor, that is indeed what’s happening: the start of the event that midicsv labelled System_exclusive_packet is an F7 byte. I believe that what’s happening is that the MIDI player I’m using saw that F7 byte and mistook it for the end of a system exclusive message, when in fact it’s the start of the next event in the MIDI file. (We can tell it’s not actually the end of the message because we have a count telling us the length of the packet, and that F7 comes after the end of the first packet, not inside it.) Having made that mistake, the MIDI player sends the first part of the message, ends it with an F7, and then skips the continuation event completely. That then tells the MT-32 that the system exclusive message is complete, at which point it tries to calculate the checksum and fails because the second last byte it received isn’t a checksum at all! This also explains why merging the messages had fixed it for me. It’s not because it helped with the timing of sending that data, as I thought, but because it avoided the system exclusive message format that the player I was using would misinterpret.
We’ve been through quite a few plot twists just to get here. I started out assuming this was the classic MT-32 delay problem, which I’ve encountered many times before; then I assumed it was the hardware having issues with the chunked message format; before finally figuring out this was a software problem all along. It’s been a journey.
Now, at this point, I have to come clean with you. I have something to admit, but you have to promise not to get mad. You see,
I didn’t actually do any of that stuff I described above.
At least, not at the time. I did all of that research now, while writing up this blog post, so that I could go into this with full technical detail. At the time, I did something much simpler. Since I’d started with opening one of the files in a MIDI sequencer and peeking at their contents after converting them into CSV, I’d noticed two things:
- All of the files had the same number of system exclusive messages, which were in the same place in every song; and
- I’d already worked out by hand a small tweak to the timing that would get each of the files playable on my MT-32.
So, when I actually did this two years ago, I just wrote a simple script that hardcoded everything: I knew exactly the parts of the MIDI files that needed a delay, and I knew what amount of delay was working, so I could just manually shift everything around.
Of course, as we’ve already established, that wouldn’t have accidentally fixed anything. So why did I think it was fixed? Well, it seems that I fixed it by accident: I did experiment briefly with opening the files in various MIDI-editing tools, and it seems that at least one of them must have automatically merged the system exclusive messages together without telling me. So, for two years I’ve been convinced I fixed these files via csvmidi surgery when it was completely unrelated! I tricked myself this whole time!
This was a long journey just to find out that nothing was wrong with the MIDIs in the first place. Now that we’re finally at our destination, let’s listen to some MIDIs, shall we?
-
It specifically uses subtractive synthesis.↩
-
These are largely sound effects rather than instruments, though I’m sure some songs used them anyway.↩
-
Yet. You never know…↩
-
As far as I can tell, 120 beats per minute is also considered the default MIDI tempo unless a file specifies otherwise.↩
-
Little compatibility report: Sweet MIDI Player Pro for Mac and Windows and PXR MIDI Player for Mac both exhibit the same error with these files, while TMIDI Player for Windows and Drumstick MIDI File Player for Mac play them back correctly. And don’t worry, I’ve submitted bug reports to both players that had the issue.↩








