フェイザーとは?

フェイザーはシュワシュワという感じを音に与えます. その感じとしてはフランジャーに似ています.

フランジャーに似ているエフェクトでありながら, その原理はまったく異なります. フェイザーは音の位相を変化させる周波数帯域を周期的に変化させて, 原音とのミックスで音を干渉させることによって実装できるエフェクトです. ちなみに, フェイザーの正式な名称は, フェイズ・シフターであり, まさに, 名称が原理を意味しています.

フェイザーの原理をある程度理解するには音の特性や音信号処理の基本知識が必要になるので, まずは, 実装することから始めましょう.

フェイザー

フェイザーは, 音の位相を変化させる周波数帯域を周期的に変化させて, 原音とのミックスで音を干渉させることによって実装できます. つまり, フェイザーの実装は以下の3つ処理に分解可能となります.

位相を変化させる
オールパスフェィルタに原音を通過させる
原音とエフェクト音をミックスにより干渉させる
GainNodeインスタンスを利用して, Dry / Wet機能を実装する
位相を変化させる周波数帯域を周期的に変化させる
LFOをAudioParamインスタンスであるBiquadFilterNodeインスタンス (typeプロパティの値が 'allpass' ) のfrequencyプロパティに接続する

原音とエフェクト音をミックスで, 干渉という音の物理現象の用語を利用していますが, 実装すべきこととしては, 原音とエフェクト音をミックスさせるだけです (ミックスすることで干渉が発生します). したがって, 干渉させる (原音とエフェクト音をミックスする) ことはコーラス・フランジャーで既に解説・実装済のことです. ちなみに, フェイザーもコーラス・フランジャーと同じく, センド・リターン型のエフェクトです.

したがって, 位相を変化させること, および, 位相を変化させる周波数帯域を周期的に変化させることができれば, フェイザーが実装できます. そして, BiquadFilterNodeクラスがそれを可能にします. BiquadFilterNodeクラスはフィルタの種類によって様々な処理が可能で, その詳細は波形描画のページで解説していますが, フェイザーの実装では, 位相を変化させることができればいいので, その視点からBiquadFilterNodeクラスの解説をします.

まずは, 入力となるOscillatorNodeインスタンスの生成と, BiquadFilterNodeインスタンスの生成を実装します. BiquadFilterNodeインスタンスを生成するには, AudioContextインスタンスのcreateBiquadFilterメソッドを利用します.

サンプルコード 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

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

// Create the instance of BiquadFilterNode
var filter = context.createBiquadFilter();

BiquadFilterNodeインスタンスのフィルタの種類は全部で8つ定義されています. そして, そのうちの1つである, オールパスフィルタ (All-Pass Filter) が位相を変化させる機能をもちます. オールパスフィルタを利用するには, BiquadFilterNodeインスタンスのtypeプロパティに, 'allpass' を指定します. ただし, 初期の仕様においては, typeプロパティは数値型 (0 - 7) で, それぞれの数値がフィルタの種類に対応していました. したがって, サンプルコード 02のようにフォールバックを記述しておくと安全です.

表2 - 4 - a. BiquadFilterNodeインスタンスのtypeプロパティの値
Filter TypeStringNumber
ローパスフィルタlowpass0
ハイパスフィルタhighpass1
バンドパスフィルタbandpass2
ローシェルビングフィルタlowshelf3
ハイシェルビングフィルタhighshelf4
ピーキングフィルタpeaking5
ノッチフィルタnotch6
オールパスフィルタallpass7

サンプルコード 02


/*
 * Add code to sample code 01
 */

// ....

// Use All-Pass Filter
filter.type = (typeof filter.type === 'string') ? 'allpass' : 7;

フェイザーの実装では, オールパスフィルタを複数 (4, 8, 12, 24など) 接続します. オールパスフィルタの接続数によって, 4段フェイザー8段フェイザーのように呼ばれることもあります. ただし, 原理上の理由で, 奇数の接続はしません. この点には注意してください.

複数のオールパスフィルタを接続可能なように, サンプルコードを変更します.

サンプルコード 03


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

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

