Остановите MIDI Sequencer, чтобы я мог играть что-то еще
РЕДАКТИРОВАТЬ: Вот автономный пример:
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
здесь Извините за то, что имел дело с этим; Я должен использовать это для моего класса. (Если бы это было до меня, я бы использовал javax.sound.midi
непосредственно…) Что это должно сделать, это начать играть последовательность из 12 нот (сначала for
цикл), но остановите его через 3000 миллисекунд, чтобы он мог воспроизводить другую последовательность (второй for
петля). Однако в конечном итоге происходит то, что, когда начинает воспроизводиться вторая последовательность, она не делает этого с самого начала; начинается несколько заметок в.
Я делаю приложение Java GUI, которое использует javax.sound.midi
API. В частности, это секвенсор, который позволяет пользователям вводить ноты и воспроизводить их последовательно. Проблема, с которой я сталкиваюсь, заключается в том, что после отправки моих 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
это служебный класс, который облегчает отправку миди-сообщений и переносит Sequencer
, В принципе, способ, которым это работает, заключается в том, что tasks
это LinkedBlockingQueue
из Consumer<MidiLatte>
s. Это позволяет мне иметь метод во внешнем классе (MPThread
мой обычай Thread
, является внутренним классом), который добавляет Consumer<MidiLatte>
в очередь. Это позволяет легко планировать задачи MIDI следующим образом:
midiPlayer.addAction(midiLatte -> {
// do midi stuff
});
Однако, как указано выше, мне нужно иметь возможность отменить эти задачи и остановить воспроизведение MIDI, как только они уже были отправлены. Первое легко - я могу просто очистить очередь:
tasks.clear();
Однако последнее затруднительно, поскольку задачи MIDI уже отправлены и находятся в руках API MIDI. Я пытался использовать 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()
и очищает очередь. В итоге происходит следующее (из опыта я знаю, что это может раздражать и сбивать с толку, когда пользователь неясно описывает свою ошибку, поэтому я собираюсь показать ее шаг за шагом. Пожалуйста, скажите мне, если вы все еще не понимаете, что бывает).:
- Первая последовательность начинает играть правильно.
- Через 3000 (более или менее) миллисекунд первая последовательность останавливается.
- Вторая последовательность сразу начинает играть, но не начинается с начала; скорее он начинает играть так, как будто играл все время.
Чтобы проиллюстрировать это, представьте, что примечания первой последовательности A B C D E F G A
и вторая последовательность идет 1 2 3 4 5 6 7 8
, What should happen is that if 3000 mils falls between C
а также 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. Спасибо!
1 ответ
Итак, насколько я могу судить, проблема в чем-то кроме неспособности фактически контролировать основной Sequencer
, так как он продолжает играть в кешированные данные.
К сожалению, у вас нет доступа к Sequencer
... Сиппи, да и MidiLatte
предоставить любую точку расширения для вас, чтобы изменить код.
HACK-решением было бы сделать то, что вы уже делаете, используя отражение для доступа к приватному полю, например...
!! Это взлом!
Если вы не можете изменить исходный источник MidiLatte
тогда у вас мало выбора, чтобы достичь того, чего вы, кажется, хотите достичь, вы должны иметь доступ к 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
уже использует свой собственный поток для воспроизведения нот, и параллелизм только усложнит в этом случае