ここまで小節、拍、テンポなどかなり複雑な計算が必要な処理を実装してきました。正直なところこれから音楽を演奏させるにあたり、このあたりの計算に心を煩わせることなく楽曲の内容に集中できるようにまとめたクラスを作っておきます。
小節クラス
拍子を管理するTimeSignクラスとテンポを管理するTempoMapクラスを内部で持ち小節と拍を入れると開始時間を秒単位で返すようにします。
package mocha.sound; public class Measures { TimeSign timeSign; TempoMap tempoMap; public Measures(TimeSign TimeSign, TempoMap tempoMap) { this.timeSign = TimeSign; this.tempoMap = tempoMap; tempoMap.updateTime(TimeSign); } public double getTime(int measure, double beat) { double totalBeat = timeSign.getTotalBeat(measure, beat); return tempoMap.getTime(totalBeat); } }
演奏者クラスの作成
このクラスがあっても一つの音を演奏するには以下のことをやらなければなりません。
- 小節と拍を計算し、それらの値から開始時間を計算する
- 小節と拍を計算し、それらの値から終了時間を計算する
- 終了時間ー開始時間で音の長さを計算する
- 開始時間、音の高さ、音の長さ、音の速さ、パンから演奏させる
楽譜は一つの音符を演奏すると次の音符の開始はかならず演奏された音符分後にきます。このような実装をすれば小節と拍をいちいち計算する必要はありません。必要な情報だけ与えればあとは楽器を使って演奏の処理をしてくれるクラスを作ります。
演奏者のクラスPlayerは内部に小節情報と楽器オブジェクトを保持するようにします。自分で現在の小節と拍を管理して演奏結果はPlayedオブジェクトに保持します。
package mocha.sound; public class Player { Played played; Measures measures; Instrumental inst; int measure; double beat; public Player(Measures measures, Instrumental inst){ this.measures = measures; this.inst = inst; played = new Played(); measure = 0; beat = 0; }
演奏処理のメソッドです。入力には音の高さnote、音の長さduration、長さの割合ratio、音の速さを指定します。durationは音符の論理的な長さです。実際には長く伸ばすテヌートや短かく切るようなスタッカートなどがありますのでそれはratioで指定します。1であればdurationと同じ、音符の長さ通り演奏しますし短く切りたければ1より小さい数字にします。
内部では開始時間と実際の長さを計算して演奏処理をします。durationの分拍を移動しますので次にこのメソッドを呼び出すときには次の開始位置に移動している状態になっています。
public void play(double note, double duration, double ratio, double velocity){ double time = measures.getTime(measure, beat); double realDuration = measures.getTime(measure, beat + duration * ratio) - time; played.addReadable(time, inst.play(note, realDuration, velocity)); beat += duration; }
次の小節に移動するときのメソッドです。拍は0にリセットされます。
public void nextMeasure(){ measure++; beat = 0; }
明示的に拍の位置を指定したいときに呼び出すメソッドです。
public void setBeat(double beat){ this.beat = beat; }
演奏処理が一通り終わったときに演奏結果を取得したいときに呼ぶメソッドです。
public Played getPlayed(){ return played; }
演奏処理
以上のクラスを使って演奏処理を書き直しました。前回のものに比べると楽曲に関係する処理がほとんどになってきています。右と左で別のPlayerインスタンスを作りました。あとでパン設定するために分けておく必要があるからです。
double[][] notes = new double[][]{ new double[]{60, 64, 67, 72, 76}, new double[]{60, 62, 69, 74, 77}, new double[]{59, 62, 67, 74, 77}, new double[]{60, 64, 67, 72, 76},}; Measures Played = new Measures(new TimeSign(4), new TempoMap(60)); Instrumental inst = new FirstInstrument(); Player rightHand = new Player(Played, inst); Player leftHand = new Player(Played, inst); for (int i = 0; i < notes.length; i++) { leftHand.play(notes[i][0], 0.25, 7.6, 0.7); leftHand.play(notes[i][1], 0.25, 6.6, 0.5); rightHand.setBeat(0.5); rightHand.play(notes[i][2], 0.25, 0.6, 0.6); rightHand.play(notes[i][3], 0.25, 0.6, 0.8); rightHand.play(notes[i][4], 0.25, 0.6, 1.0); rightHand.play(notes[i][2], 0.25, 0.6, 0.6); rightHand.play(notes[i][3], 0.25, 0.6, 0.8); rightHand.play(notes[i][4], 0.25, 0.6, 1.0); leftHand.setBeat(2); leftHand.play(notes[i][0], 0.25, 7.6, 0.7); leftHand.play(notes[i][1], 0.25, 6.6, 0.5); rightHand.setBeat(2.5); rightHand.play(notes[i][2], 0.25, 0.6, 0.6); rightHand.play(notes[i][3], 0.25, 0.6, 0.8); rightHand.play(notes[i][4], 0.25, 0.6, 1.0); rightHand.play(notes[i][2], 0.25, 0.6, 0.6); rightHand.play(notes[i][3], 0.25, 0.6, 0.8); rightHand.play(notes[i][4], 0.25, 0.6, 1.0); leftHand.nextMeasure(); rightHand.nextMeasure(); } TimeLine tl = new TimeLine(); tl.addReadable(0, new Panner(leftHand.getPlayed(), new DoubleMap(0.75))); tl.addReadable(0, new Panner(rightHand.getPlayed(), new DoubleMap(0.25))); WavFileWriter.create(tl, new File("wav/clavier_sample.wav"));
ソース
Playerのソースを載せておきます。
package mocha.sound; public class Player { Played played; Measures measures; Instrumental inst; int measure; double beat; public Player(Measures measures, Instrumental inst) { this.measures = measures; this.inst = inst; played = new Played(); measure = 0; beat = 0; } public void play(double note, double duration, double ratio, double velocity) { double time = measures.getTime(measure, beat); double realDuration = measures.getTime(measure, beat + duration * ratio) - time; played.addReadable(time, inst.play(note, realDuration, velocity)); beat += duration; } public void nextMeasure() { measure++; beat = 0; } public void setBeat(double beat) { this.beat = beat; } public Played getPlayed() { return played; } }