编辑:这是一个独立的例子:
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();
我能够找到MidiLatte
here的源代码。很抱歉不得不处理它;我需要在课堂上使用它。(如果由我决定,我会javax.sound.midi
直接使用......)这应该做的是开始播放 12 个音符的序列(第一个for
循环),但在 3000 毫秒后停止它,以便它可以播放不同的序列(第二个for
循环)。然而,最终发生的事情是,当第二个序列开始播放时,它并没有从头开始播放;它开始了几个音符。
我正在制作一个使用javax.sound.midi
API 的 Java GUI 应用程序。具体来说,它是一个音序器,允许用户输入音符并按顺序播放它们。我遇到的问题是我需要一种方法,在我发送我的 MIDI 消息之后,阻止它们,以便我可以播放其他东西。例如,假设用户点击播放。然后,显然,程序播放音符。我想要发生的是,如果用户在播放序列时按下播放,它会停止当前播放并重新开始。
我让 MIDI 与 Swing 一起工作的方式——在不破坏事件队列的情况下这样做并非易事——是通过单独Thread
处理 MIDI 内容。除了我在上一段中提出的问题之外,这工作正常。这是run
我的Thread
实现方法:
@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
是一个实用程序类,可以更轻松地发送 midi 消息并包装一个Sequencer
. 基本上,它的工作方式tasks
是 a LinkedBlockingQueue
of Consumer<MidiLatte>
s。这允许我做的是在外部类中有一个方法(MPThread
我的自定义Thread
,是一个内部类),它将 a 添加Consumer<MidiLatte>
到队列中。这样可以很容易地安排这样的 MIDI 任务:
midiPlayer.addAction(midiLatte -> {
// do midi stuff
});
但是,如上所述,我需要能够取消这些任务并在它们已经发送后停止 MIDI 播放。前者很简单——我可以清空队列:
tasks.clear();
但是,后者很困难,因为 MIDI 任务已经发送并且在 MIDI API 手中。我试过使用sequencer.stop()
,但我得到了奇怪的结果,如本例所示:
player.addAction(midiLatte -> {
//midi stuff
});
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
player.cancelPending();
player.addAction(midiLatte -> {
//Midi stuff
});
WherecancelPending()
调用sequencer.stop()
并清除队列。最终发生的情况如下(我从经验中知道,当用户不清楚地描述他们的错误时,它可能会令人烦恼和困惑,所以我将一步一步地展示它。如果你仍然不明白什么,请告诉我发生。):
- 第一个序列开始正确播放。
- 3000(或多或少)毫秒后,第一个序列停止。
- 第二个序列立即开始播放,但不是从头开始;相反,它开始播放就好像它一直在播放一样。
为了说明这一点,假设第一个序列的音符是A B C D E F G A
,第二个序列的音符是1 2 3 4 5 6 7 8
。应该发生的是,如果 3000 mils 落在第一个序列之间C
和之间D
,则整个播放是A B C 1 2 3 4 5 6 7 8
. 然而,实际发生的是A B C 4 5 6 7 8
.
我怎样才能实现我想要的行为?如果我设置并发的方式存在固有问题,请告诉我。只是为了重申我想要的行为,就是这样。当按下播放按钮时,MIDI 序列播放。如果我在序列仍在播放时再次按下它,第一次播放将停止并且序列从头开始再次播放。如果我有任何不清楚的地方,请告诉我。谢谢!