FM 音源みたいなもの JavaScript の説明
戻る


昔の PC のサウンド機能で使われていた FM 音源を,Web Audio API の機能で作ります.
もちろん,ScriptProcessorNode などを用いて,プログラムで PCM データを生成して再生するのであれば,どのような波形でも作れる訳ですが,ここでは直接 PCM データを生成するのではなく,OscillatorNode を使ってやってみます.
ただ,私は FM 音源についてあまり詳しくないので,正しい FM 音源の処理になっていない部分があるかも知れません.なので,このおもちゃのタイトルは「FM 音源」ではなく,「FM 音源みたいなもの」です.

FM 音源の原理は次のような式で表されます.
S(t)=Asin(ωct+βsin(ωmt))

t: 時間
S(t): 時間tにおける合成信号
A: キャリアの振幅
β: 変調指数
ωc: キャリアの角周波数
ωm: 変調信号の角周波数
名前は「FM 音源」ですが,この式を見ると,これは周波数変調ではなく位相変調だと思うのですが...

Web Audio API の標準の機能には,直接的に位相変調をかけるようなものは無いようです.OscillatorNode の周波数は動的に変えられるので,周波数変調をかけることはできそうです.
位相変調と周波数変調の間には密接な関係があり,数学的には簡単な操作で相互に変換可能です.変調信号を微分してから周波数変調をかけると位相変調になるそうなので,変調信号を微分した信号で OscillatorNode の周波数を制御すれば,位相変調をかけることができそうです.
デジタル信号ですから,微分は差分に置き換えればいいはずです.ということで,変調信号の差分をとって,それで OscillatorNode の周波数を制御するという方法で試してみることにします.

変調信号の差分をとるには IIRFilterNode を利用します.
IIRFilterNode は,x[i]をi番目の入力信号,y[i]をi番目の出力信号としたとき,n番目の出力信号y[n]がこのように定義されるフィルターです.
y[n]=(1/a0)(b0x[n]+b1x[n−1]+b2x[n−2]+ … −a1y[n−1]−a2y[n−2]− …)
係数(b0,b1,b2,…),(a0,a1,a2,…)は IIRFilterNode 作成時にパラメータとして与えます.
0=1,b1=−1,a0=1 とし,その他の係数はすべてゼロとすると,y[n]はこのようになります.
y[n]=x[n]−x[n−1]
これで 1 サンプル前の値との差分をとることができます.

オペレータは 4 つ作ります.
オペレータの構成はこのようになります.
オペレータの構成
変調レベル設定の GainNode は,指定した変調レベルに従ってオペレータの変調入力レベルを設定するものです.
周波数変位調整の GainNode は,変調の周波数変位が適切な大きさになるよう調整するためのものです.
エンベロープ作成の GainNode は,指定したエンベロープ パラメータに従ってエンベロープを作成するためのものです.
出力レベル設定の GainNode は,指定した出力レベルに従ってオペレータの出力レベルを設定するものです.

変調レベル設定と周波数変位調整の GainNode,エンベロープ作成と出力レベル設定の GainNode は,それぞれひとつにまとめることも可能ですが,別にした方が処理が簡単になるので分けています.
出力は他のオペレータの変調入力に接続されるか,最終段のオペレータの場合はオーディオ出力に接続されます.

1 番目のオペレータにはフィードバックをかけられるようにします.このように出力を自身の変調入力に接続します.
フィードバックの接続
オペレータ 1 の変調レベルはフィードバックの量を調節するものになります.
出力と変調入力の間に DelayNode を挟んでいるのは,このような循環するノードの接続を作る場合,ループの中に少なくともひとつの DelayNode が必要なためです.

アルゴリズムは,昔の音源 LSI で使われていた 8 種類のアルゴリズムのうちの 7 つと同じものを用意しました.
1
アルゴリズム 1 の接続

2
アルゴリズム 2 の接続

3
アルゴリズム 3 の接続

4
アルゴリズム 4 の接続
5
アルゴリズム 5 の接続

6
アルゴリズム 6 の接続

