前回MIDIで音を鳴らしました。ここでは音を開始するコマンドと音を終了させるコマンドを時間差でシンセサイザーに送ることによって一定時間音を鳴らしていました。
起こりうる問題
システムというのは常に障害がつきものです。全ての処理が問題なくうまくいくに越したことはないのですが、うまくいかない場合も想定しておく必要があります。
例えば音を開始するコマンドを送ったあと何か問題が発生して音を終了させるコマンドが送れない場合はどうなるでしょうか?
例えばピアノのような音が減衰するものであれば時間が経てば音は最小になっていくのですがオルガンのような持続音の場合はいつまでもなり続けることになります。
実験をします。以下のような処理を行います。
- シンセサイザーを起動し送信先を取得する
- プログラムチェンジつまり音色の変更でオルガンの音を指定(GMで#16または0x10)
- ドの音を開始する。
- 10,000ミリ秒つまり10秒間処理を待つ
ここではノートオフつまり音の終了をしていません。
Synthesizer synthesizer = MidiSystem.getSynthesizer(); synthesizer.open(); Receiver receiver = synthesizer.getReceiver(); receiver.send(new ShortMessage(ShortMessage.PROGRAM_CHANGE, 0, 0x10, 0x00), -1); receiver.send(new ShortMessage(ShortMessage.NOTE_ON, 0, 0x3C, 0x7F), -1); Thread.sleep(10000);
実行するとオルガンの音が10秒間なり続けることになります。デフォルトのシンセサイザーはJava内臓のものなのでJavaのプロセスが終了するとシンセサイザーも終了します。もしこれが外付けの実物のシンセサイザーである場合はJavaのプロセスが終了してもシンセサイザーは生き続けるので音は永遠になり続けることになります。
Javaでシンセサイザーのリセットをするコマンド
実物のシンセサイザーにはパニックボタンのようなリセットボタンが付いているものもありますがMIDIの仕様で音をオフにしたり設定を初期化するメッセージがあるのでこちらを使うことにします。
ノートオンやノートオフと同じようにシンセサイザーを設定を変更するコマンドがあります。これをコントロールチェンジと言います。
コマンド | 定数名 | 10進 | 16進 |
---|---|---|---|
ノートオフ | NOTE_OFF | 128-143 | 0x80-0x8F |
ノートオン | NOTE_ON | 144-159 | 0x90-0x9F |
コントロールチェンジ | CONTROL_CHANGE | 176-191 | 0xB0-0xBF |
このコントロールチェンジも2つのデータバイトを指定できますがその一つ目がコントロール番号といって「何をコントロールするか」の指定になります。
リセットに関係するものは以下の三つです。
オールノートオフ #123 (0x7B)
指定のチャンネルでノートオンの状態になっているものを全てノートオフにします。全てが対象なのでノート番号の指定は必要ありません。
オールサウンドオフ #120 (0x78)
指定のチェンネルで鳴っている音を全て消します。例えばピアノは鍵盤を離してもその瞬間に音が消えるわけではなくわずかに時間がかかります。これをリリース時間と言います。シンセサイザーの場合はこのリリース時間を自由に調節できるためとても長い時間かけて音が消えるように設定することも可能です。オールノートオフの場合ノートオフにしてもこのリリース時間待たないと本当に音が鳴らない状態にはできませんがオールサウンドオフはオールノートオフの処理を行ったあとさらにリリース時間中に鳴っている音もその場で消します。
リセットオールコントローラー #121 (0x79)
指定のコントローラを全てリセットします。
リセットの実装
では先ほどのコードを改良します。今度は5秒間待った後オールノートオフを行います。その後5秒まってプロセスを終了します。
Synthesizer synthesizer = MidiSystem.getSynthesizer(); synthesizer.open(); Receiver receiver = synthesizer.getReceiver(); receiver.send(new ShortMessage(ShortMessage.PROGRAM_CHANGE, 0, 0x10, 0x00), -1); receiver.send(new ShortMessage(ShortMessage.NOTE_ON, 0, 0x3C, 0x7F), -1); Thread.sleep(5000); System.out.println("ALL NOTE OFF"); receiver.send(new ShortMessage(ShortMessage.CONTROL_CHANGE, 0, 0x78, 0x00), -1); Thread.sleep(5000); System.out.println("END OF PROCESS");
今まで10秒間なりっぱなしだったものが5秒後に音が止まって5秒間無音の時間ができました。
リセットユーティリティ
最後にここので経験をメソッド化しておきたいと思います。何かの時にシンセサイザーをリセットするメソッドです。
ここではオールノートオフとオールサウンドオフの両方を呼び出していますがこれはシンセサイザーによっては片方しか実装されていない場合があるからです。なのである意味では「駄目元」でもあります。
リセットオールコントローラーも含めこの3つのコマンドは二つ目のデータバイトは必要としていないため0にしています。
また16チャンネルそれぞれにこのコマンドを送らなくてはいけないのでfor文で繰り返しをしています。
import javax.sound.midi.InvalidMidiDataException; import javax.sound.midi.Receiver; import javax.sound.midi.ShortMessage;
public static void reset(Receiver receiver) { try { for (int i = 0; i < 16; i++) { receiver.send(new ShortMessage(ShortMessage.CONTROL_CHANGE, i, 0x78, 0), -1); receiver.send(new ShortMessage(ShortMessage.CONTROL_CHANGE, i, 0x7B, 0), -1); receiver.send(new ShortMessage(ShortMessage.CONTROL_CHANGE, i, 0x79, 0), -1); } } catch (InvalidMidiDataException ex) { throw new IllegalStateException(ex); } }