Когда заканчивается последовательность из midi.Заканчивается секвенсор?

#java #midi #duration #javax.sound.midi

#java #midi #Продолжительность #javax.sound.midi

Вопрос:

Имея этот простой код, который работает (но просто никогда не заканчивается, пока я не завершу его с помощью ^-c):

 import javax.sound.midi.*;

public class Foo {

    public void play(int instrument, int note) {
        try {
            Sequencer player = MidiSystem.getSequencer();
            player.open();

            Sequence seq = new Sequence(Sequence.PPQ, 4);
            Track track = seq.createTrack();

            ShortMessage first = new ShortMessage();
            first.setMessage(192, 1, instrument, 0);
            MidiEvent changeInstrument = new MidiEvent(first, 1);
            track.add(changeInstrument);

            ShortMessage a = new ShortMessage();
            a.setMessage(144, 1, note, 100);
            MidiEvent noteOn = new MidiEvent(a, 1); //fired on "tick 1"
            track.add(noteOn);

            ShortMessage b = new ShortMessage();
            b.setMessage(128, 1, note, 100);
            MidiEvent noteOff = new MidiEvent(b, 8); //fired on "tick 8"
            track.add(noteOff);

            player.setSequence(seq);
            player.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Foo foo = new Foo();
        foo.play(0, 42); 
    }
}
 

Запускается noteOn MidiEvent 1 tick (второй аргумент MidiEvent конструктора). Запускается noteOff MidiEvent 8 tick , что означает. должно быть 7 тиков. Но я никогда не вижу конца (конец программы). При запуске из cmd приглашение никогда не возвращается обратно, если я не принудил процесс завершить (с <ctrl-c> помощью ). Почему это? Когда заканчивается секвенсор и, следовательно, программа?

РЕДАКТИРОВАТЬ: это может быть вызвано открытием секвенсора, но не его закрытием. Поэтому я хотел закрыть ее в конце, но она не может сразу следовать за player.start() инструкцией, иначе она немедленно заканчивается. Поэтому должна быть некоторая задержка. Хотя это неуклюжее решение

         ...
        player.setSequence(seq);
        player.start();
        TimeUnit.SECONDS.sleep(3);
        player.close();
 

После чего возвращается приглашение. Но есть ли лучшее решение?

Ответ №1:

Midi-последовательность завершается, когда Sequencer#isRunning() равен false . Я думаю, вам следует закрыть объект sequencer перед запуском другой последовательности. Объявление объекта Sequence в качестве члена класса может дать немного больше гибкости:

 // Declare Sequencer as a class member variable and initialize to null.
javax.sound.midi.Sequencer sequencer = null;

public void playMidi(String midiFilePath) {
    // Prevent a new Midi audio file is one is already playing.
    if (sequencer != null) {
        // If sequncer is running get outta here.
        if (sequencer.isRunning()) { 
            return; 
        }
        // Otherwise close the sequencer.
        else { 
            sequencer.close(); 
        }
    }
    // Play the Midi file in its own thread.
    Thread newThread = new Thread(() -> {
        try {
            // Obtains the default Sequencer connected to the System's default device.
            sequencer = javax.sound.midi.MidiSystem.getSequencer();

            // Opens the device, indicating that it should now acquire any
            // system resources it needs and becomes operational.
            if (sequencer.isRunning()) {
                return;
            }
            sequencer.open();

            // Stream in a Midi file
            java.io.InputStream is = new java.io.BufferedInputStream(new java.io.FileInputStream(new File(midiFilePath)));

            // Sets the current sequence on which the sequencer operates.
            // The stream must point to MIDI file data.
            sequencer.setSequence(is);
            is.close();
            // Starts playback of the MIDI data in the currently loaded sequence.
            sequencer.start();
        }
        catch (MidiUnavailableException | IOException | InvalidMidiDataException ex) {
            Logger.getLogger("playMidi() Method Error!").log(Level.SEVERE, null, ex);
        }
    });
    newThread.start();
}
 

Комментарии:

1. Извините за -1, но это действительно не отвечает на вопрос. Также нет необходимости запускать другой поток, sequencer.start() позаботится об этом внутри.

Ответ №2:

Вам нужно добавить MetaEventListener, чтобы перехватить событие «конец последовательности»:

   player.addMetaEventListener(metaEvent ->
        {
            if (metaEvent.getType() == 47) // end of sequence
            {
                player.stop();  // Not mandatory according to API doc, but better to have it

                // This is called from the Sequencer thread, NOT from the EDT
                // So we need SwingUtilities if we want to update the UI
                Runnable doRun= new Runnable()
                {
                    @Override
                    public void run()
                    {
                        doSomethingAfterStop();
                    }
                };
                SwingUtilities.invokeLater(doRun);
            }
        });
 

Если вы просто хотите выйти из своей программы, когда последовательность закончится, просто вызовите напрямую System.exit() в прослушивателе.

Обратите внимание, что если вы используете Sequencer.setLoopCount(x) с x> 0, прослушиватель конца последовательности никогда не вызывается, вам понадобится кнопка пользовательского интерфейса (или таймер) для запуска вызова Sequencer.stop() .

Есть несколько хитростей, которые нужно знать при использовании Java sequencer, если вам нужен пример кода, ознакомьтесь с моим приложением JJazzLab-X на GitHub, особенно MusicController.java в MusicController модуле.