7
アルゴリズム 7 の接続

OPn: オペレータ n (n = 1,2,3,4)
残りのひとつは以下のようなものですが,このアルゴリズムは,6 番のアルゴリズムでオペレータ 3 とオペレータ 4 の変調レベルをゼロに設定したのと同じことになるので,省きました.
省いたアルゴリズムの接続

使い方

「アルゴリズム」で使用するアルゴリズムを選び,各オペレータのパラメータを入力して「発音」ボタンを押します.
「発音」ボタンを押している間発音を継続し,放すと(リリース時間経過した後)発音を停止します.(「発音」ボタンを押している間でも,サステイン中の減衰によって音が消えることはあります.)

「周波数」にはオペレータの発振周波数を Hz 単位で入力します.
「出力レベル」にはオペレータの出力の大きさを,「変調レベル」にはオペレータの変調入力の大きさを入力します.出力レベルと変調レベルは 0(出力/変調 無し)から 1(出力/変調 最大)までの数値で指定します.未入力の場合は 0 として扱われます.
出力レベルと変調レベルの関係
変調する側と変調される側のオペレータの数が 1 対多や多対 1 で接続されているときは,1 個の側のレベル設定を変えると,相手側のすべてのオペレータの信号に対して影響があります.
1 対 1 の場合は,どちら側を変えても効果に違いはありません.

「AR」,「DR」,「SR」,「RR」,「SL」の各項目には,エンベロープのアタック レート,ディケイ レート,サステイン レート,リリース レート,サステイン レベルを入力します.
エンベロープの値は,最大値(アタック終了直後の値)を 1 とした比率で表します.たとえば,アタック レートの指定値が 0.1/ms の場合,アタック開始から 10ms で最大値に達します.
オペレータの出力の大きさは,「出力レベル」で設定した大きさにその比率を掛けたものになります.
エンベロープ

エンベロープ
アタック レート
アタック時のエンベロープ値の 1ms あたりの増加量を,最大値に対する比率で,0 から 1 までの数値で指定します.
未入力または 0 を指定した場合は,最初から最大値になります.
ディケイ レート
アタック後のエンベロープ値の 1ms あたりの減衰量を,最大値に対する比率で,0 から 1 までの数値で指定します.サステイン レベルに達するまでこのレートで減衰します.
未入力または 0 を指定した場合は,アタック終了後直ちにサステイン レベルになります.
サステイン レート
ディケイ後のエンベロープ値の 1ms あたりの減衰量を,最大値に対する比率で,0 から 1 までの数値で指定します.
未入力または 0 を指定した場合は,サステイン状態の間エンベロープ値は減衰しません.
リリース レート
リリース時のエンベロープ値の 1ms あたりの減衰量を,最大値に対する比率で,0 から 1 までの数値で指定します.
未入力または 0 を指定した場合は,「発音」ボタンを放すと直ちにエンベロープ値は 0 になります.
サステイン レベル
サステイン開始時のエンベロープ値を,最大値に対する比率で,0 から 1 までの数値で指定します.
未入力または 0 を指定した場合は,サステイン レベルは 1 になります.その場合,ディケイ レートは意味を持ちません.
アタック レート 〜 サステイン レベルがすべて未入力または 0 の場合,「発音」ボタンを押してから放すまでの間ずっと,エンベロープ値は最大値になります.

入力したパラメータをファイルに保存しておいて,後でそれを読み込んで復元することができます.
「パラメータ読込」ボタンを押すと,パラメータの値をファイルから読み込んで入力領域にセットします.
「パラメータ保存」ボタンを押すと,パラメータの入力値をファイルに保存します.

「パラメータ クリア」ボタンを押すと,パラメータの入力領域をすべて未入力状態に戻します.


パラメータ サンプル

パラメータのサンプルをいくつか用意しました.このファイルをダウンロードして「パラメータ読込」で読み込むと,サンプルの音を聴くことができます.
fm_samp1.dat

fm_samp2.dat

fm_samp3.dat

fm_samp4.dat

fm_samp5.dat

戻る