エンベロープ・ジェネレーターとはシンセサイザーの音量の変化をコントロールするエンベロープを作成するものです。Javaでこれを実装して楽器クラスから演奏音を簡単に作れるようにしたいと思います。
エンベロープの最適化
まず今までエンベロープとして使っていたDoubleMapを最適化したいと思います。今まではあるサンプル位置での値を毎回計算していました。計算する内容はざっと以下の通りでした。
- 指定された位置の前後の設定値を取得する。
- 前後のサンプル位置と設定値を元に指定された位置の値を計算する。
演奏音作成時にはサンプル位置は最初から順に読み込んでいます。設定値はそれほど多くなければ1の処理で取得される設定値が頻繁に変わることはありません。
このため最後の計算以外の処理はなるべくしないようにしたいと思います。
クラスとフィールドの定義の修正
前後のサンプル位置keyFloorとkeyCeiling、前後の設定値valueFloorとvalueCeilingを変数として持つようにしました。また設定が足された時には再読み込みしなければいけませんので更新フラグupdatedを定義しておきます。
public class DoubleMap extends TreeMap<Long, Double> implements SoundConstants{
double sampleRate;
long index;
double keyFloor;
double keyCeiling;
double valueFloor;
double valueCeiling;
boolean updated;
コンストラクタを修正
更新フラグはfalse「更新していない」にしておきます。
public DoubleMap(double initialValue) {
this(SAMPLE_RATE, initialValue);
}
public DoubleMap(double sampleRate, double initialValue) {
this.sampleRate = sampleRate;
put(0l, initialValue);
index = 0;
updated = false;
}
設定メソッドの修正
設定値が追加かされた時にも更新フラグはfalse「更新していない」にします。
public void putSecondValue(double second, double value) {
put((long) (second * sampleRate), value);
updated = false;
}
値を取得するメソッドを修正
更新していて位置が前の位置と同じであれば前の値を返します。
private double getValue(long index) {
if(updated && keyFloor == index){
return valueFloor;
}
更新していて位置が後の位置と同じであれば後の値を返します。
if(updated && keyCeiling == index){
return valueCeiling;
}
更新していない場合、位置が前後の間からはみ出すようであれば再計算します。
if(!updated || index < keyFloor || index > keyCeiling){
前と後の設定を取得します。
Entry<Long, Double> floor = floorEntry(index);
Entry<Long, Double> ceiling = ceilingEntry(index);
両方ない場合は中断します。まず起こりえませんが。
if (floor == null && ceiling == null) {
throw new IllegalStateException("map should contain at least one entry");
}
前がない場合は後と同じに、後がない場合には前と同じにします。
if (floor == null) {
floor = ceiling;
} else if (ceiling == null) {
ceiling = floor;
}
それぞれの値を変数に代入します。
keyFloor = floor.getKey();
keyCeiling = ceiling.getKey();
valueFloor = floor.getValue();
valueCeiling = ceiling.getValue();
更新フラグを「更新した」にします。
updated = true; }
前後の位置が同じ場合には前の値を返します。これはこの後の計算で0で割ってしまわないようにするためです。
if(keyCeiling == keyFloor){
return valueFloor;
}
指定された位置が前後の中間の位置にある場合には前後の値を按分して返します。
return valueFloor + (valueCeiling - valueFloor) * (index - keyFloor) / (keyCeiling - keyFloor); }
サブマップの取得メソッドの追加
今後雛形のエンベロープから演奏用のエンベロープを作っていきたいと思います。雛形のエンベロープより時間が短い時のために「切り出し」ができるようにしておきます。指定の時間のサンプル位置とその時の値を取得します。新規にDoubleMapのインスタンスを作りそこにサンプル位置前の設定を全てコピーします。最後に指定のサンプル位置とその値も設定値として保持するようにします。
public DoubleMap createSubMap(double second){
long endIndex = (long) (second * sampleRate);
double endValue = getValue(endIndex);
DoubleMap subMap = new DoubleMap(sampleRate, get(0l));
for(long key:keySet()){
if(key >= endIndex){
break;
}
subMap.put(key, get(key));
}
subMap.put(endIndex, endValue);
return subMap;
}
値の乗算
全ての設定値に同じ値をかけられるようにします。音量の調節が必要な時に便利です。
public void multiply(double ratio){
for(long key:keySet()){
put(key, get(key) * ratio);
}
}