vDSPライブラリを用いた振幅スペクトルの算出方法

iOSアプリ「オーディオ/スペクトル アナライザ」をリリースしました。
https://apps.apple.com/jp/app/audio-spectrum-analyzer/id1508848574

マイクから入力したオーディオ信号の「オーディオ波形」と「振幅スペクトル」(FFT演算結果の周波数ごとの大きさ)の同時表示に対応したアプリです。

本アプリの振幅スペクトルの算出は、vDSPライブラリを用いています。実際の振幅スペクトル部分の実装をまとめたものを公開します。

変数の定義

#define MAX_FFT_SIZE 16384

// FFT入力サンプル数
long lFftSize;
// vDSPでのFFTSetup構造体
FFTSetup fftSetup;

// 使用する窓関数のindex
int windowType;

// 使用するバッファ
float *inputBuffer;
float *windowBuffer;
float *analysisBuffer;
COMPLEX_SPLIT *complexSplitBuffer;
float *spectrumBuffer;

初期化

// FFT入力サンプル数
lFftSize = 2048;	// 1024, 2048, 4096, ...

// vDSPでのFFTの初期化
fftSetup = vDSP_create_fftsetup(log2f((float)lFftSize), FFT_RADIX2);

windowType = (使用する窓関数のindex);

// 使用するバッファ領域
inputBuffer = (float *)calloc(MAX_FFT_SIZE, sizeof(float));
windowBuffer = (float *)calloc(MAX_FFT_SIZE, sizeof(float));
analysisBuffer = (float *)calloc(MAX_FFT_SIZE, sizeof(float));
complexSplitBuffer = (COMPLEX_SPLIT *)calloc(1, sizeof(COMPLEX_SPLIT));
complexSplitBuffer->realp = (float *)calloc(MAX_FFT_SIZE, sizeof(float));
complexSplitBuffer->imagp = (float *)calloc(MAX_FFT_SIZE, sizeof(float));
spectrumBuffer = (float *)calloc(MAX_FFT_SIZE, sizeof(float));

振幅スペクトルの算出

オーディオデータを取得するコールバック関数内で、以下の処理を行います。
正しい計算結果を取得するための大切なポイントとして、入力オーディオデータ inputBuffer がFFT入力サンプル数分だけデータをコピーしたタイミングで呼び出すことにあります。

inputBuffer = (入力オーディオデータ);

// FFTバンド数
long lFftHalfSize = lFftSize / 2;

// 窓関数の定義
if (windowType == 0) {
	vDSP_hann_window(windowBuffer, lFftSize, 0);
} else if (windowType == 1) {
	vDSP_hamm_window(windowBuffer, lFftSize, 0);
} else if (windowType == 2) {
	vDSP_blkman_window(windowBuffer, lFftSize, 0);
}

// 窓関数の計算 (配列同士の掛け算)
vDSP_vmul(inputBuffer, 1, windowBuffer, 1, analysisBuffer, 1, lFftSize);

// DSPComplex 配列から DSPSplitComplex に変換
vDSP_ctoz((COMPLEX *)analysisBuffer, 2, complexSplitBuffer, 1, lFftHalfSize);

// FFT forward (FFTを計算)
vDSP_fft_zrip(fftSetup, complexSplitBuffer, 1, log2(lFftSize), FFT_FORWARD);

// スケーリング(ベクトルとスカラーの乗算)
Float32 fFFTNormFactor = 1.0 / (2 * lFftSize);
vDSP_vsmul(complexSplitBuffer->realp, 1, &fFFTNormFactor, complexSplitBuffer->realp, 1, lFftHalfSize);
vDSP_vsmul(complexSplitBuffer->imagp, 1, &fFFTNormFactor, complexSplitBuffer->imagp, 1, lFftHalfSize);

// 実部と虚部の自乗和のルート
vDSP_zvabs(complexSplitBuffer, 1, spectrumBuffer, 1, lFftHalfSize);

// リニア値からdBへの変換
Float32 fOne = 1;
vDSP_vdbcon(spectrumBuffer, 1, &fOne, spectrumBuffer, 1, lFftHalfSize, 1);

// spectrumBuffer が振幅スペクトルの結果(正規化済)
// (inputBuffer がlFftSizeサンプル、spectrumBuffer がlFftHalfSizeサンプル)
 

入力オーディオデータ inputBuffer が 2048 サンプルの場合、振幅スペクトル spectrumBuffer は 1024 サンプルになります。

spectrumBuffer の各値の周波数値は、ナイキスト周波数(サンプリング周波数半分)によって決まります。
サンプリング周波数を 44100 Hzとした場合、周波数値は以下のようになります。

ナイキスト周波数 / FFTバンド数 * インデックス = 周波数値
1 : 22050 / 1024 * 1 = 21 Hz
2 : 22050 / 1024 * 2 = 43 Hz
3 : 22050 / 1024 * 3 = 65 Hz
4 : 22050 / 1024 * 4 = 86 Hz
5 : 22050 / 1024 * 5 = 108 Hz
6 : 22050 / 1024 * 6 = 129 Hz
7 : 22050 / 1024 * 7 = 151 Hz
8 : 22050 / 1024 * 8 = 173 Hz
9 : 22050 / 1024 * 9 = 194 Hz
10 : 22050 / 1024 * 10 = 215 Hz
11 : 22050 / 1024 * 11 = 237 Hz
12 : 22050 / 1024 * 12 = 258 Hz
13 : 22050 / 1024 * 13 = 280 Hz
14 : 22050 / 1024 * 14 = 301 Hz
15 : 22050 / 1024 * 15 = 323 Hz
16 : 22050 / 1024 * 16 = 345 Hz
17 : 22050 / 1024 * 17 = 366 Hz
18 : 22050 / 1024 * 18 = 388 Hz
19 : 22050 / 1024 * 19 = 409 Hz
20 : 22050 / 1024 * 20 = 431 Hz
21 : 22050 / 1024 * 21 = 452 Hz
 ...
512 : 22050 / 1024 * 512 = 11025 Hz
 ...
1024 : 22050 / 1024 * 1024 = 22050 Hz

この結果から、サンプリング周波数 44100 Hz、サンプル数 2048 の場合の振幅スペクトルがチューナーとしてどのくらい使えるかどうかを確認してみます。

A  : 55 * 2^(24/12) = 220 Hz
Bb : 55 * 2^(25/12) = 233 Hz
B  : 55 * 2^(26/12) = 247 Hz
C  : 55 * 2^(27/12) = 262 Hz
Db : 55 * 2^(28/12) = 277 Hz
D  : 55 * 2^(29/12) = 294 Hz
Eb : 55 * 2^(30/12) = 311 Hz
E  : 55 * 2^(31/12) = 330 Hz
F  : 55 * 2^(32/12) = 349 Hz
Gb : 55 * 2^(33/12) = 370 Hz
G  : 55 * 2^(34/12) = 392 Hz
Ab : 55 * 2^(35/12) = 415 Hz
A : 55 * 2^(36/12) = 440 Hz

このように、1オクターブ(12階音)の周波数値と比べてみると、残念ながらサンプル数 2048 の場合ではチューナーとして使うには厳しいことがわかります。もし振幅スペクトルの方法でチューナーを実用化させるには、サンプル数は 16384 は用意したいところです。

コメントを残す

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