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
- Anonymous
November 11, 2010
Sort events order available in nevest version of lib, see it on github.com/.../jdksmidi or github.com/.../jdksmidi