Swapping of Sequencers in JSound

Trying to make realtime streaming of midi possible using JavaSound.
Written by: Espen Riskedal, developer at BEK, student at University of Bergen

Situation in JDK 1.3rc2

The current version of JavaSound (JDK 1.3rc2) does not support dynamically updating of the midisequence it plays (you have to stop the sequencer, change the data, and start it again), and therefore applications trying to play streaming-midi fails more or less (because all mididata has to be known for the sequencer to play it).

One "solution" for this is chopping the mididata as it is generated into small midisequences that are played one after another in a seamless way. But, setting a new sequence right after the sequencer is finished playing the previous one, takes some time... and the callback function notifying when the sequencer is finished also takes some time This causes an annoying gap in the playback of the small sequences.

Reducing the gap?

One way of getting the gap smaller could be using more than one sequencer, and while one sequencer plays, the other(s) updates its mididata (sequence) and is ready for playback as fast as the first sequencer is finished (much in the same way as double-buffering in graphic programming). This will reduce the gap... but how much? Well, thats what I wanted to find out.

I made three midi files (format 0). Two small ones drum01.mid and drum02.mid (174 & 180 bytes, one midi chn.), and a "big" one complex01.mid (1244 bytes, 6 midi chns.). The small ones was set to 140bpm and the big one 100bpm. The length of them all was one measure (I also made 4 measures versions of them: drum01and02.mid and complex01long.mid, for use as reference as to how it should be played).

Results swapping vs. no-swapping

I sampled the output from the testprogram SeqSwapTest.java playing 4 measures (that means swapping sequence three times), and wrote down the start-time for the 4th measure. I did this three times for each set and took the average. Using this I calculated the avg. ms added to the playback of a sequence using swapping or without swapping.

Name Time, 3 meas. (sec) Deviation (msec) pr.meas % added to meas.
drum01 & 025.1460NANA
swapping5.2157231.3
no swapping5.2738432.5
complex017.1976NANA
swapping7.3137391.6
no swapping7.51111054.4

As we can by using more then one sequencer and swapping we almost halves the time added pr. measure for the first example, and more then halves it for the second example. The reason it takes so long for the sequencer to restart playing when using the complex01.mid and not swapping, is because setting the sequence propably takes longer time for bigger sequences. Still, for the sequencer just to callback when finished, and starting another takes time... between 23ms to 39ms in this scenario, and this is noticable.

Listen to see if you tolerate it? (mp3 64kbit, mono)
complex01 (original) 76k complex01 (swapping) 77k complex01 (no swapping) 79k

Conclusion and what next?

It helps, but as you hear the gap is still there.... and it ANNOYS me!! (anything below 10ms we usually think is ok, once it gets above we start to notice and at least to me as a musician it bothers me). On the bright side, using swapping the gap usually gets more stable... it seems to be of allways the same length (at least this proved to be true for my 2x3 testruns with swapping, the sampleposition of the start for the 4th measure was found, by a few samples, allways at the same spot).

I can't really think of any ways to improve this, ofcourse I could propably optimize the code a bit, but I believe the 'bottleneck' of the system now is the callback from the sequencer. This propably gets done as quickly as possible, but as we all know in java, not quick enough :D (scheduling in java sucks).

Any ideas, questions?

espenr@ii.uib.no