シーケンサーを使ってシーケンスを演奏させるプログラムを書きましたが現在の実装では強制終了しない限り処理が終了しないままになってしまいます。これをなんとかしたいと思います。
問題点
現在は処理の最後でシーケンサーを開始しますがこれを終了していません。このためシーケンサーはそのままプロセス上に残ったままになります。
System.out.println("start");
sequencer.start();
処理を終了するには以下のようにシーケンサー自体と設定されているレシーバーを終了する必要があります。
sequencer.close();
for(Receiver receiver:sequencer.getReceivers()){
receiver.close();
}
しかしシーケンサーは別のスレッドで動くため上記のstartの下にすぐ終了処理を書いてしまうと演奏前にシーケンサーが終了されてしまいます。これはこれで問題です。
解決方法
シーケンサーには以下のようにメタイベントのリスナーを設定することができます。リスナーとは「聴く人」の意味ですがJavaにおいては何かイベントがあった時にそのことを知ることができるクラスのことを意味します。
Sequencer.addMetaEventListener(MetaEventListener listener)
リスナーはインタフェースなのでMetaEventListenerは以下のメソッドを実装する必要があります。
Sequencerが処理中のSequenceからMetaMessageを検出し、処理した場合に呼び出されます。
void meta(MetaMessage meta)
https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/midi/MetaEventListener.html
実装内容
では実装します。クラスの宣言とコンストラクターです。あとでsleep処理を行うためThreadを継承しておきます。コンストラクターではシーケンサーを参照できるように引数に取ります。
public class EndOfTrackListner extends Thread implements MetaEventListener {
private final Sequencer sequencer;
public EndOfTrackListner(Sequencer sequencer) {
this.sequencer = sequencer;
}
以下はmetaメソッドの内容です。EndOfTrackメッセージは47、16進数で0x2fなのでこれ以外は無視します。ちなみにこのメッセージは各トラックの最後に一つずつ設定されているのですがデフォルトのシーケンサーを使う限りシーケンスの全てのイベントを処理した最後に1回だけ送られてくるようです。
if (message.getType() != 0x2f) {
return;
}
これ以降に終了処理をします。すぐに処理を開始するとリリース音がある場合それをぶつ切りしてしまうため一秒ほど待ちます。
try {
sleep(1000);
} catch (InterruptedException e) {
System.out.println("failed to sleep");
}
終了処理をします。
sequencer.close();
for(Receiver receiver:sequencer.getReceivers()){
receiver.close();
}
今度はシーケンサーの開始時にこのリスナーを設定します。start前にリスナーを追加する処理を追加します。
sequencer.addMetaEventListener(new EndOfTrackListner(sequencer));
System.out.println("start");
sequencer.start();
これで実行すると曲の終了時にJavaの処理も終了するようになります。
ソース
リスナーのソースです。
package mocha.sound.midi;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.Receiver;
import javax.sound.midi.Sequencer;
public class EndOfTrackListner extends Thread implements MetaEventListener {
private final Sequencer sequencer;
public EndOfTrackListner(Sequencer sequencer) {
this.sequencer = sequencer;
}
@Override
public void meta(MetaMessage message) {
if (message.getType() != 0x2f) {
return;
}
try {
sleep(1000);
} catch (InterruptedException e) {
System.out.println("failed to sleep");
}
sequencer.close();
for (Receiver receiver : sequencer.getReceivers()) {
receiver.close();
}
}
}