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 は用意したいところです。