IIRローパスフィルターについて概要を見てきました。ここでは係数の計算を整理して実装に入りたいと思います。
係数の計算
前回の通り遮断する周波数をサンプルレートを加味して計算する方法は以下の式です。
遮断変数fc = tan( π * cutoff / sample_rate) / 2π
各係数は分数の形で計算されますがすべての係数で分母が同じためまずこれを計算します。Qはクオリティファクターです。
分母denom = 1 + 2πfc / Q + 4π2fc2
以下のように係数を計算します。
b0 = 4π2fc2 / denom
b1 = 8π2fc2/ denom
b2 = 4π2fc2 / denom
a1 = (8π2fc2 – 2) / denom
a2 = (1 – 2πfc / Q + 4π2fc2) / denom
これらの式の導き方についてはフィルターの設計関連の書籍を参考にしてください。
実装
まずフィルターの理論に関わる部分と変換処理を実装するクラスを作り、あとでこのクラスを使ってフィルタリングするようにします。
package mocha.sound.effect; import mocha.sound.DoubleBuffer; import static java.lang.Math.PI; import mocha.sound.SoundConstants; public class IIRLowPassFilter implements SoundConstants {
フィールド変数
一般的なクオリティファクターの値をstatic変数にしておきます。
public static final double DEFAULT_QUALITY = 1.0 / Math.sqrt(2.0);
過去のサンプルのバッファーです。バッファーbは読み込んだ値、バッファーaは書き出した値です。
DoubleBuffer buffer_b; DoubleBuffer buffer_a;
係数coefficientのための変数配列です。
double[] coef_b; double[] coef_a;
現在設定されている遮断周波数とクオリティファクターです。
double cutoff; double quality;
メソッド
コンストラクタ
過去サンプルのバッファーには2履歴分保持するようにします。係数の配列の長さは3にします。ただし係数aの0番目は使いません。updateメソッドで係数の計算をします。
public IIRLowPassFilter(double cutoff) { this(cutoff, DEFAULT_QUALITY); } public IIRLowPassFilter(double cutoff, double quality) { this.cutoff = cutoff; this.quality = quality; buffer_b = new DoubleBuffer(2); buffer_a = new DoubleBuffer(2); coef_b = new double[3]; coef_a = new double[3]; update(); }
係数の計算
係数を計算するメソッドです。外から触ることはないのでprivateにしています。遮断周波数もしくはクオリティファクターが変更された場合には再計算が必要です。
private void update(){ double fc = Math.tan(PI * cutoff / SAMPLE_RATE) / (2.0 * PI); double denom = 1.0 + 2.0 * PI * fc / quality + 4.0 * PI * PI * fc * fc; coef_b[0] = 4.0 * PI * PI * fc * fc / denom; coef_b[1] = 8.0 * PI * PI * fc * fc / denom; coef_b[2] = 4.0 * PI * PI * fc * fc / denom; coef_a[0] = 0; coef_a[1] = (8.0 * PI * PI * fc * fc - 2.0) / denom; coef_a[2] = (1.0 - 2.0 * PI * fc / quality + 4.0 * PI * PI * fc * fc) / denom; }
設定の更新
現在の値と違う場合だけ再計算するようにして処理を軽減しています。
public void set(double cutoff, double quality){ if(this.cutoff == cutoff && this.quality == quality){ return; } this.cutoff = cutoff; this.quality = quality; update(); } public void setQuality(double quality){ if(this.quality == quality){ return; } this.quality = quality; update(); } public void setCutoff(double cutoff){ if(this.cutoff == cutoff){ return; } this.cutoff = cutoff; update(); }
フィルタリング
実処理になります。与えられた入力値を元に出力値を計算していきます。入力値と出力値はそれぞれバッファに保持するようにします。
public double process(double read_value) { double return_value = 0; return_value += read_value * coef_b[0]; for (int i = 1; i < 3; i++) { return_value += buffer_b.get(i - 1) * coef_b[i]; return_value -= buffer_a.get(i - 1) * coef_a[i]; } buffer_b.put(read_value); buffer_a.put(return_value); return return_value; }
次回はこれを呼び出す側の処理を作ります。