var MAXIMUM_STAGES = 24;                         // The maximun number of All-Pass Filters
var filters        = new Array(MAXIMUM_STAGES);  /** @type {Array.<BiquadFilterNode>} */
var numberOfStages = 4;                          // The number of using All-Pass Filters

// Create the instance of BiquadFilterNode
for (var i = 0; i < MAXIMUM_STAGES; i++) {
    // Create the instance of BiquadFilterNode
    filters[i] = context.createBiquadFilter();

    // Use All-Pass Filter
    filters[i].type = (typeof filters[i].type === 'string') ? 'allpass' : 7;
}

入力ノードをBiquadFilterNode (オールパスフィルタ) に接続することによって, 位相が変化します. そこで, 原音の接続とエフェクト音の接続を実装します.

サンプルコード 04


/*
 * Add code to sample code 03
 */

// ....

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

// Create the instance of GainNode
var mix = context.createGain();  // for effect (Phaser) sound

// Connect nodes for original sound
// OscillatorNode (Input) -> AudioDestinationNode (Output)
oscillator.connect(context.destination);

// Connect nodes for effect (Phaser) sound
// OscillatorNode (Input) -> BiquadFilterNode (All-Pass Filter) x N -> GainNode (Mix) -> AudioDestinationNode (Output)
oscillator.connect(filters[0]);

for (var i = 0; i < numberOfStages; i++) {
    if (i < (numberOfStages - 1)) {
        filters[0].connect(filters[i + 1]);
    } else {
        filters[i].connect(mix);
    }
}

mix.connect(context.destination);

BiquadFilterNode (All-Pass Filter)の多段接続

図2 - 4 - a. BiquadFilterNode (All-Pass Filter)の多段接続

ところで, 位相を変化させるにはオールパスフィルタを利用すればいいことはわかりましたが, 位相を変化させる周波数帯域を周期的に変化させるにはどうすればいいのでしょうか?

BiquadFilterNodeインスタンスにはfrequencyプロパティが定義されており, BiquadFilterNodeインスタンスのtypeプロパティが 'allpass' (オールパスフィルタ) の場合には, このfrequencyプロパティが位相を変化させる周波数帯域を決定します. さらに, frequencyプロパティはAudioParamインスタンスです. したがって, LFOをBiquadFilterNodeインスタンス (オールパスフィルタ) のfrequencyプロパティに接続すれば, 位相を変化させる周波数帯域を周期的に変化させることが可能になります.

サンプルコード 05


/*
 * Add code to sample code 04
 */

// ....

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

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

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

// Connect nodes for LFO that changes frequency periodically
// OscillatorNode (LFO) -> GainNode (Depth) -> frequency (AudioParam) x N
lfo.connect(depth);

for (var i = 0; i < MAXIMUM_STAGES; i++) {
    depth.connect(filters[i].frequency);
}

LFOの接続

図2 - 4 - b. LFOの接続

フェイザーのバリエーションのために, 接続するオールパスフィルタの数を増減させることがあるでしょう. その場合は, disconnectメソッドでそれまでの接続をクリアして, 必要な数だけ再度接続します.

その実装例と, まとめのコードをサンプルコード 06に記載します. また, フェイザーではフランジャーと同じく, 強烈なエフェクトを与えるために, フィードバック機能をもつことも多いので, フィードバックの実装も追加しています.

エフェクト音の調整

図2 - 4 - c. エフェクト音の調整

エフェクト音調整のためのGainNodeのノード接続は, コーラスやフランジャーと同じです.

フィードバックの接続

図2 - 4 - d. フィードバックの接続

サンプルコード 06


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;

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

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

var MAXIMUM_STAGES = 24;                         // The maximun number of All-Pass Filters
var filters        = new Array(MAXIMUM_STAGES);  /** @type {Array.<BiquadFilterNode>} */
var numberOfStages = 4;                          // The number of using All-Pass Filters

