Java MIDI audio is delayed after laptop comes out of hibernation

348 views Asked by At

I'm developing a music programming language, and using the JVM (via Clojure) to play musical scores written in this language. So far, we are just using the javax.sound.midi MidiSynthesizer to play the scores.

Because Clojure has a slow startup time and we want to be able to play a score from the command-line and hear it immediately, we've chosen to structure the score interpreter as a background server process, and communicate with it using a more lightweight command-line client written in Java.

All of this is working great for the most part, however, there is a strange issue that we're seeing where if you start the server, then close your laptop* and let it hibernate, then open it again and have the server play a score, the audio doesn't happen immediately but is delayed for several seconds. Running the server with debug logging, I can actually see that the MIDI note on/off events are happening immediately (and timed correctly), but the audio is delayed.

*This may or may not be platform-specific. I'm seeing the issue on my 2014 Macbook Pro running OS X 10.9.5 Mavericks.

To help narrow it down, I put together this simple example (using Java, not Clojure) that demonstrates the problem:

https://github.com/daveyarwood/java-midi-delayed-audio-example

I've been scratching my head over this for a while now. Why is the audio delayed, and is there anything we can do about it?

2

There are 2 answers

4
apangin On BEST ANSWER

This looks like a bug in Sun's implementation of Synthesizer.

I did not investigate this deeply, but I've found that the problem is apparently in Jitter Corrector that wraps AudioInputStream. Jitter Corrector thread relies on System.nanoTime(). However nanoTime may jump when a computer wakes up from standby or hibernate mode.

The work-around is to disable Jitter Corrector. You can do so by opening Synthesizer this way:

    synth = MidiSystem.getSynthesizer();

    if (synth instanceof com.sun.media.sound.SoftSynthesizer) {
        Map<String, Object> params = Collections.singletonMap("jitter correction", false);
        ((com.sun.media.sound.SoftSynthesizer) synth).open(null, params);
    } else {
       synth.open();
    }
0
Dave Yarwood On

In addition to @apangin's solution, I have found two other workarounds:

  • Before each playback, close and re-open the same Synthesizer instance.

  • Use a new Synthesizer instance for each playback.

Neither of these are ideal because it takes a couple seconds to open a synthesizer instance (even if it's an existing one that was previously open), but these workarounds may be sufficient for some use cases.