Smile Engineering Blog

ジェイエスピーからTipsや技術特集、プロジェクト物語を発信します

FIRフィルタを作って周波数特性を検証

適応フィルタを作ってみる では、サンプルコードを紹介しましたが、実際に動かしてみたいと思います。そこで適応フィルタの前に、まずはシンプルに固定フィルタを検証してみます。

FIRフィルタ

f:id:jspnet:20200306005824p:plain:right:w600 適応フィルタを作ってみる で使用したFIR【Finite Impulse Response】のサンプルコードを、まずは固定フィルタで検証してみます。

{
 y(n) = \sum_{k=0}^{N}h_k x(n-k)
}


サンプルコード

サンプルコード【C言語

#define FIR_TAP   15  //  目的に合わせて調整する (今回は15で試してみます)
// clip
static short double_saturate(double in)
{
    if (in > 32767.0) in = 32767.0;
    if (in < -32768.0) in = -32768.0;
    return (short)in;
}
// FIR
short fir_sample(float* coef, short *mem, short in)
{
    int i;
    double sum = 0.;
    short *buff = &mem[FIR_TAP - 1];

    for (i = 0; i < (FIR_TAP - 1); i++) {
        mem[i] = mem[i + 1];
    }
    mem[i] = in;

    // convolution
    for (i = 0; i < FIR_TAP; i++) {
        sum += coef[i] * (double)*buff--;
    }

    return double_saturate(sum);
}

検証用のコード

FIRの動作を確認するための環境を用意します。

サンプルコード

サンプルコード【C言語

ファイル入出力で動かしてみます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

short fir_sample(float* coef, short *mem, short in);  // サンプルコードのプロトタイプ宣言
extern float g_filter_coef[FIR_TAP];  //フィルタ係数
short mem[FIR_TAP];  //フィルタメモリ

