以前JavaでMIDIを使って音を鳴らしました。この時はリアルタイムで「音を出す」「待つ」「音を止める」というMIDIメッセージを音源に送信していましたが実際に曲を演奏する時にこの処理を全てのメッセージで記述するのは少々面倒です。
javax.sound.midiパッケージにはSequenceというクラスが用意されています。これはMIDIメッセージをどのタイミングで送信するかを管理します。
https://docs.oracle.com/javase/jp/8/docs/api/javax/sound/midi/Sequence.html
シーケンスについて
まずシーケンスは時間を管理するものなのでどのようなスケールで時間を管理するかを決める必要があります。
タイミングの指定
タイミングの指定については大きく二つに分かれます。
- 4分音符を基準とするもの
- 1秒を基準とするもの
楽曲のためにシーケンスを使う場合は前者の方が便利です。テンポの変更によって時間的な位置が自動で変更されるからです。
後者はSMPTE(米国映画テレビ技術者協会 Society of Motion Picture and Television Engineers)の規格で映像とリンクさせるためのものです。1秒間に何フレームの画像が使われるかによっていくつか種類が存在します。
解像度の指定
タイミングの指定で決めた基準をさらにどのくらい分割できるかを決めます。
4分音符基準であれば4分音符をどのくらい分割できるかを指定します。一般には480や960という値が使われるようです。両方とも3や5の倍数でもあるので3連符や5連符も指定することができます。480は32の倍数でもあるので128分音符、960は64の倍数なので256分音符まで表現できることになります。
1秒基準であれば指定のフレームをさらにどのくらい分割できるかを指定します。
トラック
トラックとはMIDIメッセージを保持する単位です。一つのシーケンスは一つ以上のトラックを持ちます。シーケンスを実際に動かすシーケンサーはトラックごとにミュート(再生しない)やソロ(他トラックを再生させない)の設定ができるのでトラック分けを任意にしておくと便利です。MIDIチャンネルごとつまり演奏する楽器ごとに分けるのが一般的です。
実装する
では以下のような音符を鳴らすようなシーケンスを作ります。一般にシーケンスでは最初の1小節は演奏音は設定しません。
シーケンスを作ります。PPQはPulses Per Quater noteの略で「4分音符あたりのパルス」を意味します。パルスはティックtickとも呼ばれシーケンスで指定する最小単位になります。次の480は解像度です。これで4分音符を480分割したものをティックとして使うことができます。
Sequence sequence = new Sequence(Sequence.PPQ, 480);
トラックを一つ作ります。このメソッドを読んだ数だけトラックが作られます。
Track track = sequence.createTrack();
トラックにはMidiEventというオブジェクトで設定します。これはMIDIメッセージと時間情報であるティックを保持するものです。例えば1小節目の頭であればティックは0ですが2小節目の頭は4分音符4つ分あとなのでティックは解像度480の4倍になります。
track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 60, 127), 480 * 4)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 60, 0), 480 * 5)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 64, 127), 480 * 5)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 64, 0), 480 * 6)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 67, 127), 480 * 6)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 67, 0), 480 * 7));
作ったシーケンスを鳴らすためシンセサイザーを呼び出します。とりあえずデフォルトのものを呼び出しオープンすることにします。
Synthesizer synthesizer = MidiSystem.getSynthesizer(); synthesizer.open();
シーケンスを演奏するためのシーケンサーを呼び出します。引数にfalseを指定しているのは出力先の音源を空の状態で取得するためです。こちらもオープンします。
Sequencer sequencer = MidiSystem.getSequencer(false); sequencer.open();
シーケンサーにシーケンスを設定します。
sequencer.setSequence(sequence);
シーケンサーの出力とシンセサイザーの入力をつなげます。これで音が出るように配線ができました。
sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());
startで演奏が開始されます。
sequencer.start();
ソース
以下今回のソースです。
import javax.sound.midi.MidiEvent; import javax.sound.midi.MidiSystem; import javax.sound.midi.Sequence; import javax.sound.midi.Sequencer; import javax.sound.midi.ShortMessage; import javax.sound.midi.Synthesizer; import javax.sound.midi.Track; public class SequenceTest { public static void main(String[] args) throws Exception { Sequence sequence = new Sequence(Sequence.PPQ, 480); Track track = sequence.createTrack(); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 60, 127), 480 * 4)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 60, 0), 480 * 5)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 64, 127), 480 * 5)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 64, 0), 480 * 6)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 67, 127), 480 * 6)); track.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, 67, 0), 480 * 7)); Synthesizer synthesizer = MidiSystem.getSynthesizer(); synthesizer.open(); Sequencer sequencer = MidiSystem.getSequencer(false); sequencer.open(); sequencer.setSequence(sequence); sequencer.getTransmitter().setReceiver(synthesizer.getReceiver()); System.out.println("start"); sequencer.start(); } }