サウンドプログラミングで音色を変えるとき、いちばん基本になる道具がフィルターです。フィルターは、音を「加工する魔法の箱」ではありません。波形の中から、速く変化する部分を弱めたり、ゆっくり変化する部分を弱めたりする計算です。

この記事では、ローパスとハイパスに絞ります。まずは「ローパスのカットオフ周波数を、デジタルの1サンプルごとの計算へどう変換するのか」をはっきりさせます。

ローパスは波形をならす

ローパスフィルターは、低い周波数を通し、高い周波数を落とすフィルターです。言い換えると、波形の急な変化をならします。矩形波やノコギリ波の角が丸くなり、音は明るさを失って、こもった方向へ変わります。

いちばん直感的なローパスは、移動平均です。いまの値だけでなく、少し前の値もまとめて平均します。たとえば4個の値を平均するなら、次のようになります。

$$ y[n] = \frac{x[n]+x[n-1]+x[n-2]+x[n-3]}{4} $$

$x[n]$ は入力、$y[n]$ は出力です。$n$ は「何番目の値か」を表す番号です。最後に4で割っているのは、4個の値を足して平均しているからです。3個を平均するなら3で割りますし、8個を平均するなら8で割ります。

この式は、波形の細かいギザギザをならします。細かいギザギザは高い周波数成分なので、高域が落ちます。まずはこれがローパスの出発点です。

実際のサウンドプログラミングでは、次のような形もよく使います。

$$ y[n] = ax[n] + (1-a)y[n-1] $$

これは、いまの入力 $x[n]$ と、ひとつ前の出力 $y[n-1]$ を混ぜる式です。$a$ が大きいほど入力に素早く追従し、$a$ が小さいほど前の出力を強く引きずります。前の値を引きずるほど、急な変化には追いつけません。だから高域が落ちます。

コードにすると、1サンプルごとの処理は次の形です。

let y = 0;

function lowpass(x, a) {
  y = a * x + (1 - a) * y;
  return y;
}

ここではまだ、カットオフ周波数をどう計算するかは置いておきます。まず大事なのは、ローパスが「前後の値を混ぜて、急な変化をならす」処理だということです。

ハイパスは低域を引き算で作る

ハイパスフィルターは、ローパスの逆です。高い周波数を通し、低い周波数を落とします。低域の濁りを減らす、風切り音や床振動を切る、音を細くする、といった用途で使います。

ハイパスは、ローパスを使うとかなり直感的に書けます。

$$ \mathrm{low}[n] = \mathrm{LPF}(x[n]) $$

$$ \mathrm{high}[n] = x[n] - \mathrm{low}[n] $$

入力から低い成分を取り出し、それを元の音から引く。すると、残るのは高い成分です。

別の書き方をすると、ハイパスは「ひとつ前の値からどれだけ変わったか」を見る計算でもあります。ゆっくりした変化では差が小さく、急な変化では差が大きくなります。だからハイパスは、ゆっくりした低域を落として、速い変化を残します。直接の式は、ローパスの係数を求めたあとで出します。

デジタルでは時間をサンプル数へ置き換える

ここから、ローパスとハイパスをデジタルの計算へ落とします。

コンピューターの中では、音は一定間隔で並んだ数の列です。44.1kHzなら1秒に44100個、48kHzなら1秒に48000個の値が並びます。この「1秒に何個の値を扱うか」がサンプリング周波数です。ここでは $f_s$ と書きます。

1サンプルぶんの時間は、次のようになります。

$$ \Delta t = \frac{1}{f_s} $$

一方、ローパスやハイパスで指定するカットオフ周波数は、音としての周波数です。ここでは $f_c$ と書きます。$f_c = 1000\text{Hz}$ なら、1秒に1000回振動するあたりを境目として考える、という意味です。

ただし、カットオフ周波数は「ここから先を完全に消す壁」ではありません。ローパスなら、そのあたりから高域が落ち始める目安です。ハイパスなら、そのあたりから低域が落ち始める目安です。一次フィルターでは、カットオフ周波数で振幅がおよそ $-3\text{dB}$ になります。

デジタルで必要なのは、$f_c$ を「1サンプル進む間に、どれくらい変化を許すか」という係数へ変換することです。その係数が、さきほどのローパス式に出てきた $a$ です。

カットオフ周波数から係数aを求める

一次ローパスは、「入力へ向かって、少しずつ近づく計算」と見られます。

$$ y[n] = y[n-1] + a(x[n] - y[n-1]) $$

$x[n] - y[n-1]$ は、いまの入力と、ひとつ前の出力の差です。$a$ は、その差のうち何割だけ近づくかを決めます。

  • $a$ が大きい: 入力へ速く近づくので、高い成分まで残りやすい
  • $a$ が小さい: 入力へゆっくり近づくので、急な変化がならされる

つまり、$a$ はただの謎の定数ではなく、「1サンプルごとに入力へ近づく割合」です。

では、この $a$ を $f_c$ からどう作るのか。ここで時定数 $\tau$ を使います。一次ローパスでは、カットオフ周波数と時定数に次の関係があります。

$$ \tau = \frac{1}{2\pi f_c} $$

