フィードバックとはFM理論ではオシレーターが自分自身を変調modulateすることを言います。今まで実装してた変調の機能はオシレーターの他にモジュレーターを定義してオシレーターを変調していましたがフィードバックはオシレーター自身が自分自身のモジュレーターとなります。
変調と「くすぐり」
FM理論で周波数変調のことを「くすぐり」で説明しているものがあるのでここでも説明しておきたいと思います。
モジュレーターはそれ自体では音を出しませんが音を出すキャリアーや他のモジュレーターに影響して変調させます。例えばある歌手をキャリアーとします。歌手は歌いますので音を出します。モジュレーターは声は出さない代わりに同じエネルギーで歌手をくすぐります。くすぐられた歌手は通常の声とは違う声色を出すことになります。このモジュレーターつまり「くすぐり手」のくすぐり方によって歌手の声色を変えて生きます。
フィードバックとはこの「くすぐり手」と「歌手」が同じ人物と説明されます。つまり自分で歌いながら自分をくすぐるのです。実際にはおかしな話ですがこれがくすぐりによるFM理論の説明になります。
オシレーターにフィードバック機能をつける
実装上はオシレーターが出す一つ前の出力を使って周波数に変調をかけます。このためオシレーターの内部で一つ前の出力を保持しておくバッファを追加します。
また音の読み出し時には今までは周期上の現在地と音量を渡していましたがこれにフィードバックの音量を渡せるようにします。オシレーター用のインタフェースOscillatableは以下のように変更します。
package mocha.sound; public interface Oscillatable extends SoundConstants { public double read(double freq, double feedback, double volume); }
ヤマハのシンセサイザーDX7ではフィードバックは6つあるオシレーターの決められた一箇所にのみ設定できました。エンベロープによるコントロールもない固定値でしたのでそれより複雑な実装になります。
今回はこのインタフェースを実装した抽象クラスをまず作ります。
オシレーターの抽象クラスを作る
クラスの定義とフィールド値を決めます。出力を計算する部分はサブクラスの実装に任せるため抽象クラスとしています。
tは一つのサンプルが占める時間、例えば48kHzでしたら1/48000秒です。
delta_tは現在の時間的な位置で単位は周期になります。整数倍ちょうどでしたら周期の変わり目になりますし小数点以下が0.5であれば周期のちょうど真ん中となります。fb_bufferは上で話た一つ前の出力値のためのバッファです。
public abstract class AbstractOscillator implements Oscillatable { double t; double delta_t; double fb_buffer;
コンストラクタです。今の所使う予定はないのですがシステム上のサンプルレート以外のサンプルレートも使えるようにしています。
public AbstractOscillator() { this(SAMPLE_RATE); } public AbstractOscillator(double sample_rate) { t = 1.0 / sample_rate; delta_t = 0; fb_buffer = 0; }
出力値の計算メソッドを抽象メソッドで定義します。この実装はサブクラスに任せます。
public abstract double getAmplitude(double delta_t);
読み込みのメソッドです。このクラスの中心的な処理になります。
まず引数にあるフィードバック音量と前回の出力値を掛け合わせてフィードバック値を計算します。これが0の場合は出力値もバッファも同じ値にします。もし0以外ん場合は出力値はフィードバック付きに、次回の計算のためのバッファにはフィードバックなしにします。バッファはフィードバック値なしにしないとフィードバックがフィードバックを取り込んで値がカオスになってしまいます。
@Override public double read(double freq, double feedback, double volume) { double fb_value = fb_buffer * feedback; double amplitude; if(fb_value == 0){ amplitude = fb_buffer = getAmplitude(delta_t); }else{ amplitude = getAmplitude(delta_t + fb_value); fb_buffer = getAmplitude(delta_t); } delta_t += freq * t; return amplitude * volume; }
各オシレーターの改修
今まで利用していたサイン波、三角波、ノコギリ波、矩形波の実装も変えます。この抽象クラスを継承して出力値の計算メソッドを実装するだけです。それぞれ作り方は以前書きましたのでソースだけ載せておきます。
サイン波
package mocha.sound; public class SineOscillator extends AbstractOscillator { @Override public double getAmplitude(double delta_t) { return Math.sin(delta_t * 2.0 * Math.PI); } }
三角波
package mocha.sound; public class TriangleOscillator extends AbstractOscillator { @Override public double getAmplitude(double delta_t) { double x = delta_t - Math.floor(delta_t); return x < 0.25 ? x * 4.0 : x < 0.75 ? (0.5 - x) * 4.0 : (x - 1.0) * 4.0; } }
ノコギリ波
package mocha.sound; public class SawOscillator extends AbstractOscillator { @Override public double getAmplitude(double delta_t) { double x = delta_t - Math.floor(delta_t); return (0.5 - x) * 2.0; } }
矩形波
package mocha.sound; public class SquareOscillator extends AbstractOscillator { @Override public double getAmplitude(double delta_t) { double x = delta_t - Math.floor(delta_t); return x < 0.5 ? 1 : -1; } }
次回はこのオシレーターを読み出す側のクラスを修正します。