JavaでのMIDIシーケンスの作り方メモ
JavaのプログラムでMIDIシーケンスを作りたかったんだけど、思いの外手こずったので作り方のメモです。
やりたいこと
使うAPI(というかクラス)とか
Sequencer | シーケンスの再生・停止等を担当するクラス。(プレーヤー) |
---|---|
Sequence | シーケンスを表すクラス。(楽譜) |
ShortMessage | ステータスバイトとデータバイトから構成されるMIDIメッセージのクラス。 |
MidiEvent | ShortMessageのインスタンスを基に作られるMIDIイベントのクラス。 |
コード例
簡単なMIDIイベント群によるシーケンスの生成・再生・停止を行うクラス。
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; public class JavaMidiSequence { private Sequencer sequencer; private Sequence sequence; public JavaMidiSequence() { try { sequencer = MidiSystem.getSequencer(); sequencer.open(); sequence = new Sequence(Sequence.PRQ, 480); sequence.createTrack(); sequencer.setSequence(sequence); } catch(Exception e) { e.printStackTrace(); } } /** * プログラムチェンジとノートオン/オフから構成されるMIDIイベントを追加 */ public void addNote(int channel, int progNumber, int noteNumber, int position, int duration) { try { // プログラムチェンジイベント生成 ShortMessage progChange = new ShortMessage(); progChange.setMessage(ShortMessage.PROGRAM_CHANGE, channel - 1, progNumber - 1, 0); MidiEvent progChangeEvent = new MidiEvent(progChange, position); // ノートオンイベント生成 ShortMessage noteOn = new ShortMessage(); noteOn.setMessage(ShortMessage.NOTE_ON, channel - 1, noteNumber, velocity); MidiEvent noteOnEvent = new MidiEvent(noteOn, position); // ノートオフイベント生成 ShortMessage noteOff = new ShortMessage(); noteOff.setMessage(ShortMessage.NOTE_OFF, channel - 1, noteNumber, 0); MidiEvent noteOffEvent = new MidiEvent(noteOff, position + duration); // イベント群をシーケンスへ追加 sequence.getTracks()[0].add(progChangeEvent); sequence.getTracks()[0].add(noteOnEvent); sequence.getTracks()[0].add(noteOffEvent); } catch(Exception e) { e.printStackTrace(); } } /** * シーケンスを再生 */ public void play() { try { sequencer.start(); } catch(Exception e) { e.printStackTrace(); } } /** * シーケンスを停止 */ public void stop() { if(sequencer.isRunning()) { sequencer.stop(); } sequencer.setTickPosition(0); } /** * シーケンサを閉じる */ public void close() { sequencer.close(); } }
ポイント
大事なのは以下のメソッド。各引数の意味は次の通り。
channel | 音色を割り振るチャンネル。1~16の整数値。 |
---|---|
progNumber | General MIDIの音色マップに示される楽器の番号。1~128の整数値。 |
noteNumber | 音程番号。0~127の整数値。 |
position | ノートオン(ノートの発音)が発生する時刻。整数値。 |
duration | ノートの発音時間長。整数値。 |
/** * プログラムチェンジとノートオン/オフから構成されるMIDIイベントを追加 */ public void addNote(int channel, int progNumber, int noteNumber, int position, int duration) { try { // プログラムチェンジイベント生成 ShortMessage progChange = new ShortMessage(); progChange.setMessage(ShortMessage.PROGRAM_CHANGE, channel - 1, progNumber - 1, 0); MidiEvent progChangeEvent = new MidiEvent(progChange, position); // ノートオンイベント生成 ShortMessage noteOn = new ShortMessage(); noteOn.setMessage(ShortMessage.NOTE_ON, channel - 1, noteNumber, velocity); MidiEvent noteOnEvent = new MidiEvent(noteOn, position); // ノートオフイベント生成 ShortMessage noteOff = new ShortMessage(); noteOff.setMessage(ShortMessage.NOTE_OFF, channel - 1, noteNumber, 0); MidiEvent noteOffEvent = new MidiEvent(noteOff, position + duration); // イベント群をシーケンスへ追加 sequence.getTracks()[0].add(progChangeEvent); sequence.getTracks()[0].add(noteOnEvent); sequence.getTracks()[0].add(noteOffEvent); } catch(Exception e) { e.printStackTrace(); } }
最初、複数楽器の割り当ては「トラック」に対して行うんだと勘違いしてたんですけど、どうやら「トラック」ではなく「チャンネル」に割り当てるのが正解らしいですね。
プログラムチェンジイベントを生成する際に、チャンネル番号に対応する任意の楽器番号を指定すれば良いっぽい。
ドラムスはチャンネル番号に9を指定すれば楽器番号はどうでもいいらしい。
チャンネル番号と楽器番号を指定したらあとはガシガシとノートオン/オフイベントを追加しまくる。
(例) ピアノ、オルガン、エレキギター、ベース、ドラムスを鳴らしたい場合 progChange.setMessage(ShortMessage.PROGRAM_CHANGE, 0, 0, 0); // Acoustic Piano progChange.setMessage(ShortMessage.PROGRAM_CHANGE, 1, 16, 0); // Drawbar Organ progChange.setMessage(ShortMessage.PROGRAM_CHANGE, 2, 30, 0); // Distortion Guitar progChange.setMessage(ShortMessage.PROGRAM_CHANGE, 3, 33, 0); // Electric Bass (finger) progChange.setMessage(ShortMessage.PROGRAM_CHANGE, 9, 0, 0); // Drums