int main(int argc, char **argv)
{
    FILE *fp_in;
    FILE *fp_out;

    if((fp_in = fopen(argv[1],"rb"))==NULL){ // PCM入力ファイル
        printf("can not open %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }
    if((fp_out = fopen(argv[2],"wb"))==NULL){ // PCM出力ファイル
        printf("can not open %s\n", argv[2]);
        exit(EXIT_FAILURE);
    }
    for (int i = 0; i < FIR_TAP; i++) mem[i] = 0; // フィルタメモリ初期化

    while(1) {
        short in, out;
        if (!fread(&in, sizeof(short), 1, fp_in))break; // PCM入力(16bitモノラル)
        out = fir_sample(g_filter_coef, mem, in); // FIRサンプルコード
        fwrite(&out, sizeof(short), 1, fp_out); // PCM出力(16bitモノラル)
    }
    fclose(fp_in);
    fclose(fp_out);
    return EXIT_SUCCESS;
}

フィルタスルーで動作確認

上記の検証用コードで、フィルタ係数をg_filter_coef[FIR_TAP]としました。この係数によってフィルタ特性が決まります。 ADF【Adaptive Filter】では、これを適応アルゴリズムで自動で求め更新して行くわけですが、 固定フィルタなので、フィルタを設計してこの係数を求めます。

まずは、簡単にフィルタをスルーにして基本的な動作を確認してみます。例えば、フィルタ係数の5番目を1.0、それ以外を0.0にします。

float g_filter_coef[FIR_TAP] = { 
 0., 0., 0., 0., 1.0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0. 
};

Audacityで正弦波を作ってフィルタに入力

検証用の入力音源の作成にはAudacityを使用しました。 Audactiyについてはこちらを Audacityを使ってみました - Smile Engineering Blog

Audacityで正弦波の生成

【ジェネレーター】➞【トーン】
Audacityで正弦波を作ってフィルタに入力してみます。周波数は適当に1000(Hz)です。 Audacityで周波数分析

トーン 設定 説明
波形: サイン波 波形の種類
周波数(Hz): 1000 とりあえず1kHz
振幅(0-1): 0.8 0~1.0の値

カーソルを波形の上に置き、Ctrl+スクロールで波形が拡大されます。

f:id:jspnet:20191129001202j:plain
正弦波

AudacityでPCMファイルの保存

【ファイル】➞【Export】➞【オーディオの書き出し】

f:id:jspnet:20200613012723p:plain:right:w480 生成した正弦波をファイル保存します。
「ファイルの種類」は、【その他の非圧縮ファイル】として、今回はファイル名をsin_1kHz.pcmとしました。
フォーマットオプションは、「ヘッダ」を【RAW (header-less)】、「エンコーディング」を【Signed 16-bit PCM】です(リニアPCMでwavヘッダ無しで使用したいため)。

f:id:jspnet:20200604005808p:plain:right:w620 この時のサンプリング周波数を覚えておいて下さい。読み込む時も同じ周波数です。
今回は48kHz(モノラル)にしています。 Audacityを使ってみました

実行方法

ビルドした実行プログラムが、"fir_test.exe"の場合の起動方法は次です。

> fir_test.exe sin_1kHz.pcm out.pcm

AudacityでPCMファイルの読み込み

【ファイル】➞【 取り込み】➞【 ロー (Raw) データの取り込み】
出力されたout.pcmを、Audacityで読み込んで波形表示してみます。PCMファイルの読み込みは保存した時と同じファイル形式で、 サンプリング周波数も保存した時と同じ周波数です。

f:id:jspnet:20200604010128p:plain

フィルタ出力(out.pcm)の波形は、入力(原音: sin_1kHz.pcm)に対し5サンプル遅れた波形になります。いわゆるインパルス応答が、遅延5サンプルというイメージです。係数の1.0を0.5にすれば半分の振幅になります(-6dB)。

f:id:jspnet:20200604010359p:plain
(上)フィルタ処理前、(下)フィルタ処理後

ローパスフィルタを検証

とは言っても、フィルタ設計は簡単には行かないので、 こちら を参考にします。 フィルタの種類はローパスフィルタ(Low Pass Filter: LPF)にして、フィルタ長:N(タップ)は15にしました。今度はこのフィルタ係数をg_filter_coef[FIR_TAP] として使用します。ローパスフィルタはサンプリング変換で使う機会が多いです。

// フィルタ長 : N = 15
// フィルタの種類 : LPF
// 窓の種類 : Hamming
// 正規化遮断周波数 : 0.2
float g_filter_coef[15] = {
    2.138261220088319e-03,
    6.334857723412623e-03,
    - 3.947985652659257e-18,
    - 3.312179298358531e-02,
    - 4.006136998985733e-02,
    7.734675062134251e-02,
    2.889400004322881e-01,
    4.000000000000000e-01,
    2.889400004322881e-01,
    7.734675062134251e-02,
    - 4.006136998985733e-02,
    - 3.312179298358531e-02,
    - 3.947985652659257e-18,
    6.334857723412623e-03,
    2.138261220088319e-03
};

Audacityでホワイトノイズを生成してフィルタ特性を調べる

【ジェネレータ】➞【ノイズ】(デジタル信号処理とは、Audacityでフィルタを使ってみる)

フィルタの周波数特性を見るには、ホワイトノイズ(White noise)という全ての周波数成分が同じ強度で含まれる雑音を使用すると良く分かります。

ノイズ 設定 備考
ノイズの種類: ホワイト 乱数によって生成できます
振幅(0-1): 0.5 0.0~1.0を指定、信号の振幅です

Audacityスペクトラム表示

【解析】➞【 スペクトラム表示】(リニア周波数軸)(Audacityで周波数分析

Audacityスペクトラム表示で周波数特性を見ると、全ての周波数成分があることが分かります。

f:id:jspnet:20200131225438j:plain
ホワイトノイズ

実行方法

Audacityで生成したホワイトノイズを正弦波と同様の手順でファイル保存し、このホワイトノイズをFIR(ローパスフィルタ)に入力してみます。次はホワイトノイズのファイル名をwnoise.pcmとした例です。

> fir_test.exe wnoise.pcm out.pcm

ローパスフィルタの結果(out.pcm)も、Audacityで読み込んでスペクトラム表示で波形を見ます。高域が減衰される周波数特性が分かります。

f:id:jspnet:20200604010631p:plain
ローパスフィルタ