振幅変調
RMは二つの波形の掛け算だけど、これは一方の波形の振幅をもう一方の波形で変調しているとも見なせる。例えば、サイン波の振幅を決まった値じゃなくて何かの波形にするとRMだ。つまりRMは振幅変調(AM)の一種のはず。でも調べてみると、音響分野では
- RM:双極(プラスにもマイナスにもなる)の波形同士の掛け算
- AM:双極の波形の振幅を単極(0以上にしかならない)の波形で変調
という違いがあるらしい。まーこれはこういうもんだと思って覚えよう。あと今さらだけど、AMに限らず、変調する側をモジュレータ、変調される側をキャリアと呼ぶ。
実は上で書いたAMは既にエンベロープでやっている。WikipediaとかでADSRの図を見ればすぐわかるように、ADSRに代表されるエンベロープは単極波形になっているから、ある波形の振幅にエンベロープを適用するのはAM。
サイン波のような波形をモジュレータに使うときは、まず単極になるように波形全体を上にシフトする。この場合、RMとは違ってキャリアの周波数成分は出力に残る。サイン波を使ったAMの例。
SinOsc c => Gain am => dac; SinOsc m => Gain mix => am; Step step => mix; // 波形のシフトに使う 220 => m.freq; 1 => step.next; 3 => am.op; for(0 => int i; i < 660; i++) { i => c.freq; <<<c.freq() + m.freq(), c.freq(), c.freq() - m.freq()>>>; 10::ms => now; }
ユニットジェネレータStepはfloat型パラメータ.nextにセットした値をサンプリングごとに出力するため、一定の値が欲しいときに使える。今はstepの出力1をmixのところでサイン波に足しているので結果としてmixの出力は0から2の間を振動する単極波形になる。これをモジュレータにして、もう一方のサイン波(キャリア)と掛け算する。最終的な波形は、キャリアの周波数に加えて、二つのサイン波の周波数の和と差を周波数成分として持つ。
もう少し複雑な波形を使ったプログラム。
Mandolin mandolin => Gain am => dac; TriOsc mod => Gain mix => am; Step step => mix; 2000 => mod.freq; 1.6 => mod.gain; 2 => step.next; 3 => am.op; .1 => am.gain; [67, 69, 72, 74, 76, 79] @=> int scale[]; // 徴調式 int i, d; // 現在位置と目標位置 while(true) { if (i == d) Std.rand2(0, scale.cap() - 1) => d; Std.sgn(d - i) $ int +=> i; // 目標へ一歩進む scale[i] => Std.mtof => mandolin.freq; 1 => mandolin.pluck; (maybe&&maybe ? 200 : 400)::ms => now; }
関数Std.sgnはfloat型の値を取って、それが負の場合-1.0、ゼロの場合0、正の場合1.0を返す。プログラムの短さの割には面白い音が鳴るんじゃない?