Playing MIDI file lacks some note-offs


#1

Hello.

I've used JUCE MIDI functionality to make a very simple MIDI playback program and it goes like this:


    //file input
    ScopedPointer<MidiOutput> OutputController = MidiOutput::openDevice(0);
    ScopedPointer<File> ReadFile(new File("D:/soundcheck.mid"));
    ScopedPointer<FileInputStream> ReadFileStream(new FileInputStream(*ReadFile));
    ScopedPointer<MidiFile> ReadMIDIFile(new MidiFile());
    ReadMIDIFile->readFrom(*ReadFileStream);
    ScopedPointer<MidiMessageSequence> MidiSequence(new MidiMessageSequence());
    ScopedPointer<MidiMessage> msg(new MidiMessage());
    //file output
    ScopedPointer<File> WriteFile(new File("D:/soundcheck2.mid"));
    ScopedPointer<FileOutputStream> WriteFileStream(new FileOutputStream(*WriteFile));
    ScopedPointer<MidiFile> WriteMIDIFile(new MidiFile());
    //get all tracks together
    for (int track = 0; track < ReadMIDIFile->getNumTracks(); track++)
    {
        const MidiMessageSequence* CurrentTrack = ReadMIDIFile->getTrack(track);
        MidiSequence->addSequence(*CurrentTrack, 0,  0, CurrentTrack->getEndTime());
        //write events to other file
        WriteMIDIFile->addTrack(*CurrentTrack);
    }
    WriteMIDIFile->writeTo(*WriteFileStream);
    //should keep note ons and note offs matched?
    MidiSequence->updateMatchedPairs();
    int numEvents = MidiSequence->getNumEvents();
    double TPQN = ReadMIDIFile->getTimeFormat();
    int currentPosition = 0;
    double NextEventTime = 0.;
    double PrevTimestamp = 0.;
    double msPerTick = 250. / TPQN; //set BPM
    //sending messages to output device in loop
    while ((currentPosition < numEvents)) 
    {
        //getting next message
        *msg = MidiSequence->getEventPointer(currentPosition)->message;
        //time left to reach next message
        NextEventTime = msPerTick * (msg->getTimeStamp() - PrevTimestamp);
        //wait for it 
        Time::waitForMillisecondCounter(Time::getMillisecondCounter() + NextEventTime);
        //play it
        OutputController->sendMessageNow(*msg);
        //store previous message timestamp
        PrevTimestamp = msg->getTimeStamp();
        //moving to next message
        currentPosition++;
    }
 

 But for some reason, even with using MidiSequence->updateMatchedPairs(); some note-ons are missing note-off pairs and are hanging in the air till end of the tune.
Still, saving this midi file soundcheck2.mid is played well with windows player. (It's missing delta time events, but note ons and offs are paired).

Where am I wrong?


#2

I don't think that updateMatchedPairs() includes any new notes (note off included), it just assigns MidiEventHolder's noteOffObject to the corresponding note off. If there is no such a note, I guess the sound will just hang till you stop it somehow.

 

As for the MediaPlayer thing - that does not make much sense. Either there are note off event in which case they are sent to MIDI output and correctly interpreted or there are some note off event missing, in which case there is a problem with the midi sequence.


#3

That's weird any program I play tune with is aware of a G note-off, but it just doesn't appear in my sequence for some reason.

It's hanging till the end of song.

 

Edit: I've looked into a bytecode and note-off is actually there. I have no idea why it isn't added in a sequence.


#4

Looks like I've found something relative to an issue, but I still don't know how to handle one.

I had an open note-on event at the end of song in my app and it was hanging until app is closed, and I decided to track note-off event with MidiEditor app.

MidiFile::getLastTimestamp() is returning 238800 tick, so presumably that's a tick corresponding to last event.

Looking into MidiEditor I can see this:

238800 is not latest timestamp, it's latest note-on event and should be followed by note-off on 244080, but it isn't registered by MidiFile. 

Why is that?


#5

That's strange. So if you read the last event with MidiMessageSequence, it's the last event is the notOn at 238800? Even thought that if you open the very same (are you sure?) file in a sequencer, there is a note off? Well my guess is that there is something wrong with the actual file. That editor might be able to cope with it somehow but the JUCE class not... Do you have problems with more files or is it just the one?

 

Also what I don't understand about the picture you posted is that the highlighted timestamps do not fit with the timeline on the top...


#6

It is not known in which the editor was created midi file. There are any errors or omissions. Therefore, any MIDI player should when you switch to stop mode or returns in the loop at the start point of the loop to send to midiOut MidiMessage::allNotesOff(int  channel).

For your own peace of mind.


#7

I've just downloaded bunch of MIDI tunes from internet and can say this issue occures to 50% of files.

Say, downloading and opening juicebox.mid at MidiEditor is giving timestamp of last note-on event at 473760 and note-off one at 474240.

In my app :

    ScopedPointer<File> ReadFile(new File("D:/juicebox.mid"));
    ScopedPointer<FileInputStream> ReadFileStream(new FileInputStream(*ReadFile));
    ScopedPointer<MidiFile> ReadMIDIFile(new MidiFile());
    ReadMIDIFile->readFrom(*ReadFileStream);
    std::cout << ReadMIDIFile->getLastTimestamp() << std::endl;

 is giving 473760, so it doesn't see eny events happening after this timestamp and note is hanging until app is closed.

Opening another file, say, bach.mid is giving last note-off timestamp in the app same as at MidiEditor, so file stops after last event in sequence is executed.

Strange indeed. Can this be something MIDI version related? 

P.S. As you see right now, timeframe at the top is measured in ms, so it doesn't fit to ticks.

 


#8

Sending all-notes-off message to each channel could really be the solution and I'm actually doing so at the end of the sequence, but things are a bit worse.

Mostly unpaired note-on is happening at the end of channel sequence (which is mostly the end of file), but sometimes channel's last event is happening in the middle of track and is left hanging open till the end of file which is really annoying.

What's really weird is Windows Media Player is handling this unpaired note-on event very well and it ends up with a note-off where it's expected. 


#9

I've made a dirty patch to fix it, that would work for a while.


void MidiPlayer::CloseHangingNoteOn(int channel)
{
    ScopedPointer<MidiMessageSequence> ChannelSequence(new MidiMessageSequence());
    MidiSequence->extractMidiChannelMessages(channel, *ChannelSequence, false);
    int LastEvent = ChannelSequence->getNumEvents();
    if (LastEvent > 1)
    {
        MidiMessage LastMessage = ChannelSequence->getEventPointer(LastEvent-1)->message;
        double timestamp = LastMessage.getTimeStamp();
        LastMessage = MidiMessage::allNotesOff(channel);
        LastMessage.setTimeStamp(timestamp);
        ChannelSequence->addEvent(LastMessage, 500);
        MidiSequence->deleteMidiChannelMessages(channel);
        MidiSequence->addSequence(*ChannelSequence, 0, 0, MidiSequence->getEndTime());
    }
}
void MidiPlayer::CloseAllHangingNotes()
{
    for (int i = 1; i <= 15; i++)
    {
        MidiPlayer::CloseHangingNoteOn(i);
    }
}

Problem is that it almost instantly closing a hanging note-on. Sometimes original channel ending can have a long note on, so it's kinda uncomfortable to hear note ending instantly. 

If someone has a better solution - please let me know.  


#10

I've downloaded and parsed hundreds of midi files from various places and haven't found a single one with hanging notes or missing note offs. I don't say they don't exists, but merely suggest you check the files in a proper daw. And the last midi event in a track should always me the End of Track meta event. If it's not there it's broken anyway.