高速フーリエ変換

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種類)です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です