FM音源によるシンセサイザーは1980年代に流行しました。当時はサンプリング音を扱うほどは機械のスペックを高くすることができなかったため計算によって複雑な倍音を発生させるシステムは重宝されました。
FM理論
FM音源はFM理論という考え方が元になっています。音を出すキャリアーと呼ばれる発振器oscillatorの周波数に変調をかけるものです。おそらく専門書がたくさんあるのでそちらを参照するのが良いと思いますがここでは簡単な説明だけします。
周波数変調について
前回LFOによる周波数の変調を実装しましたが実装としてはこれと全く同じです。サイン波は三角関数を使って計算しますがこの関数に入れる値に変調をかけることを周波数変調と言います。以下の式でいうと基本の周波数αに対して変調をかけるxにどのような値を設定するかが重要です。ちなみにyは関数の外なので音量を調節するものです。
sin(α + x) * y
LFOによる変調との違い
実装は同じなのですがLFOによる変調との違いは設定する値の違いです。
- モジュレーターの周波数が高い
- モジュレーターの出力が大きい
モジュレーターに設定する周波数は基本的にはキャリアーの整数倍になります。LFOはLow Frequency Oscillatorの通り低周波での変調を目的としていますがFM理論ではもっと積極的に音を変更することを意図しています。
実装
音を聞いてみるためサンプル音声を作ります。まずいくつかのパターンを見れるようにメソッドを作成します。500Hzで3秒間徐々に音量が小さくなっていくキャリアーに対して変調をかけます。引数ratioはモジュレーターの周波数をキャリアーの周波数の何倍にするかを決めます。モジュレータの周波数が500Hzなのでratioが1であれば500Hz、2であれば1000Hzになります。変数ampはモジュレーターの出力を決めます。あまり小さいと変調がよくわかりませんので1000前後の数字を設定するようにします。
public static OscillatorReader getOscillatorReader(double ratio, double amp) {
モジュレーターを作成します。
DoubleMap freqMod = new DoubleMap(500 * ratio); DoubleMap envMod = new DoubleMap(amp); OscillatorReader modulator = new OscillatorReader(new SineOscillator(), freqMod, envMod, 3);
作成したモジュレータを指定してキャリアーを作成して返します。
DoubleMap freq = new DoubleMap(500); DoubleMap env = new DoubleMap(1); env.putSecondValue(3, 0); return new OscillatorReader(new SineOscillator(), freq, env, 3, new SoundReadable[]{modulator}); }
こんどはこれを呼び出す側を作ります。サンプルとして以下のパターンを作成します。
- 周波数比1倍で出力0、モジュレータの出力がないだたのサイン波です。
- 周波数比1倍で出力1000
- 周波数比1倍で出力5000
- 周波数比1倍で出力10000
- 周波数比2倍で出力1000
- 周波数比2倍で出力5000
- 周波数比4倍で出力1000
- 周波数比4倍で出力5000
- 周波数比8倍で出力1000
- 周波数比8倍で出力5000
- 周波数比13倍で出力1000
- 周波数比13倍で出力5000
- 周波数比2.3678倍で出力1000、周波数比が整数でないため音高が不明瞭になります。
- 周波数比2.3678倍で出力5000、周波数比が整数でないため音高が不明瞭になります。
ここで決めた順番に音をならしていきます。
TimeLine tl = new TimeLine(); int i = 0; tl.addReadable(i++ * 3, getOscillatorReader(1, 0)); tl.addReadable(i++ * 3, getOscillatorReader(1, 1000)); tl.addReadable(i++ * 3, getOscillatorReader(1, 5000)); tl.addReadable(i++ * 3, getOscillatorReader(1, 10000)); tl.addReadable(i++ * 3, getOscillatorReader(2, 1000)); tl.addReadable(i++ * 3, getOscillatorReader(2, 5000)); tl.addReadable(i++ * 3, getOscillatorReader(4, 1000)); tl.addReadable(i++ * 3, getOscillatorReader(4, 5000)); tl.addReadable(i++ * 3, getOscillatorReader(8, 1000)); tl.addReadable(i++ * 3, getOscillatorReader(8, 5000)); tl.addReadable(i++ * 3, getOscillatorReader(13, 1000)); tl.addReadable(i++ * 3, getOscillatorReader(13, 5000)); tl.addReadable(i++ * 3, getOscillatorReader(2.3678, 1000)); tl.addReadable(i++ * 3, getOscillatorReader(2.3678, 5000)); WavFileWriter.create(tl, new File("wav/fm.wav"));
実行結果
以下実行結果です。キャリアーが出力するサイン波がモジュレータによって変調されています。