vDSPで高速フーリエ変換を行う関数の使い方です。
vDSP One-Dimensional Fast Fourier Transforms Referenceというリファレンスを見ると、一次元のフーリエ変換だけで30個ほどの関数が用意されています。
大きく分けてRealとComplexがあり、そのそれぞれがさらに、In-Place(変換前のバッファにそのまま上書き)かOut-of-Place(変換前と後で別々のバッファ)かで分かれています。さらに細かく見るとfloat用とdouble用とか、一度にたくさん処理するものとか、いろいろあります。
今回はIn-PlaceでComplexのfft_zip()を使ってみます。
ところで、このfft_zip()関数などは、リファレンスには関数名にvDSP_がついていますが、コード補完では引数付きの状態では出てこなくて、vDSP_がつかないfft_zipで出てきたりします。
//fft_zip()関数
void vDSP_fft_zip (FFTSetup setup, //FFTセットアップ
DSPSplitComplex * ioData, //複素数配列の構造体
vDSP_Stride stride, //ストライド
vDSP_Length log2n, //FFTサイズ
FFTDirection direction); //正変換か逆変換か
だいたいのvDSPの関数は、その関数のみで完結しますが、FFTを使うにはvDSP Libraryというリファレンスでもわざわざ使い方が解説されているように、ちょっとした準備と後始末が必要です。
まず、Accelerateフレームワークをインポートしておきます。
#import <Accelerate/Accelerate.h>
create_fftsetup()という関数で、FFTSetupというインスタンスのようなものを作成します。第一引数で指定するFFTのサイズは2の何乗か、になります。9なら512サンプル、10なら1024サンプルといった感じです。使う関数によって指定できるサイズの範囲が決まっています。
第二引数ではradixを指定します。今回はFFT_RADIX2を指定します。ほかにFFT_RADIX3とFFT_RADIX5もあり、FFTするサンプル数が合えば(2の累乗の3倍とか5倍とか)、こちらを使う事もできるようです。
//FFTするサイズを2の何乗かで指定する。この場合1024サンプル。
vDSP_Length log2n = 10;
//FFTセットアップを作成する
FFTSetup fftSetup = create_fftsetup(log2n, FFT_RADIX2);
複素数を実数と虚数を別々の配列でメンバに持つDSPSplitComplexという構造体を宣言し、realp(実数配列)とimagp(虚数配列)に、それぞれ変換するデータを入れたメモリ領域を割り当てます。
//FFTを行うデータを作成する
vDSP_Length fftSize = 1 << log2n;
DSPSplitComplex splitComplex;
splitComplex.realp = calloc(fftSize, sizeof(float));
splitComplex.imagp = calloc(fftSize, sizeof(float));
/*
ここでsplitComplex.realpとimagpに、変換するデータをコピーする
*/
変換する方向を指定します。正変換ならFFT_FORWARD、逆変換ならFFT_INVERSEです。ストライドも指定します。ここでは連続したデータと想定して1にしておきます。
//FFTか逆FFTを指定する。逆ならFFT_INVERSE
FFTDirection direction = FFT_FORWARD;
//ストライドを指定する
vDSP_Stride signalStride = 1;
fft_zip()関数で変換を行います。
//FFTを行う
fft_zip(fftSetup, &splitComplex, signalStride, log2n, direction);
なお、fft_zip()関数において逆変換をした場合には、FFTサイズ倍の大きさのレベルそのままでデータが返ってきていますので、FFTサイズで割ります。
//逆FFTの場合FFTサイズでシグナルを割る
if (direction == FFT_INVERSE) {
float scale = 1.0 / fftSize;
vsmul(splitComplex.realp, 1, &scale, splitComplex.realp, 1, fftSize);
vsmul(splitComplex.imagp, 1, &scale, splitComplex.imagp, 1, fftSize);
}
/*
ここでsplitComplexからデータを取り出す
*/
このvsmul()という関数は、float配列の全要素に対して、同じ数をかけ算する関数です。このようにIn-Placeでも使えますし、アウト側に別の配列を渡してOut-of-Placeで使う事もできます。
最後にdestroy_fftsetup()でFFTセットアップを解放します。
//FFTセットアップを解放する
destroy_fftsetup(fftSetup);
free(splitComplex.realp);
free(splitComplex.imagp);
続けて変換を行うなら、fft_zipを繰り返して終わった後で解放すれば良いと思います。FFTをクラスとして作ってしまうなら、initでcreateして、deallocでdestroyという感じでしょうか。
と、単純にフーリエ変換はこんなところですが、フーリエ変換時によく使われる窓関数をかける関数もvDSPに用意されています。
vDSP_blkman_window (ブラックマン窓)
vDSP_hamm_window (ハミング窓)
vDSP_hann_window (ハニング窓)
という3種類(double用も含めると6種類)です。