When saving a MIDI file, it becomes corrupted due to duplicated events. From what I can tell, it seems the issue stems from the code in this location: MidiFile::writeTrack().
The algorithm in this method is not checking to see if there’s an end of track i[/i] event existing prior to writing this event.
The image represents a basic MIDI file I created in a DAW, loaded it with my JUCE project’s code and saved it soon thereafter. The circled regions in this image demonstrate the issue I am having, and stem from the most current tip.
Here is the hex representation of the original file.
Ok, thanks, I see now. But a couple of things are bothering me…
Firstly, looking at my code, I can’t understand why it’s adding the zero byte before the ff 2f 00 marker! I’ve obviously deliberately added code to do that, and it’s been producing files that work, but there’s nothing in the midi file standard that says you should do it, and it doesn’t seem to make any sense! I wrote the class a long time ago, and wish I’d added a comment explaining that decision…
Secondly, I can’t decide whether your suggestion is correct, and that end-of-track events should be removed when writing the track… or whether it’d be better to make the readTrack() remove those events when loading it. Any thoughts?
Well, if you load a MIDI file that works via JUCE code, save it right away, it will create a bogus MIDI file. Unless I’m missing something completely, it’s reading the same event at the end of writeTrack([…]).
My thoughts are that, perhaps in this case, filtering this event series in readNextTrack([…]) and writeTrack ([…]) and adding it at the end may be a safer and robust solution for a JUCE user/programmer. This may seem like overkill, may slow down the process a little bit, but who knows what a user might do in between loading tracks, and later saving tracks.
A separate method that completely cleans out a track’s End of Track event series, and adds it at the end, could be called after reading a track, and before writing it to a file, probably eliminating the need for this code in writeTrack([…]):
out.writeByte (0);
const MidiMessage m (MidiMessage::endOfTrack());
out.write (m.getRawData(), m.getRawDataSize());
ie:
void MidiFile::fixEndOfTrackEvent (MidiMessageSequence& out)
{
//Don't bother with a blank sequence
if(out.getNumEvents() <= 0) return;
MidiMessageSequence seq;
double lastTime = 0.0;
for(int i = 0; i < out.getNumEvents(); ++i)
{
if(out.getEventPointer(i)->message.isEndOfTrackMetaEvent() == false)
{
seq.addEvent(out.getEventPointer(i)->message);
lastTime = out.getEventPointer(i)->message.getTimeStamp();
}
}
seq.addEvent(MidiMessage::endOfTrack(), lastTime);
out = seq;
}
Thanks. In the end I went for your first suggestion, and just filtered them out when writing the track. I figured that even if it correctly removed the events when reading the file, people could mistakenly (or deliberately) add an end-of-track when building their data, so it’d still need to be checked for when writing.