编辑:这是一个独立的例子:
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
是LinkedBlockingQueue
的{{1}}。这允许我做的是在外部类(Consumer<MidiLatte>
,我的自定义MPThread
,是一个内部类)中有一个方法,它将Thread
添加到队列中。这样可以很容易地安排这样的MIDI任务:
Consumer<MidiLatte>
但是,如上所述,我需要能够取消这些任务并在已经发送后停止MIDI播放。前者很简单 - 我可以清除队列:
midiPlayer.addAction(midiLatte -> {
// do midi stuff
});
然而,后者很难,因为MIDI任务已经发送并且掌握在MIDI API手中。我尝试过使用tasks.clear();
,但我得到了奇怪的结果,如下例所示:
sequencer.stop()
player.addAction(midiLatte -> {
//midi stuff
});
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
player.cancelPending();
player.addAction(midiLatte -> {
//Midi stuff
});
调用cancelPending()
并清除队列。最终发生的事情如下(我从经验中知道,当用户不清楚地描述他们的错误时,它可能会令人讨厌和混乱,所以我将逐步展示它。请告诉我你是否仍然不明白什么发生):
为了说明这一点,假设第一个序列的注释为sequencer.stop()
,第二个序列为A B C D E F G A
。应该发生的情况是,如果第一个序列中的{m}介于1 2 3 4 5 6 7 8
和C
之间,则整个回放为D
。但是,实际发生的是A B C 1 2 3 4 5 6 7 8
。
如何实现我想要的行为?如果我设置并发性的方式存在固有问题,请告诉我。只是为了重申我想要的行为,现在就是这样。 按下播放按钮时,将播放MIDI序列。如果我在序列仍在播放时再按一次,则第一次播放停止,序列从头开始播放。请告诉我,我是否不清楚。谢谢!
答案 0 :(得分:1)
好吧,所以,尽管可以说,问题在于除了无法实际控制基础Sequencer
之外的任何事情,因为它继续播放它的缓存数据。
很遗憾,您无法访问Sequencer
... yippy skippy,MidiLatte
也没有为您提供修改代码的扩展点。
HACK解决方案是做你正在做的事情,使用反射访问私有字段,例如......
如果您无法修改MidiLatte
的原始来源,那么您别无选择,为了达到您想要实现的目标,您必须能够访问Sequencer
< / p>
public static class StoppableMidiLatte extends MidiLatte {
public void stop() {
removeAll();
try {
Field f = MidiLatte.class.getDeclaredField("player");
f.setAccessible(true);
Sequencer s = (Sequencer) f.get(this);
s.stop();
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
至于对并发性的需求,我会说你不需要它,因为Sequencer
已经在使用它自己的线程来播放笔记而且并发性只会使这种情况下更加困难< / p>