Mac OS XのAccelerateフレームワークには、連続したデータの配列にデジタル信号処理をするときなどに便利な関数がいろいろ用意されています。Accelerateフレームワークを使えば1つの記述をするだけで、内部で勝手に判断してAltiVecやSSE等で素早く処理を行ってくれるようです。場合によってはアクセラレートしない事もありますが、いちいち自分でfor文を回したりしなくても良くて、処理が速くなる可能性があるのですから、使わない手はありません。
では、Accelerateフレームワークの中にあるvDSP_deq22()という関数でbiquadのフィルタをかけるクラスを作ってみます。vDSP_deq22()関数は以下のように宣言されています。
void vDSP_deq22 (
float * A, //インプットする配列
vDSP_Stride I, //インプットのストライド
float * B, //係数の配列
float * C, //アウトプットされる配列
vDSP_Stride K, //アウトプットのストライド
vDSP_Length N //処理するサイズ
);
これはfloat配列用ですが、double配列用にvDSP_deq22D()という関数も用意されています。基本的にvDSPの関数はDがついているのがdouble用で、ついてないのがfloat用という名前のつけ方になっているようです。
まず、Accelerateフレームワークを追加しインポートします。
#import <Accelerate/Accelerate.h>
オーディオデータを細切れに分けて処理をすることを考え、使い回すデータのインスタンス変数を用意します。
@interface IIRUnit : NSObject {
float *gCoefBuffer; //係数の配列
float *gInputKeepBuffer; //インプット2サンプル分のバッファ
float *gOutputKeepBuffer; //アウトプット2サンプル分のバッファ
}
- (id)init
{
self = [super init];
if (self != nil) {
gInputKeepBuffer = calloc(2, sizeof(float));
gOutputKeepBuffer = calloc(2, sizeof(float));
gCoefBuffer = calloc(5, sizeof(float));
}
return self;
}
- (void)dealloc
{
free(gInputKeepBuffer);
free(gOutputKeepBuffer);
free(gCoefBuffer);
[super dealloc];
}
biquadフィルタでは処理するデータに対して2サンプル前と1サンプル前のデータが必要なので、その配列を用意します。インプット用2サンプル分とアウトプット用2サンプル分です。
次に5個の係数の配列を用意します。係数の順番はB0、B1、B2、A1、A2です。6個の係数を使用する場合は先に5つの係数をA0で割っておきます。フィルタやEQの係数の求め方はAudio-EQ-Cookbook等を参考にしてください。
http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
それでは、処理を行うメソッドです。
- (void)processWithIoData:(float *)ioData frames:(NSUInteger)frames
{
//処理用のバッファを用意する
float *tInputBuffer = malloc((frames + 2) * sizeof(float));
float *tOutputBuffer = malloc((frames + 2) * sizeof(float));
//処理用のバッファにデータをコピー
memcpy(tInputBuffer, gInputKeepBuffer, 2 * sizeof(float));
memcpy(tOutputBuffer, gOutputKeepBuffer, 2 * sizeof(float));
memcpy(&(tInputBuffer[2]), ioData, frames * sizeof(float));
//処理を行う
vDSP_deq22(tInputBuffer, 1, gCoefBuffer, tOutputBuffer, 1, frames);
//処理後のデータをコピー
memcpy(ioData, tOutputBuffer, frames * sizeof(float));
memcpy(gInputKeepBuffer, &(tInputBuffer[frames]), 2 * sizeof(float));
memcpy(gOutputKeepBuffer, &(tOutputBuffer[frames]), 2 * sizeof(float));
free(tInputBuffer);
free(tOutputBuffer);
}
処理したいシグナルデータの配列のサイズ+2サンプル分のサイズの配列をインプットA用とアウトプットC用に用意します。それぞれ頭の2サンプルには、前回処理した最後の2サンプルをコピーしておきます。3サンプル目以降に元のデータをコピーします。
ストライドI、Kはデータをいくつ飛ばしで読み込みor書き込みをするかを指定します。シグナルデータがNotInterleavedなモノラルの連続したデータであれば、ストライドは1です。もし、StereoのInterleavedなデータ(LRLRLR…の順番)の片方のチャンネルを処理するのであれば、ストライドは2です。StereoのRchとかならストライドを2にしつつ、vDSP_deq22関数に渡す配列のポインタをRchの先頭位置に持っていかなければいけません。
処理サイズNは、2サンプル足さない元データのサイズ(バイト数ではなくサンプル数)を指定します。
vDSP_deq22()関数で処理したら、処理前のデータA、処理後のデータCのそれぞれ最後の2サンプルを次の処理用にインスタンス変数にコピーしておきます。
処理後のデータCの3サンプル目から必要に応じてコピーします。今回はもとのデータを上書きしています。