エフェクターの実装に必要な知識

Web Audio APIを利用する楽しみの1つはエフェクターを実装することかもしれません. Web Audio APIが定義するノード (DelayNode, BiquadFilterNode, WaveShaperNode, DynamicsCompressorNode…) を利用することによって, 比較的容易にエフェクターを実装することが可能です.

しかしながら, Web Audio APIによるハイレベルな (抽象度の高い) APIを利用することによって, エフェクターを実装する場合でも, 基本となる知識が2点ほどあります.

  • LFO (Low Frequency Oscillator)
  • AudioParamへの接続

LFOに関しては, Web Audio APIに限ったことではなく音信号処理に関係することです. また, connectメソッドはAudioNodeへの接続だけではなく, AudioParamへの接続も可能です. その基本とエフェクターの実装に必要な接続の考え方を解説します.

LFO

エフェクターにはいくつかの種類があり, モジュレーション系と呼ばれるエフェクター (コーラス, フランジャー, フェイザー, トレモロ…) を実装するには, ある特定のパラメータを時間経過とともに周期的に変化させる必要があります. 具体的には, コーラス / フランジャーは, ディレイタイム (遅延時間) を時間経過とともに周期的に変化させることによって実装可能です.

そして, 特定のパラメータを時間経過とともに周期的に変化させる機能が, LFO (Low Frequency Oscillator) です. 日本語に訳すと, 低周波数発振器です.

正弦波のx軸の物理量は時間のままにして, 出力となるy軸の物理量を周期的に変化させたいパラメータ (例えば, コーラス / フランジャーであればディレイタイム) に変更して, 1 ~ 10 Hz程度の低周波数に設定することがLFOを実装するために必要なことです.

LFO

図2 - 1 - a. LFO (Low Frequency Oscillator)

ちなみに, LFOの波形は, 正弦波以外にもノコギリ波や三角波, 矩形波でも可能です. これによって, パラメータの変化のパターンが異なってくるので, サウンドエフェクトのバリエーションを簡単に増やすことが可能です.

AudioParamへの接続

LFOがエフェクターの実装において重要なのはわかりましたが, 具体的にどうすれば実装できるのでしょうか? ここで, LFOの実装に必要なポイントを整理してみます.

LFOの出力先をパラメータにする
OscillatorNodeインスタンスの接続先をパラメータにする.
低周波数に設定する
OscillatorNodeインスタンスのfrequencyプロパティの値を低く設定する.

低周波数に設定することは簡単そうです. したがって, LFOの出力先をパラメータにすることができれば…LFOを実装できそうです.

そして, connectメソッドはそのためにも定義されています. connectメソッドの引数の型は, AudioNodeクラスを (プロトタイプ) 継承したクラスのインスタンスだけでなく, AudioParamインスタンスも指定可能です.

AudioParamインスタンスへの接続

図2 - 1 - b. AudioParamインスタンスへの接続

もっとも, connectメソッドの呼び出しにおいて大きな違いはなく, 引数の型が異なるだけです (つまり, connectメソッドはオーバーロード定義されているメソッドです) .

サンプルコード 01


window.AudioContext = window.AudioContext || window.webkitAudioContext;

// Create the instance of AudioContext
var context = new AudioContext();

// Create the instance of OscillatorNode
var oscillator = context.createOscillator();  // for Input
var lfo        = context.createOscillator();  // for LFO

// for legacy browsers
oscillator.start = oscillator.start || oscillator.noteOn;
oscillator.stop  = oscillator.stop  || oscillator.noteOff;
lfo.start        = lfo.start        || lfo.noteOn;
lfo.stop         = lfo.stop         || lfo.noteOff;

// OscillatorNode (Input) -> AudioDestinationNode (Output)
oscillator.connect(context.destination);

// OscillatorNode (LFO) -> frequency (AudioParam)
lfo.connect(oscillator.frequency);

// Start sound
oscillator.start(0);

// Effector (Vibrato) ON
lfo.start(0);

サンプルコード 01は, AudioParamインスタンスであるOscillatorNodeインスタンスのfrequencyプロパティをLFOの出力先として接続しています. これによって, (サウンドの入力点となる) OscillatorNodeインスタンスのfrequencyプロパティが時間経過とともに周期的に変化します. これは, エフェクターの1種であるビブラートの原理です.

ノード接続とLFO接続図

図2 - 1 - c. ノード接続とLFO接続図

1つ注意点として, connectメソッドが接続できる型 (引数の型) は, AudioNodeかAudioParamなので, 独自に定義した数値型のパラメータなどは接続 (引数に指定) できません.

Depthの設定

LFOを直接AudioParamインスタンスに接続するだけでは, エフェクターの実装において真価を発揮できません. なぜなら, LFOの出力をAudioParamインスタンスに直接接続しただけでは, その出力は, -1 ~ +1の範囲に限定されます. サンプルコード 01のビブラートなら, -1 ~ +1 Hzの範囲でしかfrequencyプロパティが変化しません. この範囲の振れ幅だと, ビブラートがかかっている感じがほとんどありません.

