ラッパークラス

アップサンプラーとダウンサンプラーのプログラムはできました。それぞれC言語から呼び出し可能でいろいろな局面に対応可能な柔軟さを持っていますが、このままではあまりにも使いにくく何とかする必要があります。

そこでこれら関数のためにラッパー(Wrapper)クラスを用意して、もっと簡単に使えるようにしてみます。

一番親の型

親になる型はCFilterです。この型は子孫の型のためにフィルターとしてのインターフェースを整えます。CFIlterは内部にディレイラインとインパルス応答などの変数を持ち、フィルターとしての必要な情報をすべてそろえています。そのため、CFilterの子孫の型は容易に複数インスタンス化できます。これによってシステム内にいくつでも任意の数のフィルターを作ることができます。

                                // フィルター用の抽象クラス
class CFilter{
protected:
    short * h;                  // インパルス応答
    short * delayline;          // 遅延線
    short * curptr;             // 現在の遅延線上のポインタ
    int taps;                   // タップ数
public:
    CFilter(                    // コンストラクタ
        int taps,               // タップ数を指定
        short *coeffInit );     // インパルス応答の初期値を与える
    ~CFilter();                 // デストラクタ
    virtual void run(           // フィルター実行関数
        short * input,          // 入力データ列へのポインタ
        int inputStep,          // 入力データのステップ(間隔)
        short * output,         // 出力データ列へのポインタ
        int outputStep,         // 出力データのステップ(間隔)
        int count               // 入力データ数
        )=0;
};

CFilterはrunメソッドを持っていますが、これは純粋仮想メソッドなので実体はありません。コンストラクタとデストラクタのみ実装します。コンストラクタはインパルス応答用に領域を用意し、与えられた係数をコピーします。また、taps分のディレイラインを用意し、クリアします。インパルス応答とディレイラインはいずれもヒープから確保されます。そのため、これらはデュアルロード時に直列化され、1サイクルのストールを起こします。アナログデバイセズが提供しているデュアルヒープを使えばこれは避けられますが、とりあえずこの実装としておきます。

デストラクタは確保したヒープを開放します。

CFilter::CFilter( int taps, short *coeffInit )
{
    h=new short[taps];
    delayline=new short[taps];
    curptr = delayline;
    this->taps=taps;
        
    for ( int i= 0; i<taps; i++ )
        h[i]=coeffInit[i];
    for ( int i= 0; i<taps; i++ )
        delayline[i]=0;
}

CFilter::~CFilter()
{
    delete[] h;
    delete[] delayline;
}

アップサンプラ型

アップサンプラは内部にratio変数とtapsDivRatio変数を保持します。これらはアセンブリ関数に渡されます。

class CUpSampler : public CFilter{
private:
    int ratio;
    int tapsDivRatio;
public:
    CUpSampler(                     // コンストラクタ
        int taps,                   // ratioの整数倍でなければならない
        short * coeffInit, 
        int ratio                   // アップサンプル比 L 
        );
    virtual void run (      
        short* input, 
        int inputStep, 
        short* output, 
        int outputStep, 
        int count
        );
};

コンストラクタで注目すべき点はCFilter::CFilterで確保したディレイラインをいったん開放し、再割り当てする点です。これは、ディレイラインに必要なメモリー量がtaps/ratioであるためで、再割り当てすることによりヒープを節約することができます。

実際にフィルター処理を行うrunメソッドは単にパラメータをアセンブリファンクションに渡すだけです。この処理はヘッダーファイルでインライン処理として記述してもいいのですが、内部の処理量を考えると呼び出しオーバーヘッドは無視できますので記述のきれいさを選ぶことにしました。

// Up sampler implementation by poly phase filter.

CUpSampler::CUpSampler( int taps, short * coeffInit, int ratio )
                        :CFilter( taps, coeffInit )
{
    this->ratio = ratio;
    tapsDivRatio = taps / ratio;
    delete[] delayline;                     // Doesn't need taps size.
    delayline= new short[tapsDivRatio];
}

void CUpSampler::run( short* input, int inputStep, short* output, 
                                        int outputStep, int count)
{
    polyphaseUpSamplerFunc(
        input,
        inputStep,
        output,
        outputStep,
        count,
        ratio,
        delayline,
        h,
        taps,
        &curptr,
        tapsDivRatio
        );
}

ダウンサンプラ型

アップサンプラ型に輪をかけて簡単です。

class CDownSampler : public CFilter{
private:
    int ratio;
public:
    CDownSampler(                   // コンストラクタ
        int taps,
        short * coeffInit, 
        int ratio                   // ダウンサンプル比 M
        );
    virtual void run (      
        short* input, 
        int inputStep, 
        short* output, 
        int outputStep, 
        int count                   // ratio の整数倍でなければならない
        );
};

コンストラクタはディレイラインの再割付が不要ですので増えた内部変数を初期化するだけです。また、runメソッドは渡すパラメータが少し違うだけで、ほとんど同じです。

// Down sampler implementation. 

CDownSampler::CDownSampler( int taps, short * coeffInit, int ratio )
                        :CFilter( taps, coeffInit )
{
    this->ratio = ratio;
}

void CDownSampler::run( short* input, int inputStep, short* output, 
                                        int outputStep, int count)
{
    downSamplerFunc(
        input,
        inputStep,
        output,
        outputStep,
        count,
        ratio,
        delayline,
        h,
        taps,
        &curptr,
        count / ratio
        );
}

FIRフィルタ型

ついでですのでこれまで作りっぱなしだったFIRフィルタをCFilterの子の型として統合しました。これでFIRフィルタ、アップサンプラ、ダウンサンプラをフィルター型として統一的に使うことができます。オブジェクト指向化はオーバーヘッドが増えるとして今でも嫌う向きがあります。しかし単一インターフェースで多くの機能を部品化できるという特徴は、趣味でも仕事でも大きく生産性を押し上げます。オーバーヘッドの問題は処理をブロック化すれば解決する話ですので

class CFirFilter : public CFilter{
public:
    CFirFilter(                     // コンストラクタ
        int taps, 
        short * coeffInit 
        );
    virtual void run (
        short* input,           
        int inputStep, 
        short* output, 
        int outputStep, 
        int count
        );
};

コンストラクタもrunメソッドも特に記すべきことはありません。

// FIR フィルタークラス

CFirFilter::CFirFilter( int taps, short * coeffInit )
        :CFilter( taps, coeffInit )     // 親クラスのコンストラクタを継承
{                                       // 独自処理はない
}

                                        // フィルター処理
                                        // 入出力バッファとデータ間隔(ステップ)、
                                        // 処理カウント数を与える
void CFirFilter::run( short* input, int inputStep, short* output, 
                                        int outputStep, int count
            )                                               
{
    firFilterFunc(
        input,
        inputStep,
        output,
        outputStep,
        count,
        delayline,
        h,
        taps,
        &curptr
        );
}

まとめ

長かったレート変換もやっとまとまりました。出来上がったクラス群をライブラリ化してダウンロードできるようにしておきます。filt.hをインクルードして使ってください。クラスはすべて名前空間filterの中で定義されています。

ダウンロード:polyphase.zip 12.4KB

2191空挺団 | プログラム | EZ-KIT | こぼれ話 | アーキテクチャー | 命令 | レジスタ | DSP掲示板 | FAQ |