バッハのチェロ組曲のシーケンス作りをしましたがまだコードが煩雑であるのでツールをさらに追加していきたいと思います。結果として楽曲作成に集中できるコーディングができるようにします。
シーケンスエディターの作成
Javaの実装では一つのシーケンスに複数のトラックを作成できるようになっています。ここまででトラックの編集ツールは作りましたがシーケンスについてはありません。このためシーケンスの初期化に様々な記述が必要になっています。以下に抜き書きしました。
解像度を指定してシーケンスを初期化、コンダクタートラックにテンポを設定、拍子情報を四拍子で作成しています。
int resolution = 480; Sequence sequence = new Sequence(Sequence.PPQ, resolution); Track track; track = sequence.createTrack(); double tempo = 65; track.add(new MidiEvent(getTempoMessage(tempo), 0)); MidiTimeSign timeSign = new MidiTimeSign(resolution, 4.0);
またシーケンス中でテンポを設定するところもコードが冗長です。
track.add(new MidiEvent(getTempoMessage(tempo * 0.78), timeSign.getTick(m, 0)));
このあたりの記述をスッキリできるようにしたいと思います。だいたいの方針としては以下のように考えています。
- シーケンスを直接触らずエディタークラスを介して操作する(トラックエディターと同じ)
- 拍子情報やトラックの初期化はエディタークラスで行う
- テンポ設定などコンダクタートラックへの操作はエディタークラスを介して行う
実装
まずクラスの宣言とフィールド変数です。拍子情報とシーケンスと複数のトラックを保持します。トラックはコンダクタートラックと演奏用のトラックと分けてあります。演奏用のトラックはトラックエディターでラップしたものを保持します。解像度は480固定で定数にしておきます。
public class SequenceEditor {
public static final int RESOLUTION = 480;
MidiTimeSign timeSign;
Sequence sequence;
Track conductor;
TrackEditor[] trackEditors;
コンストラクターでは演奏用のトラックの数と初期用の拍子、初期用のテンポを取るようにします。
拍子情報のインスタンスを生成しシーケンスを作成します。コンダクタートラックを作成したあと指定のトラック数だけトラックを作成してトラックエディターにラップします。最後にテンポを設定します。
今の所は一つのトラックが一つのチャンネルに対応するようにしてありますが将来的に必要であれば見直すようにします。チャンネルは16までなのでトラック数を17以上にすると例外を投げるようになっています。
public SequenceEditor(int trackLength, double initialBeat, double tempo){
if(trackLength > 16){
throw new IllegalArgumentException("max of trackLength is 16");
}
this.timeSign = new MidiTimeSign(RESOLUTION, initialBeat);
try {
sequence = new Sequence(Sequence.PPQ, RESOLUTION);
} catch (InvalidMidiDataException ex) {
throw new IllegalStateException(ex);
}
conductor = sequence.createTrack();
trackEditors = new TrackEditor[trackLength];
for(int i = 0;i < trackLength;i++){
trackEditors[i] = new TrackEditor(timeSign, sequence.createTrack(), i);
}
setTempo(0, 0, 65);
}
getterメソッドを作ります。
public TrackEditor getTrackEditor(int index){
return trackEditors[index];
}
public Sequence getSequence(){
return sequence;
}
public MidiTimeSign getTimeSign(){
return timeSign;
}
コンダクタートラックに設定するメソッドを追加します。今の所はテンポの設定のみです。コンストラクターでも呼び出しているのでサブクラスが変更することのないようにfinalにしておきます。
public final void setTempo(int measure, double beat, double bpm) {
long tick = timeSign.getTick(measure, beat);
long mpq = Math.round(60000000d / bpm);
byte[] data = new byte[3];
data[0] = new Long(mpq / 0x10000).byteValue();
data[1] = new Long((mpq / 0x100) % 0x100).byteValue();
data[2] = new Long(mpq % 0x100).byteValue();
try {
conductor.add(new MidiEvent(new MetaMessage(0x51, data, data.length), tick));
} catch (InvalidMidiDataException ex) {
throw new IllegalStateException(ex);
}
}
このトラックエディターを使う形でシーケンス編集のコードを修正しました。演奏される内容は同じですが記述が前よりスッキリしました。次回は演奏情報を記述しているところをもう少し簡略にしたいと思います。
double tempo = 65;
SequenceEditor se = new SequenceEditor(1, 4, tempo);
TrackEditor te1 = se.getTrackEditor(0);
te1.setProgram(0, 0, 0, 0, 42);
int[][] notes = new int[][]{
new int[]{43, 50, 59, 57, 50},
new int[]{43, 52, 60, 59, 52},
new int[]{43, 54, 60, 59, 54},
new int[]{43, 55, 59, 57, 54}
};
for (int n = 0; n < 4; n++) {
for (int i = 0; i < notes.length; i++) {
int m = n * notes.length + i;
se.setTempo(m, 0, tempo * 0.78);
te1.play(m, 0.00, notes[i][0], 100, 0.3);
se.setTempo(m, 0.25, tempo * 0.91);
te1.play(m, 0.25, notes[i][1], 90, 0.3);
se.setTempo(m, 0.5, tempo);
te1.play(m, 0.50, notes[i][2], 90, 0.28);
te1.play(m, 0.75, notes[i][3], 80, 0.28);
se.setTempo(m, 1, tempo * 0.96);
te1.play(m, 1.00, notes[i][2], 90, 0.13);
te1.play(m, 1.25, notes[i][1], 70, 0.18);
te1.play(m, 1.50, notes[i][2], 70, 0.15);
te1.play(m, 1.75, notes[i][1], 60, 0.15);
se.setTempo(m, 2, tempo * 0.78);
te1.play(m, 2.00, notes[i][0], 100, 0.3);
se.setTempo(m, 2.25, tempo * 0.91);
te1.play(m, 2.25, notes[i][1], 90, 0.3);
se.setTempo(m, 2.5, tempo);
te1.play(m, 2.50, notes[i][2], 90, 0.28);
te1.play(m, 2.75, notes[i][3], 80, 0.28);
se.setTempo(m, 3, tempo * 0.96);
te1.play(m, 3.00, notes[i][2], 90, 0.13);
te1.play(m, 3.25, notes[i][1], 70, 0.18);
te1.play(m, 3.50, notes[i][2], 70, 0.15);
te1.play(m, 3.75, notes[i][4], 60, 0.15);
}
}
Synthesizer synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
Receiver receiver = synthesizer.getReceiver();
//MidiDevice aria = MidiUtil.getMidiDevices("ARIA", true)[0];
//aria.open();
//Receiver receiver = aria.getReceiver();
playMidi(se.getSequence(), receiver);