この先もう少し凝ったことをするために一度クラスを最適化、オプティマイズします。
オシレータークラス
サンプリングレートをコンストラクタで指定していましたが一般にサンプリングレートは音声ファイルを作るときにはどの音も同じに鳴るはずです。SampleWavクラスから直接参照することもできるようにします。ついでにSampleWavクラスもwavファイルを書き出すものに特化して名前もWavFileWriterと変えます。以下オシレータークラスのソースです。
オシレーターのためのインタフェース
波形の違うオシレータークラスが同じreadメソッドを持つこと保証するためのインターフェースです。これを実装しているクラス同士は入れ替え可能になります。
package mocha.sound;
public interface Oscillatable {
public double read(double freq, double volume);
}
サインオシレーター
package mocha.sound;
public class SineOscillator implements Oscillatable {
double omega_t;
double delta_t;
public SineOscillator() {
this(WavFileWriter.SAMPLE_RATE);
}
public SineOscillator(double sample_rate) {
omega_t = 2.0 * Math.PI / sample_rate;
delta_t = 0;
}
@Override
public double read(double freq, double volume) {
double point = Math.sin(delta_t);
delta_t += freq * omega_t;
return point * volume;
}
}
矩形波オシレーター
package mocha.sound;
public class SquareOscillator implements Oscillatable {
double t;
double delta_t;
public SquareOscillator() {
this(WavFileWriter.SAMPLE_RATE);
}
public SquareOscillator(double sample_rate) {
t = 1.0 / sample_rate;
delta_t = 0;
}
@Override
public double read(double freq, double volume) {
double x = delta_t - Math.floor(delta_t);
double point = x < 0.5 ? 1 : -1;
delta_t += freq * t;
return point * volume;
}
}
三角波オシレーター
package mocha.sound;
public class TriangleOscillator implements Oscillatable {
double t;
double delta_t;
public TriangleOscillator() {
this(WavFileWriter.SAMPLE_RATE);
}
public TriangleOscillator(double sample_rate) {
t = 1.0 / sample_rate;
delta_t = 0;
}
@Override
public double read(double freq, double volume) {
double x = delta_t - Math.floor(delta_t);
double point = x < 0.25 ? x * 4.0 : x < 0.75 ? (0.5 - x) * 4.0 : (x - 1.0) * 4.0;
delta_t += freq * t;
return point * volume;
}
}
のこぎり波オシレーター
package mocha.sound;
public class SawOscillator implements Oscillatable {
double t;
double delta_t;
public SawOscillator(){
this(WavFileWriter.SAMPLE_RATE);
}
public SawOscillator(double sample_rate) {
t = 1.0 / sample_rate;
delta_t = 0;
}
@Override
public double read(double freq, double volume) {
double x = delta_t - Math.floor(delta_t);
double point = (0.5 - x) * 2.0;
delta_t += freq * t;
return point * volume;
}
}
周波数マップクラス
周波数の変化をマッピングして読み出すごとに周波数の変化を計算して返すクラスです。
package mocha.sound;
import java.util.Map.Entry;
import java.util.TreeMap;
public class FrequencyMap extends TreeMap<Long, Double> {
double sampleRate;
public FrequencyMap(double initialValue) {
this(WavFileWriter.SAMPLE_RATE, initialValue);
}
public FrequencyMap(double sampleRate, double initialValue) {
this.sampleRate = sampleRate;
put(0l, initialValue);
}
public void putFreq(double second, double freq) {
put((long) (second * sampleRate), freq);
}
public double getFreq(long index) {
if (containsKey(index)) {
return get(index);
}
Entry<Long, Double> a = floorEntry(index);
Entry<Long, Double> b = ceilingEntry(index);
if (a == null && b == null) {
throw new IllegalStateException("map should contain at least one entry");
}
if (a == null) {
return b.getValue();
} else if (b == null) {
return a.getValue();
}
double xa = a.getKey();
double xb = b.getKey();
double ya = a.getValue();
double yb = b.getValue();
return ya + (yb - ya) * (index - xa) / (xb - xa);
}
}