Java 停止MIDI音序器,以便我可以播放其他内容
编辑:这里有一个自包含的示例:Java 停止MIDI音序器,以便我可以播放其他内容,java,multithreading,swing,midi,javax.sound.midi,Java,Multithreading,Swing,Midi,Javax.sound.midi,编辑:这里有一个自包含的示例: 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 {
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();
midiatte
是一个实用类,它使发送midi消息和包装序列器变得更容易。基本上,它的工作方式是任务
是消费者
的链接锁定队列
。这允许我做的是在外部类(MPThread
,我的自定义Thread
,是一个内部类)中有一个方法,它将使用者
添加到队列中。这样可以轻松安排MIDI任务,如下所示:
midiPlayer.addAction(midiLatte -> {
// do midi stuff
});
然而,如上所述,我需要能够取消这些任务,并停止MIDI播放一旦他们已经被发送。前者很简单,我只需清除队列即可:
tasks.clear();
但是,后一种方法很困难,因为MIDI任务已经发送并且掌握在MIDIAPI手中。我尝试过使用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()
并清除队列。最终发生的情况如下(我从经验中知道,当用户不清楚地描述他们的bug时,这可能会让人恼火和困惑,所以我将一步一步地展示它。如果您仍然不明白发生了什么,请告诉我):
- 第一个序列开始正确播放
- 3000(或多或少)毫秒后,第一个序列停止
- 第二个序列立即开始播放,但不从头开始;相反,它开始演奏,就好像它一直在演奏一样
为了说明这一点,假设第一个序列的音符是abcdefga
,第二个序列是12345678
。如果在第一个序列中3000密耳落在C
和D
之间,则整个播放是abc1 2 3 4 5 6 7 8
。然而,实际发生的是abc45678
我怎样才能达到我想要的行为?如果我设置并发的方式存在固有问题,请告诉我。只是重申一下我想要的行为,就在这里按下播放按钮时,MIDI序列播放。如果在序列仍在播放时再次按此键,则第一次播放将停止,序列将从一开始再次播放。如果我有任何不清楚的地方,请告诉我。谢谢大家! 好的,所以,正如我们所知,问题在于除了无法实际控制底层的Sequencer
,因为它正在继续播放缓存的数据
不幸的是,您没有访问序列器的权限。。。yippy skippy,midiatte
也不提供任何扩展点供您修改代码
黑客解决方案是做你已经在做的事情,例如使用反射来访问私有字段
!! 这是一个黑客!!
如果您无法修改midiatte
的原始源代码,那么您就别无选择,为了实现您似乎想要实现的目标,您必须能够访问Sequencer
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
已经在使用它自己的线程来播放音符,而并发性只会使在这种情况下更难更快地获得更好的帮助,发布一个or。我与@AndrewThompson合作——让我们了解您的代码和问题的最好方法是创建您的and/or并将其与您的问题一起发布,但这不是一件容易的事情,因为您将不得不模拟我们无法访问的库。我不确定这是否与Swing-per-say有关,所以您可能只需要从自定义库生成一个示例player@ostrichofevil我理解并尊重Swing让你沿着既定的道路工作,但是您遇到的问题与Swing有关,它更像是一个一般的并发问题。我只是建议MVCE不必有Swing组件,因为您可以在没有它的情况下复制问题。如果在生成示例的过程中,它在没有UI的情况下(但使用线程模型)能按预期工作,那么无论如何,包装一个简单的UI,我只是建议您可以生成一个没有UI的简单示例it@MadProgrammer我会试着这么做。对不起,这是我的错,我没有选择在我的例子中放什么,但我已经这么做了!这就是为什么我能够呼叫stop
并遇到问题的原因。即使在我这样做的时候,第二个序列也不是从一开始就开始的。我想我将通过更仔细地查看midiatte
和更多地查看守护进程线程来解决这个问题。我也可以试着获得许可,放弃Midiatte,自己使用声音API。谢谢你抽出时间!我还做了一件事,就是调用removeAll
,不确定它是否有什么不同,但在我的测试中,我能够让它正常工作
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();
}
}
}