// Create the instance of BiquadFilterNode
for (var i = 0; i < MAXIMUM_STAGES; i++) {
    // Create the instance of BiquadFilterNode
    filters[i] = context.createBiquadFilter();

    // Use All-Pass Filter
    filters[i].type = (typeof filters[i].type === 'string') ? 'allpass' : 7;
}

// Clear connection
oscillator.disconnect(0);

for (var i = 0; i < MAXIMUM_STAGES; i++) {
    filters[i].disconnect(0);
}

mix.disconnect(0);

// Connect nodes for original sound
// OscillatorNode (Input) -> AudioDestinationNode (Output)
oscillator.connect(context.destination);

if (numberOfStages > 0) {
    // Phaser ON

    // Connect nodes for effect (Phaser) sound
    // OscillatorNode (Input) -> BiquadFilterNode (All-Pass Filter) x N -> GainNode (Mix) -> AudioDestinationNode (Output)
    oscillator.connect(filters[0]);

    for (var i = 0; i < numberOfStages; i++) {
        if (i < (numberOfStages - 1)) {
            filters[0].connect(filters[i + 1]);
        } else {
            filters[i].connect(mix);

            // Connect nodes for Feedback
            // (OscillatorNode (Input) ->) BiquadFilterNode (All-Pass Filter) x N -> GainNode (Feedback) -> BiquadFilterNode (All-Pass Filter) x N -> ...
            filters[i].connect(feedback);
            feedback.connect(filters[0]);
        }
    }

    mix.connect(context.destination);
} else {
   // Phaser OFF
}

// Connect nodes for LFO that changes frequency periodically
// OscillatorNode (LFO) -> GainNode (Depth) -> frequency (AudioParam) x N
lfo.connect(depth);

for (var i = 0; i < MAXIMUM_STAGES; i++) {
    depth.connect(filters[i].frequency);
}

// Set parameters for Phaser

for (var i = 0; i < MAXIMUM_STAGES; i++) {
    // Set Base Value
    filters[i].frequency.value = 350;  // 350 Hz
}

// This value must be less than or equal to 1.0 (100%)
var depthRate = 0.5;  // 50 %

// Set Depth
depth.gain.value = filters[i].frequency.value * depthRate;  // 350 Hz +- 175 (350 * 0.5) Hz

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

// Set Mix
mix.gain.value = 0.5;

// Set Feedback
feedback.gain.value = 0.5;

// Start sound
oscillator.start(0);

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

フェイザーのノード接続

図2 - 4 - e. フェイザーのノード接続

以上でフェイザーが完成です. デモ 10・デモ 11で試してみてください. Stage (オールパスフィルタの接続数) とオールパスフィルタの周波数, LFOのDepth / Rate, エフェクト音のMix, Feedback…と設定可能なパラメータが多いので, ぜひ, お好みのフェイザーを発見してください.

デモ 10 (サウンド)

デモ 11 (オーディオ)

位相と干渉

このセクションでは, フェイザーの原理について簡単に解説します. 簡単に…とは言いつつも, フェイザーの原理を理解するには, 音の特性音信号処理の基本知識が必要になります. とりあえず, フェイザーが実装できればOKという場合はスルーしてください.

フェイザーの原理のキーワードとなっているのが, 位相干渉です. したがって, これら2つの物理現象を理解することがフェイザーの原理を理解することに等しいと言えます. そこで, 位相と干渉に関して解説します.

位相

位相とは簡単に表現すれば波の位置です. 位相を具体的に理解するためには, 三角関数の話が適切かもしれません. sin波 (関数) をπ / 2 (90°) ずらすと, 反転したcos波 (関数) になり, さらに, π / 2 (90°) ずらすと反転したsin波 (関数) になって, 2π (360°) ずらすと, もとのsin波 (関数) に戻る…

位相

図2 - 4 - f. 位相

x軸の物理量は時間ではなく, 位相 (位置) であることに注意してください.

この π / 2 (90°) や 2π (360°) というのが位相の実体です. また, 「ずらす」ということが位相変化を意味しています. そして, 位相変化を実現するのがオールパスフィルタというわけです.

