Fixing JDKMidi to auto sort MIDI Events

Fixing JDKMidi to auto sort MIDI Events

  • Comments 1

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: http://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.

Page 1 of 1 (1 items)
Leave a Comment
  • Please add 5 and 5 and type the answer here:
  • Post