EDIT: Here's a self-contained example:
MidiLatte midiLatte = new MidiLatte();
for (int i = 60; i <= 72; i++) {
midiLatte.addNote(i, 4);
}
midiLatte.playAndRemove();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Field f = MidiLatte.class.getDeclaredField("player");
f.setAccessible(true);
Sequencer s = (Sequencer) f.get(midiLatte);
s.stop();
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
for (int i = 48; i <= 60; i++) {
midiLatte.addNote(i, 4);
}
midiLatte.playAndRemove();
I was able to find the source code of MidiLatte
here. Sorry for having do deal with it; I'm required to use it for my class. (If it were up to me, I would use javax.sound.midi
directly…) What this is supposed to do is to start playing a sequence of 12 notes (first for
loop) but stop it after 3000 milliseconds so it can play a different sequence (the second for
loop). However, what ends up happening is that when the second sequence starts playing, it does not do so from the beginning; it starts several notes in.
I am making a Java GUI application that uses the javax.sound.midi
API. Specifically, it is a sequencer which allows users to input notes and play them in sequence. The issue I'm having is that I need a way, after I have sent my MIDI messages, to stop them so that I can play something else. For instance, let's say the user hits play. Then, obviously, the program plays the notes. What I want to have happen is that if the user presses play while the sequence is playing, it stops the current playback and starts over.
The way I have gotten MIDI to work with Swing—it is not trivial to do so without ruining the event queue—is by having a separate Thread
which deals with MIDI stuff. Aside from the problem which I presented in the previous paragraph, this works fine. Here is the run
method of my Thread
implementation:
@Override
public void run() {
midiLatte = new MidiLatte();
//This loop waits for tasks to become available
while (true) {
try {
tasks.take().accept(midiLatte);
} catch (InterruptedException e) {
System.out.println("MPThread interrupted");
}
}
}
MidiLatte
is a utility class that makes it easier to send midi messages and wraps a Sequencer
. Basically, the way that this works is that tasks
is a LinkedBlockingQueue
of Consumer<MidiLatte>
s. What this allows me to do is to have a method in the outer class (MPThread
, my custom Thread
, is an inner class) which adds a Consumer<MidiLatte>
to the queue. This makes it easy to schedule MIDI tasks like this:
midiPlayer.addAction(midiLatte -> {
// do midi stuff
});
However, as stated above, I need to be able to cancel these tasks and stop MIDI playback once they already have been sent. The former is easy—I can just clear the queue:
tasks.clear();
However, the latter is difficult because the MIDI tasks are already sent and are in the hands of the MIDI API. I have tried using sequencer.stop()
but I am getting weird results from that, as seen in this example:
player.addAction(midiLatte -> {
//midi stuff
});
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
player.cancelPending();
player.addAction(midiLatte -> {
//Midi stuff
});
Where cancelPending()
invokes sequencer.stop()
and clears the queue. What ends up happening is as follows (I know from experience that it can be annoying and confusing when a user describes their bug unclearly, so I'm going to show it step by step. Please tell me if you still don't understand what happens.):
- The first sequence starts playing correctly.
- After 3000 (more or less) milliseconds, the first sequence stops.
- The second sequence immediately starts playing, but does not start from the beginning; rather it starts playing as if it had been playing the whole time.
To illustrate this, imagine the notes of the first sequence are A B C D E F G A
and the second sequence goes 1 2 3 4 5 6 7 8
. What should happen is that if 3000 mils falls between C
and D
in the first sequence, the whole playback is A B C 1 2 3 4 5 6 7 8
. However, what actually happens is A B C 4 5 6 7 8
.
How can I achieve the behavior that I want? If there's an inherent problem with the way I've set up my concurrency, please tell me. Just to reiterate the behavior that I want, here it is. When the play button is pressed, the MIDI sequence plays. If I press it again while the sequence is still playing, the first playback stops and the sequence plays again from the start. Please tell me if I am unclear in any way. Thank you!