Swiftで高速フーリエ変換(FFT)を実装する方法

  • このエントリーをはてなブックマークに追加

iOSデバイスで高速フーリエ変換を実装する方法を紹介します。この記事では、まずはじめに一般的な高速フーリエ変換(FFT)であるCooley-Tukey法をSwiftで検証し、その後Accelerateというライブラリによる実装方法も併せて紹介します。

こんにちは。wat(@watlablog)です。この記事ではSwiftによる高速フーリエ変換について色々まとめてみます! 

この記事の目標

Cooley-Tukey法によるFFTコードを自分で書いてみる

ここしばらく筆者はiPhoneアプリ制作をしてみたいと思い、基礎文法のチェックや録音の仕方、音声再生方法や画面遷移の方法を個別の記事で取り上げました。信号処理アプリを作る目標を立てており、この分野ではかかせない周波数分析機能に着手するのがこの記事の内容です。

WATLABブログでは以下の記事で前回離散フーリエ変換DFT)をPythonやSwiftで実装してみました。
Pythonで検証しながらSwiftで離散フーリエ変換を実装する

そして次に高速フーリエ変換FFT)のアルゴリズムをPythonで学びました。
Pythonで高速フーリエ変換をCooley-Tukey法で書く

慣れもあるかもしれませんが、Pythonはこのようなコードを書くのが本当に楽です。先ほどの記事で基本的なFFT関数の作り方を学んだので、この記事ではまず同様にCooley-Tukey法をSwiftに移植してみます。

AccelerateでFFTを使ってみる

ただ、Pythonで実感したのは自分で実装したFFT関数よりもNumPy等の外部ライブラリを使った方が圧倒的に高速だということでした。基本的なアルゴリズムを学ぶために自分でコードを書いてみるのは非常に良いことですが、実用には既存のライブラリを使った方が良いと思います。

そのためこの記事では一度Cooley-Tukey法を書いてみた後に、SwiftのAccelerateというライブラリを使ってFFTを使う方法も学びます。

動作環境

この記事のコードは以下の開発環境で動作を確認しました。

                                                            
Mac OSmacOS Ventura 13.2.1
CPU1.4[GHz]
メモリ8[GB]
Xcode Version14.3(14E222b)
Swift Version5.8(swiftlang-5.8.0.124.2 clang-1403.0.22.11.100)
iPhone SE2 OSiOS 16.3.1

SwiftでCooley-Tukey法によるFFTをフルスクラッチするコード

事前準備

まずは自作のFFTコードから書いてみますが、動作検証のためにいくつか事前準備を行います。

データ点数を指定して配列を作成する関数

FFTのアルゴリズムを効果的に使用するためにはデータ点数が2の冪乗でないとなりません。Swiftでメソッドがあるかどうか探せなかったので、まずはデータ点数を自由に指定するために以下のlinspace関数を作りました。Pythonのnp.linspace()を真似たのですが、これでFFTコードの検証がしやすくなります。

サンプル波形を生成する関数

上記データ点数を指定するlinspace関数を使って、サイン波を作るwavePoints関数も用意しておきます。サイン波は周波数や時間長を変更できます。また、コードの検証としては直流成分も確認したいため、dcで0[Hz]の時の振幅を定義できるようにしています。

複素数構造体を定義

Swiftには標準で複素数型がありません。そのためstructを使ってComplex構造体を定義します。詳細は離散フーリエ変換の記事で書いた通りです。

FFT関数(Cooley-Tukey法)

こちらがSwiftで書いたCooley-Tukey法によるFFT関数です。先ほど紹介したPythonによるFFTの記事と同じ書き方をしているので、Pythonユーザーは是非両コードを見比べてみてください。

全コード(コピペ動作OK)

UI部分を含めた全コードを以下に示します。freqLimitはなくても動作しますが、グラフに表示させる周波数を絞ることを目的としています。グラフへの表示は以下の記事で紹介したChartを使っています。
SwiftUI:iOS16から追加されたChartsでグラフを作成

実行結果

下図が実行結果の例です。Buttonをタップすると波形が表示されます。これはwavePointsで生成された時間波形をfft関数で高速フーリエ変換した結果です。指定した通りのピークがでました。

実行結果(FFT計算結果)

freqLimitを外したコード、もしくはfreqLimitの上限を最大まで持っていった場合は下図になります。fft関数で計算された値は鏡像ピークまで入っている結果です。

SwiftのAccelerate/vDSPでFFTをするコード

全コード

それではAccelerateというライブラリのvDSPを使ってみます。上記のコードをvDSPを使って書いたコードを以下に示します。

先ほどもデータ数が2の冪乗数でない時の処理はしていませんが、vDSPは自動的にゼロパディングによる自由なデータ数の処理が自動的に有効になっています。しかし振幅計算時に振幅補正係数を自分で書かないといけません。今回はSwiftになれていない筆者は少し面倒だったので、2の冪乗数でないデータが渡された場合に.alert()で警告文を出し計算しないという手段をとりました。

実行結果

こちらが実行結果です。

vDSPのFFT結果

データ点数(let result = wavePoints(N: 8191, length: 10))とNを2の冪乗数でない数を指定すると、以下の図に示すメッセージを出しています。

警告メッセージの例

まとめ

今回はここまで!
Swiftを使ってフーリエ変換するコードを、まずはCooley-Tukey法をフルスクラッチすることで実装してみました。一度Pythonで検証していたといっても、やはりSwiftだと慣れていないので時間がかかりますね。

そして次にAccelerateというライブラリをimportし、vDSPメソッドでFFTを使ってみました。ライブラリを使うことで少し速度UPしたように感じました。
次回は録音再生アプリにFFTを実装する部分を書いてみたいと思います。

SwiftでもFFTをかけることができるようになりました!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!

  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。

コメントを残す

*