楽器音やオーディオの波形は, 正弦波 (sin波, sin関数) のような単純な波形ではありませんが, その多くは, 周波数の異なる正弦波が合成された波形です. オールパスフィルタによる位相変化というのは, 合成されている正弦波のうち, オールパスフィルタの周波数 (帯域) をもつ正弦波が位相変化することです.

ところで, 位相を変えただけの音というのは知覚的には原音と変わりません. これは, 人間の聴覚は位相変化に鈍感ということに起因しています (一方で, 振幅や周波数の変化に関しては敏感です).

しかしながら, このような人間の聴覚特性にも関わらず, なぜフェイザーは, はっきりと知覚できるエフェクトなのでしょうか?

それは, フェイザーは位相変化させるだけでなく, 位相変化させた音を原音と干渉させているからです.

干渉

干渉とは, 複数の音波が合成 (エフェクターの解説においてはミックスと表現してきました) されたときに生じる, 振幅 (音の大きさ) の増幅や減衰のことです.

音の特性で解説したように, 音の波形というのは, 山1つ谷1つをまとまりとしてもっています. ここで, 同じ位相をもつ2つ音波を合成 (ミックス) すると, 山と山, 谷と谷がぴったり重なるので, 振幅は増幅します.

波の干渉

図2 - 4 - g. 波の干渉

同位相の波は山と山, 谷と谷がぴったりと重なるので, 干渉が発生することによって, 合成された波は増幅します.

そして, 2つの音波のうち, 一方の音波の位相を π / 2 (90°) 変化させると, 山と山, 谷と谷が重なるところがある一方で, 山と谷が重なるところも生じます. 山と谷が重なると, プラスとマイナスが重なるようなものなので, 振幅は減衰します.

波の干渉

図2 - 4 - h. 波の干渉

一方の音波の位相を π / 2 変化させると, 山と谷が重なるところでは, 干渉により減衰します. 山と山, 谷と谷が重なるところでは, 干渉により増幅します.

位相を π (180°) 変化させると, 元の音波とは反転した音波になります. したがって, 山と谷が重なるところばかりになり, 振幅は減衰の影響ですべて0となります. これは, 無音を意味しています (ソニー製ヘッドフォンのノイズキャンセリング技術はこの原理を利用しています) .

波の干渉

図2 - 4 - i. 波の干渉

一方の音波の位相を π 変化させると, 山と谷が重なるところのみになり, 干渉による減衰で無音となります.

つまり, 干渉とは複数の音波の振幅の加算処理ということです.

フェイザーの原理

位相 (変化) と干渉を解説したところで, フェイザーの原理をおさらいします.

  1. 1. オールパスフィルタを利用して, 特定の周波数帯域を位相変化させる
  2. 2. 原音と位相変化させた音を合成することにより, 干渉が生じて特定の周波数帯域の振幅が増幅・減衰する

そして, 干渉が発生する周波数帯域が周期的に変化することによって, 振幅が増幅・減衰する周波数帯域も周期的に変化し, フェイザーのエフェクトとなって知覚されます.

フェイザー まとめ

このページでは, フェイザーの実装と, その原理となっている2つの物理現象である, 位相変化と干渉について解説しました. そのエッセンスをまとめておきます.

表2 - 4 - b. フェイザーのノード接続
ConnectionDescription
原音 入力ノード + AudioDestinationNode
エフェクト音 入力ノード + All-Pass Filter x N + Mix (GainNode) + AudioDestinationNode
(N = 2, 4, 8, 12, ...)
LFO OscillatorNode + Depth (GainNode) + frequency (BiquadFilterNode)
フィードバック 入力ノード + All-Pass Filter x N + Feedback (GainNode) + All-Pass Filter x N ...
(N = 2, 4, 8, 12, ...)

フェイザーの原理の重要なポイントは, 以下の3点です.

  1. オールパスフィルタを利用して, 特定の周波数帯域を位相変化させる
  2. 原音と位相変化させた音を合成することにより, 干渉が生じて特定の周波数帯域の振幅が増幅・減衰する
  3. 位相変化する周波数帯域が周期的に変化して, 振幅が増幅・減衰する周波数帯域も周期的に変化する