LFOの出力値をコントロールするためには, LFOをAudioParamインスタンスに直接接続するのではなく, GainNodeインスタンスに接続して, GainNodeインスタンスをAudioParamインスタンスに接続します.

そして, GainNodeインスタンスのgainプロパティを設定することで, LFOの出力値を任意の範囲にコントロールすることが可能になります.

ノード接続とLFO接続図

図2 - 1 - d. ノード接続とLFO接続図

サンプルコード 02


/*
 * Add code to sample code 01
 */

// ....

// for legacy browsers
context.createGain = context.createGain || context.createGainNode;

// Create the instance of GainNode
var depth = context.createGain();  // for LFO

// OscillatorNode (LFO) -> GainNode (Depth) -> frequency (AudioParam)
lfo.connect(depth);
depth.connect(oscillator.frequency);

// Start sound
oscillator.start(0);

// Effector (Vibrato) ON
lfo.start(0);

// Set Depth
depth.gain.value = 10;  // +- 10 Hz

gainプロパティのminValue / maxValueは, それぞれ0 / 1ですが, これ以外の範囲の値に設定することも可能で, 例外は発生しません. この動作は実装の不備ではなく, 仕様で定義されています. サンプルコード 02では, GainNodeインスタンスのgainプロパティを10に設定しています. したがって, OscillatorNodeインスタンスのfrequencyプロパティが, -10 ~ +10 Hzの範囲で変化することになります.

現実世界のエフェクターにおいては, LFOの出力先となるGainNodeインスタンスのgainプロパティは, Depthとしてコントロール可能になっているものが多いです.

Rate (Speed) の設定

LFOの実装のためには, もう1つ必要なことがありました. LFOの周波数を低周波数に設定することです.

LFOの周波数は出力が変化するスピード, つまり, ある特定のパラメータを時間経過とともに周期的に変化させるというLFOの機能において, パラメータ変化のスピードを決定づけます. つまり, LFOの周波数を高くするほど変化のスピードが速く, 逆に, 低くするほど変化のスピードが遅くなります.

もっとも, LFOに設定する周波数の範囲はそれほど広くなく, 多くのモジュレーション系エフェクトで1 ~ 10 Hz程度です (リングモジュレーターは例外で, 1000 Hz前後に設定します).

LFOのRate (Speed)

図2 - 1 - e. LFOのRate (Speed)

サンプルコード 03


/*
 * Add code to sample code 02
 */

// ....

// Set Rate
lfo.frequency.value = 5;  // 5 Hz

現実世界のエフェクターにおいては, LFOの周波数 (OscillatorNodeインスタンスのfrequencyプロパティ) は, Rate, または, Speedとしてコントロール可能になっているものが多いです.

基準値の設定

最後に, 特定のパラメータが時間経過とともに周期的に変化する際の基準値の設定について解説しておきます.

もっとも, 実装はとても簡単で基準値を変化させるパラメータに設定するだけです.

例えば, サンプルコードのビブラートを880 Hzを基準値として変化させたい場合は, サンプルコード 04のように設定します.

サンプルコード 04


/*
 * Add code to sample code 03
 */

// ....

// Set Base Value
oscillator.frequency.value = 880;  // 880 Hz +- Depth

この値を基準に, Depthで設定した値 (GainNodeインスタンスのgainプロパティ) の範囲でパラメータが周期的に変化します. 具体的に解説すると, サンプルコードのビブラートは基準値を880 Hz, Depthを10に設定しているので, 880 Hz ± 10 Hzの範囲でfrequencyプロパティが周期的に変化することなります.

パラメータの基準値とDepth

図2 - 1 - f. パラメータの基準値とDepth

そして, 基準値とDepthの関係から, パラメータ変化の最小値は考慮しておく必要があります. サンプルコードの例であれば, 基準値を50 Hz, Depthを100に設定した場合, 最小値は-50 Hzとなってしまいます. この場合でも例外は発生しませんが, 無音が周期的に繰り返されるのでちょっと不自然な感じのエフェクトになります (ただし, GainNodeインスタンスのgainプロパティを変化させる場合は例外で, 負数になると位相が反転するという仕様になっています) .

また, 変化させるパラメータをコンソールなどに出力しても, 基準値に設定した値が出力されるので, 周期的に変化しているかを視覚的に確認する方法は現状はなさそうです (?) デバッグなどの際には少し注意が必要です.

それでは, ここまでのまとめとしてデモ 01を試してみてください. パラメータをいろいろ設定して, エフェクター実装の基本となるLFO・Depth・Rateを感覚で理解してみてください.

デモ 01

イントロダクション まとめ

このページでは, エフェクター実装に必要となる, LFOの実装に関して解説しました. 重要となるエッセンスを簡単にまとめておきます.

LFO (Low Frequency Oscillator)
特定のパラメータを時間経過とともに周期的に変化させる.
LFOの実装
  • AudioParamインスタンスへの接続
  • DepthとRateの設定
  • パラメータ変化の基準値と最小値の考慮