Fixing JDKMidi to auto sort MIDI Events

After reviewing many existing C/C++ MIDI libraries, it appears JDKMidi seems to be the one having the features required by many midddlware libraries.

However, JDKMIDI library (as of Revision 560) suffers a huge drawback of not being able to auto sort the MIDI events. You have to supply the events in correct time order. Fortunately, this is very easy to correct and here is how it can be done. (Get the modified code from: https://cfugue.svn.sourceforge.net/viewvc/cfugue/MusicNote/src/3rdparty/libjdkmidi/)

MIDI Event insertions in JDKMidi library are taken care by the MIDITrack class. It has MIDITrack::PutEvent method that inserts MIDITimedBigMessage objects into its internal buffered chunks. With below modification to that method, now JDKMidi will automatically sort the events based on their time while inserting.

   bool MIDITrack::PutEvent ( const MIDITimedBigMessage &msg )
  {
    if ( num_events >= buf_size )
    {
      if ( !Expand() )
        return false;
    }
    
    //GetEventAddress ( num_events++ )->Copy ( msg );

    //Fix by Gopalakrishna Palem: Automatically sorts the events while inserting

    MIDITimedBigMessage prevChunkLastMsg(msg);
    int nStartEvId = 0; // the EventId where the shifting should start from

    for( ; nStartEvId < num_events; ++nStartEvId)
    {
        if(MIDITimedBigMessage::CompareEvents(*GetEventAddress(nStartEvId), msg ) ==1 ) // if found an event larger than the new entry
        {
            if(nStartEvId == MIDITrackChunkSize - 1) // if last entry in the chunk buf
            {
                MIDITimedBigMessage* pLastEntry = GetEventAddress(nStartEvId);
                prevChunkLastMsg.Copy(*pLastEntry); // Save the last entry
                pLastEntry->Copy(msg); // copy the new message to the last entry
                nStartEvId ++;
            }
            break;
        }
    }

    int nChunkId = nStartEvId / MIDITrackChunkSize;
    int nChunkMax = num_events / MIDITrackChunkSize;

    while(nChunkId <= nChunkMax)
    {
        int nLastEvId = (nChunkId+1)*MIDITrackChunkSize -1;
        MIDITimedBigMessage tempMsg(*GetEventAddress(nLastEvId)); // Save the last entry of the current Chunk

        MIDITimedBigMessage* pSrcMsg = GetEventAddress(nStartEvId);
        MIDITimedBigMessage* pDstMsg = pSrcMsg + 1;
        memmove(pDstMsg, pSrcMsg, sizeof(MIDITimedBigMessage) * (nLastEvId - nStartEvId)); // Shift the first n-1 entries right

        pSrcMsg->Copy(prevChunkLastMsg); // copy the saved last entry of the previous chunk into the first position of current chunk

        prevChunkLastMsg.Copy(tempMsg); // pass on the saved last entry to next chunk beginning 

        nStartEvId = ++nChunkId * MIDITrackChunkSize;
    }
    
    num_events++;

    return true;
  }  

However, there is still a small problem with this approach. The MIDITimedBigMessage::CompareEvents method does not return correct values when comparing note On/Off events occurring at the same time. To fix it we need to apply the below modifications as well:

   int  MIDITimedBigMessage::CompareEvents (const MIDITimedBigMessage &m1, const MIDITimedBigMessage &m2  )
  {
    bool n1 = m1.IsNoOp();
    bool n2 = m2.IsNoOp();
    
    // NOP's always are larger.
    
    if ( n1 && n2 ) return 0; // same, do not care.
      
    if ( n2 ) return 2; // m2 is larger
      
    if ( n1 ) return 1; // m1 is larger
      
    if ( m1.GetTime() > m2.GetTime() ) return 1; // m1 is larger
      
    if ( m2.GetTime() > m1.GetTime() ) return 2; // m2 is larger

    // Fix by Gopalakrishna Palem: if times are the same, a note off should come first. Note on is larger 
    
    bool m1IsOff = (m1.GetStatus() == NOTE_OFF || (m1.GetStatus() == NOTE_ON && m1.byte2 == 0));
    bool m2IsOff = (m2.GetStatus() == NOTE_OFF || (m2.GetStatus() == NOTE_ON && m2.byte2 ==  0));
    
    if (!m1IsOff && m2IsOff) return 1; // m1 is larger 
    
    if (!m2IsOff && m1IsOff) return 2; // m2 is larger 
      
    return 0;  // both are equal.
    
  }

With these modifications, your JDKMidi is ready to take MIDI events in any order. Download the modified version and have fun.

Comments