前回では楽器のためのインタフェースを決めました。これを実装したクラスを作って音色のコントロールはこのクラスで行うようにします。楽器クラスが実装しなければいけないメソッドは以下のものでした。
public Played play(double note, double duration, double velocity);
楽器は何の曲を弾いているとかどんな曲調なのかということは考慮しません。あくまで指定の音の高さ、音の長さ、音の速さ(前回説明しました)で音を作ることのみ責任を持ちます。
楽器クラスの実装について
楽器の実装はここで作成する音楽作成のシステムの機能とは少し違うものです。というのも楽器は個人の好みによって自由に変更できるもので、変えたとしても全体の機能には何も影響を与えません。このため楽器の実装クラスは別なパッケージにして基本機能とは分けることにします。
基本の機能が以下のものでした。
package mocha.sound;
楽器用のパッケージは以下のものにします。
package mocha.sound.soundbank;
楽器の音色を考えます。ここではピアノのような音色を作ることにします。
ピアノはハンマーを叩くアタック音とその後弦が振動する長いリリースの音が混ざっています。その二つの音を表現することにします。
アタック音を音1、長いリリースを音2とします。
音1のデザイン
鋭いアタック音はエンベロープの調整によって行います。短い時間で一番大きい音になりすぐに消えるようにします。
ハンマーによる複雑な音を表現するためにはサイン波ではなくもう少し硬い音を選びます。ここでは三角波にします。また弱く弾く、つまり音の速さが遅いときにはあまり聞こえないようにします。デザインに使う時間の単位は1秒の1000分の1の単位であるミリ秒msにします。1ミリ秒は0.001秒になり整数で音の流れが扱えるようになります。
- 三角波を使う
- 無音からはじまり10ミリ秒で最高音になる。
- 音の大きさはベロシティに比例する。
- 150ミリ秒後に無音にもどる。
コードは以下の通りです。実装するときには秒単位に戻しています。
DoubleMap freq1 = new DoubleMap(Temperament.getFreq(note)); DoubleMap env1 = new DoubleMap(0); env1.putSecondValue(0.01, velocity); env1.putSecondValue(0.15, 0.0); played.addReadable(0, new OscillatorReader(new TriangleOscillator(), freq1, env1, 0.15));
音2のデザイン
では次にリリース音の方のデザインを考えます。立ち上がりは早くなく消えるまでの時間はゆっくり減衰、つまり音量が小さくなり続けます。ピアノは鍵盤から指をあげるとそれまで続いていた音はそこで途切れます。なので押しっぱなしで消えるまでの時間より早く音が終わるときにはその場所の音量を計算する必要があります。
また音1と違いあまり音の速さによる影響を受けないようにします。また音色も地味なサイン波にします。
- サイン波を使う
- 最大音量は音の速さの影響を半分だけ受ける
- 音の速さが0のときでも最大音量は0.3は出すようにする
- 最大音量には50ミリ秒で到達する
- 音の長さが3000ミリ秒(3秒)以上の場合は3000ミリ秒で無音になる
- 3000ミリ秒より満たない場合はその時点で減衰は終わり100ミリ秒後に無音になる。
コードは以下の通りです。
double top = 0.3 + velocity / 2.0; env2.putSecondValue(0.05, top); double real_duration; if(duration > 3){ env2.putSecondValue(3, 0); real_duration = 3; }else{ double end_volume = (1.0 - duration / 3.0) * top; env2.putSecondValue(duration, end_volume); env2.putSecondValue(duration + 0.1, 0); real_duration = duration + 0.1; } played.addReadable(0, new OscillatorReader(new SineOscillator(), freq2, env2, real_duration));
長くなってしまったので音出しは次回行います。