プログラムで実装する対象の曲を調査します。調査することによって楽曲の構造を把握してどのように実装すればよいか検討することができます。
対象曲はJ. S. バッハの平均律クラヴィーア曲集第1の一番最初のプレリュードです。本来はピアノの部分だけなのですが歌やチェロのメロディが載っているものも知られています。こちらは「アヴェ・マリア」として知られていますが、のちのグノーGounodというフランスの作曲家が原曲を伴奏として別のメロディを乗せたものです。今回はこのメロディは含めません。
楽曲の調査
以下は最初の数小節の抜粋です。
1小節の中に同じパターンを2回配置し、それを30小節あまり続けています。一つのパターンは5つの音で構成されて下2つは左手で演奏されるように下向きの棒で表現されています。そしてこの2つの音はスラーで引き伸ばされパターンの間ずっと鳴るようになっています。一方上の3つは16分音符で短く切られ2回繰り返して演奏されています。右2+左3+左3で合計8音の音を1パターンとして途切れなく続いていくようになっています。
今回はお試しとしてこの最初の4小節を演奏するプログラムを作ります。
実装内容について
一つの楽器で演奏されていますが左手で弾く下の2音と右手で弾く3音は音の出し方を変えたいところです。この違いは音の速さvelocityで調節することにします。
- 下の2音は音の速さを遅く(弱い音で)弾いて音の長さは長い
- 上の3音は音の速さを早く(強い音で)弾いて音の長さは短い
使う音を採集します。一小節で5音づつの配列を作っていきます。一番最初の音は中央のドでノート番号は60です。四小節分のデータは配列の配列になります。
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}, };
タイムラインと楽器のインスタンスを作ります。楽器は前回作ったFirstInstrumentを使います。実際の音色はピアノには程遠いですがバッハの時代にはそもそもピアノという楽器は開発途上でしたので気にすることはありません。
TimeLine tl = new TimeLine(); Instrumental inst = new FirstInstrument();
音を配置するための変数timeと1音の秒数durationを0.25秒で設定します。
double time = 0; double duration = 0.25;
一小節で2パターン、四小節では8パターン繰り返します。その時の5音セットの配列へのインデックスは2で割って求めます。
for (int i = 0; i < notes.length * 2; i++) { int index = i / 2;
最初の音を演奏させます。長さは1音の7.6倍にしています。これは1パターンの8番目の音がdurationの0.6倍でおわるためその終了時間に合わせています。変数timeはdurationの分後にずらします。音の速さは0.7と中くらいの値にしておきます。
tl.addReadable(time, new Panner(inst.play(notes[index][0], duration * 7.6, 0.7), new DoubleMap(0.75))); time += duration;
2番目の音も同様にします。ただしduration一つ分ずれたので8番目の音の終わりに合わせるにはdurationの6.6倍に設定します。音の速さは1番目よりさらに遅い0.5にしました。下二つの音は気持ち左0.75にパニングしています。
tl.addReadable(time, new Panner(inst.play(notes[index][1], duration * 6.6, 0.5), new DoubleMap(0.75))); time += duration;
3番目以降も同様です。音の長さは全てdurationの0.6倍にしました。音の速さは下から上の音に行くほど早く弾くようにしています。右手の音は少し右目0.25にパニングしています。
tl.addReadable(time, new Panner(inst.play(notes[index][2], duration * 0.6, 0.6), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][3], duration * 0.6, 0.8), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][4], duration * 0.6, 1.0), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][2], duration * 0.6, 0.6), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][3], duration * 0.6, 0.8), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][4], duration * 0.6, 1.0), new DoubleMap(0.25))); time += duration;
実行する
実行結果です。聞きやすくするため元の音源に少しだけエフェクトをかけています。
ソース
今回作ったソースです。
import java.io.File; import java.io.IOException; import mocha.sound.DoubleMap; import mocha.sound.Instrumental; import mocha.sound.Panner; import mocha.sound.TimeLine; import mocha.sound.WavFileWriter; import mocha.sound.soundbank.FirstInstrument; public class InstrumentTest { public static void main(String[] arg) throws IOException { 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},}; TimeLine tl = new TimeLine(); Instrumental inst = new FirstInstrument(); double time = 0; double duration = 0.25; for (int i = 0; i < notes.length * 2; i++) { int index = i / 2; tl.addReadable(time, new Panner(inst.play(notes[index][0], duration * 7.6, 0.7), new DoubleMap(0.75))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][1], duration * 6.6, 0.5), new DoubleMap(0.75))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][2], duration * 0.6, 0.6), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][3], duration * 0.6, 0.8), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][4], duration * 0.6, 1.0), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][2], duration * 0.6, 0.6), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][3], duration * 0.6, 0.8), new DoubleMap(0.25))); time += duration; tl.addReadable(time, new Panner(inst.play(notes[index][4], duration * 0.6, 1.0), new DoubleMap(0.25))); time += duration; System.out.print("."); } System.out.println("."); WavFileWriter.create(tl, new File("wav/clavier_sample.wav")); } }