RC回路で言えば $\tau = RC$ ですが、ここでは抵抗やコンデンサの話ではなく、「どれくらいの時間で反応するフィルターか」を表す量として見れば十分です。

連続時間の一次ローパスは、ざっくり言うと次の関係です。

$$ \frac{dy}{dt} = \frac{x-y}{\tau} $$

左側は「出力が時間に対してどれだけ変わるか」です。右側は「入力と出力の差が大きいほど、出力が速く動く」という意味です。

これをデジタルにするため、微分を1サンプルぶんの差に置き換えます。

$$ \frac{y[n]-y[n-1]}{\Delta t} \simeq \frac{x[n]-y[n]}{\tau} $$

ここから $y[n]$ について解きます。

$$ \tau(y[n]-y[n-1]) = \Delta t(x[n]-y[n]) $$

$$ (\tau+\Delta t)y[n] = \Delta t x[n]+\tau y[n-1] $$

したがって、

$$ \begin{aligned} y[n] &= \frac{\Delta t}{\tau+\Delta t}x[n] \ &+ \frac{\tau}{\tau+\Delta t}y[n-1] \end{aligned} $$

この式を、最初のローパス式と見比べます。

$$ y[n] = ax[n] + (1-a)y[n-1] $$

すると、

$$ a = \frac{\Delta t}{\tau+\Delta t} $$

です。これで、$a$ がどこから出てきたのかがつながります。連続時間の「なめらかに近づく動き」を、1サンプルごとの計算に置き換えた結果です。

さらに、$\Delta t = 1/f_s$、$\tau = 1/(2\pi f_c)$ なので、$a$ は $f_c$ と $f_s$ から計算できます。

$$ a = \frac{2\pi f_c}{f_s + 2\pi f_c} $$

たとえば、$f_s = 44100\text{Hz}$、$f_c = 1000\text{Hz}$ なら、

$$ a \simeq 0.125 $$

です。この値は、「1サンプルごとに入力との差の約12.5%だけ近づく」という意味です。

同じ係数でハイパスも作れる

ローパスの $a$ が決まれば、ハイパスも同じカットオフ周波数で作れます。いちばん分かりやすいのは、まず低域を取り出して、それを元の音から引く方法です。

$$ \begin{aligned} \mathrm{low}[n] &= \mathrm{low}[n-1] \ &+ a(x[n]-\mathrm{low}[n-1]) \end{aligned} $$

$$ \mathrm{high}[n] = x[n]-\mathrm{low}[n] $$

これは「ローパスで残した低域を、入力から引く」というだけです。同じ $a$ を使うので、ローパスとハイパスの境目も同じ $f_c$ になります。

ハイパスだけを直接書く式もあります。

$$ y_{\mathrm{HP}}[n] = \beta(y_{\mathrm{HP}}[n-1]+x[n]-x[n-1]) $$

ここで、

$$ \beta = \frac{\tau}{\tau+\Delta t} $$

です。ローパスの $a$ と見比べると、$\beta = 1-a$ です。ゆっくりした変化では $x[n]-x[n-1]$ が小さくなるので、低域が残りにくくなります。急な変化では差が大きくなるので、高域が残ります。

インパルス応答で尾の長さを見る

インパルス応答は、最初だけ1で、あとは0になる信号を入れたときの反応です。

$$ 1, 0, 0, 0, 0, \dots $$

4点移動平均にこれを入れると、出力は次のようになります。

$$ \frac{1}{4},\frac{1}{4},\frac{1}{4},\frac{1}{4},0,0,\dots $$

4個の値を同じ重みで平均するので、$1/4$ が4回だけ出ます。反応がそこで終わるので、移動平均はFIR、Finite Impulse Responseです。

一方、一次ローパスにインパルスを入れると、最初の反応は $a$ です。次のサンプルでは、その残りに $(1-a)$ がかかります。つまり、反応は次のように続きます。

$$ a,\ a(1-a),\ a(1-a)^2,\ a(1-a)^3,\ \dots $$

$a$ が大きいと、入力へすぐ近づくので尾は短くなります。これはカットオフ周波数が高い状態です。$a$ が小さいと、前の出力を長く引きずるので尾が長くなります。これはカットオフ周波数が低い状態です。

このように、前の出力を次の計算に戻すフィルターは、インパルスへの反応が理論上ずっと続きます。これがIIR、Infinite Impulse Responseです。ここでのIIRは難しい別物ではなく、単に「過去の出力を使うので、反応が尾を引く」という意味です。

デモで確認する

下のデモでは、ノコギリ波、矩形波、ノイズを、ローパスとハイパスへ通して比較できます。カットオフ周波数を動かすと、$f_c$ が $f_s$ に対してどの位置にあるかを計算し直し、音と周波数特性を更新します。

うまく表示されないときは、デモを別タブで開く

周波数特性の図では、上にある帯域ほど通り、下にある帯域ほど小さくなります。ローパスでは高域が落ち、ハイパスでは低域が落ちます。時間波形では、ローパスは角を丸め、ハイパスはゆっくりした揺れを削ります。

ここで扱った一次ローパスと一次ハイパスには、追加の鋭さ調整つまみは置いていません。カットオフ周波数を決め、サンプリング周波数と合わせて係数 $a$ を求める。まずはここが土台です。

参考文献