vDSPを使ったFFTをより簡単に使うためにラッパークラスを作っています。前回の記事の続きで、実際に作ったクラスの内容は下記のようになります。
MyFFT.h
//
// MyFFT(revision 1)
// MyFFT.h
// http://nantekottai.com/
//
#import
#import
@interface MyFFT : NSObject {
DSPSplitComplex splitComplex;
FFTSetup fftSetup;
unsigned int capacity;
unsigned int capacityN; //capacityが2の何乗であるかを保持
float* window;
float* windowedInput;
}
@property (assign) unsigned int capacity;
@property (readonly) float* realp;
@property (readonly) float* imagp;
- (void)process:(float*)input;
@end
MyFFT.m
//
// MyFFT.m
// http://nantekottai.com/
//
#import "MyFFT.h"
@implementation MyFFT
@synthesize capacity;
- (float*)realp
{
return splitComplex.realp;
}
- (float*)imagp
{
return splitComplex.imagp;
}
- (id)initWithCapacity:(unsigned int)aCapacity
{
if (self = [super init]) {
// aCapacityが2のn乗になっているか調べます
// aCapacityが2のn乗になっていない場合は、
// 2のn乗になるように調整します。
// (厳密にやりたい場合は0のチェック等も行ってください)
capacityN = log(aCapacity) / log(2);
capacity = 1 << capacityN;
NSLog(@"capacity: %d n: %d", capacity, capacityN);
// FFTの設定をします
fftSetup = vDSP_create_fftsetup(capacityN+1, FFT_RADIX2);
// FFTに使う配列を用意します
splitComplex.realp = calloc(capacity, sizeof(float));
splitComplex.imagp = calloc(capacity, sizeof(float));
// 窓用の配列を用意します
window = calloc(capacity, sizeof(float));
windowedInput = calloc(capacity, sizeof(float));
// 窓を作ります
vDSP_hann_window(window, capacity, 0);
}
return self;
}
- (void)process:(float*)input
{
// 窓をかけます
vDSP_vmul(input, 1, window, 1, windowedInput, 1, capacity);
// 複素数に変換します
for (int i=0; i<capacity; i++) {
splitComplex.realp[i] = windowedInput[i];
splitComplex.imagp[i] = 0.0f;
}
// フーリエ変換します
vDSP_fft_zrip(fftSetup, &splitComplex, 1, capacityN+1, FFT_FORWARD);
}
- (void)dealloc
{
// FFTに使う配列を解放します
free(splitComplex.realp);
free(splitComplex.imagp);
// 窓用の配列を解放します
free(window);
free(windowedInput);
// FFTの設定を削除します
vDSP_destroy_fftsetup(fftSetup);
[super dealloc];
}
@end
使用例
MyFFT* fft = [[[MyFFT alloc] initWithCapacity:512] autorelease]; // 解析用の信号 float inputWave[512]; float phase = 0.0f; float freq = 30.0f; for (int i=0; i
このように信号を一回解析するだけの使用方法では、MyFFTクラスを使うメリットが感じにくいですが、リアルタイムに音声を解析するような場合には効果を発揮してきます。次はいよいよリアルタイムで音声を解析してみます。
大変参考になりました。
このクラスを使ってリアルタイムにデータをFFTしてみて質問です。
AudioUnit(RemoteIO)のコールバック内で取得したデータについて
FFTをこのクラスで行ってみました。
使い方としてMyFFTのインスタンスはコールバック関数内で、
毎回autorelease(もしくは明示的にrelease)するという方向でしょうか?
コントローラクラスの初期化時にメンバ変数として保持し、
コールバック関数内では、
[fft process:inputWave];
// 周波数特性を求めます
float frequencyProperty[512];
vDSP_vdist(fft.realp, 1, fft.imagp, 1, frequencyProperty, 1, 512);
のみを実行するようにしてしまうと、
FFT結果の入る配列frequencyPropertyの値が肥大して行き、
すぐにnan値になってしまうので…
宜しくお願い致します。
配列の値が肥大していきnanになってしまう、というのがどういうことなのかよくわかりませんが、信号の長さがかわらないようであれば、基本的にはMyFFTインスタンスは毎回生成する必要はないので、一度作ったインスタンスを使い続ければよいです。
今回の記事で、vDSP_create_fftsetup()等のパラメータで capacityN+1 のように+1されているのはなぜでしょうか?
AppleのAPI仕様を見ても、以前の記事の流れからしても、+1は不要のように思えるのですが。