ここまでに作ったFM音源とエンベロープ・ジェネレーターを使って楽器を作りたいと思います。FM音源を使ったシンセサイザーのヤマハのDX-7のエレクトリックピアノような音をイメージします。
楽器の設計
3系統の音を同時に鳴らします。それぞれキャリアーとモジュレーターを一つづつ組み合わせます。
一つはアタックの強い音にしてモジュレーターキャリアの14.14倍にします。整数倍にしないのはアタック音のピッチを不明瞭いしたいからです。
後の二つはリリースの長めの音にします。一つはモジュレーターもキャリアーも1倍にしてもう一つはモジュレーターを0.993倍、キャリアーを1.007倍と1倍から微妙にずらします。こうすることによってコーラスの効果を持たせ音に厚みをつけることができます。
いろいろ調節しながら結局は以下の通りにしました。表の見方を説明します。
#列はオシレーターの番号です。osc列出力先です。数字の場合はその番号のオシレーターを偏重するモジュレーター、Cの場合はキャリアです。
envは値@時間(ミリ秒ms)となっています。「何msの時にこれだけの値になる」という読み方です。releaseは音が鳴り止むまでの時間です。sensは感度でampは出力です。
ratioは指定された音高の周波数の何倍の周波数を発振させるかになります。
# | osc | env1 | env2 | env3 | rel | sens | amp | ratio |
---|---|---|---|---|---|---|---|---|
1 | C | 1.0@10 | 0.75@1000 | 0.0@2000 | 300 | 0.2 | 1 | 1 |
2 | 1 | 1.0@10 | 0.75@600 | 0.0@1500 | 200 | 0.7 | 1000 | 14.14 |
3 | C | 1.0@10 | 0.95@1500 | 0.0@3000 | 400 | 0.2 | 1 | 1 |
4 | 3 | 1.0@10 | 0.95@800 | 0.0@1800 | 400 | 0.6 | 1000 | 1 |
5 | C | 1.0@10 | 0.95@1500 | 0.0@3000 | 400 | 0.2 | 1 | 1.007 |
6 | 5 | 1.0@10 | 0.95@800 | 0.0@1800 | 400 | 0.6 | 1000 | 0.993 |
このような値を決めた上で実装します。
実装
楽器インタフェースのメソッド追加
楽器クラスが増えてくると何の楽器だかわからなくなるのでgetNameメソッドで名前を文字列で取得できるようにします。
package mocha.sound; public interface Instrumental { public Played play(double note, double duration, double velocity); public String getName(); }
楽器の実装:コンストラクタ
public class SecondInstrument implements Instrumental { EnvelopeGenerator eg1, eg2, eg3, eg4; public SecondInstrument(){
エンベロープジェネレーターは4つ定義します。エンベロープ3と5、4と6が同じ値なので共通で使うようにします。下の実装はオシレーター1を定義しているところです。DoubleMapに雛形ヘンベロープの設定をしてエンベロープ・ジェネレーターのインスタンスを作ります。コンストラクタの引数は雛形エンベロープ、リリース時間、感度、出力です。
DoubleMap env1 = new DoubleMap(0); env1.putSecondValue(0.01, 1); env1.putSecondValue(1.0, 0.75); env1.putSecondValue(2.0, 0.0); eg1 = new EnvelopeGenerator(env1, 0.3, 0.2, 1);
以下残りの3つも定義します。
DoubleMap env2 = new DoubleMap(0); env2.putSecondValue(0.01, 1); env2.putSecondValue(0.6, 0.75); env2.putSecondValue(1.5, 0.0); eg2 = new EnvelopeGenerator(env2, 0.2, 0.7, 1000); DoubleMap env3 = new DoubleMap(0); env3.putSecondValue(0.01, 1); env3.putSecondValue(1.5, 0.95); env3.putSecondValue(3.0, 0.0); eg3 = new EnvelopeGenerator(env3, 0.4, 0.2, 1); DoubleMap env4 = new DoubleMap(0); env4.putSecondValue(0.01, 1); env4.putSecondValue(0.8, 0.95); env4.putSecondValue(1.8, 0.0); eg4 = new EnvelopeGenerator(env4, 0.4, 0.6, 1000);
楽器の実装:演奏処理
演奏処理を実装します。まずは演奏音クラスPlayedのインスタンスを作りノート番号から周波数を求めます。
@Override public Played play(double note, double duration, double velocity) { Played played = new Played(); double freq = Temperament.getFreq(note);
オシレーター2を演奏させます。これはオシレーター1のモジュレーターになるものです。音の長さは引数で指定されたものにリリース時間を足しています。
DoubleMap freq2 = new DoubleMap(freq * 14.14); DoubleMap env2 = eg2.getEnvelope(duration, velocity); OscillatorReader osc2 = new OscillatorReader(new SineOscillator(), freq2, env2, duration + eg2.getRelease());
オシレーター1を演奏させます。これはキャリアーなので実際に音を出します。このときオシレーター2をモジュレーターとして引数に設定しています。
DoubleMap freq1 = new DoubleMap(freq); DoubleMap env1 = eg1.getEnvelope(duration, velocity); OscillatorReader osc1 = new OscillatorReader(new SineOscillator(), freq1, env1, duration + eg1.getRelease(), new SoundReadable[]{osc2}); played.addReadable(0, osc1);
オシレーター4を演奏させます。これはオシレーター3のモジュレーターです。
DoubleMap freq4 = new DoubleMap(freq); DoubleMap env4 = eg4.getEnvelope(duration, velocity); OscillatorReader osc4 = new OscillatorReader(new SineOscillator(), freq4, env4, duration + eg4.getRelease());
オシレーター3を演奏させます。これはキャリアーです。オシレーター4をモジュレーターとして引数に設定しています。
DoubleMap freq3 = new DoubleMap(freq); DoubleMap env3 = eg3.getEnvelope(duration, velocity); OscillatorReader osc3 = new OscillatorReader(new SineOscillator(), freq3, env3, duration + eg3.getRelease(), new SoundReadable[]{osc4}); played.addReadable(0, osc3);
オシレーター6を演奏させます。オシレーター5のモジュレーターです。オシレーター4と違うのは周波数に1.007をかけているところです。
DoubleMap freq6 = new DoubleMap(freq * 1.007); DoubleMap env6 = eg4.getEnvelope(duration, velocity); OscillatorReader osc6 = new OscillatorReader(new SineOscillator(), freq6, env6, duration + eg4.getRelease());
オシレーター5を演奏させます。これはキャリアーです。オシレーター6をモジュレーターとして引数に設定しています。周波数に0.993をかけています。
DoubleMap freq5 = new DoubleMap(freq * 0.993); DoubleMap env5 = eg3.getEnvelope(duration, velocity); OscillatorReader osc5 = new OscillatorReader(new SineOscillator(), freq5, env5, duration + eg3.getRelease(), new SoundReadable[]{osc6}); played.addReadable(0, osc5);
全て演奏処理が終わったので演奏音を返します。
return played; }
テスト演奏
前回のように試し演奏をします。中央ドの音を10回演奏します。音の速さは0.1から1.0まで変化するのでこのとき音色の変化を聞いてください。
TimeLine tl = new TimeLine(); Instrumental inst = new SecondInstrument(); for(int i = 0;i < 10;i++){ tl.addReadable(i, new Panner(inst.play(60, 0.5, ((double)i + 1.0) / 10.0), new DoubleMap(0.5))); } WavFileWriter.create(tl, new File("wav/secondinstrument.wav"));