Shun40の日々のあれこれをまとめるやつ

DTMとかプログラミングとかその他諸々の話をするブログです。

JavaでのMIDIシーケンスの作り方メモ

JavaのプログラムでMIDIシーケンスを作りたかったんだけど、思いの外手こずったので作り方のメモです。

やりたいこと

  • Javaで用意されているMIDI関連APIを用いて、「複数の楽器パートから構成される」MIDIシーケンスを作る。
  • ここで言うMIDIシーケンスっていうのは「ノートオン/オフ」「プログラムチェンジ(音色変更)」等の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

最後に

MIDI知識のガバガバ加減が露呈してしまったので今度MIDI検定でも受験してきます。
あとはてな記法シンタックスハイライト記法いいゾ~これ。