
kivyはPythonのみでGUIアプリを開発可能なライブラリです。今回はPythonが得意とする信号処理分野のアプリとして簡易的なFFTアナライザを作ってみました。録音したデータの時間波形/周波数波形切り替えやピーク検出機能を実装しましたので、その詳細コードを紹介します。
こんにちは。wat(@watlablog)です。今回は録音したデータの周波数分析に使用可能なアプリを作ってみました!
kivyによるFFTアナライザアプリのコード例
前提知識
今回はいきなりPythonコードとkvファイルを示しますが、やっていることや基礎的な内容は以下の記事に記載しました。kvファイルの書き方、イベント処理のやり方、信号処理クラスをまとめた自作ファイルについてはこの記事をご覧ください。
動作環境
このページのコードは以下のPC環境とPython環境で確認しました。
Mac | OS | macOS Catalina 10.15.7 |
---|---|---|
CPU | 1.4[GHz] | |
メモリ | 8[GB] |
Python | Python 3.9.6 |
---|---|
PyCharm (IDE) | PyCharm CE 2020.1 |
Numpy | 1.21.1 |
Scipy | 1.4.1 |
Pandas | 1.0.5 |
matplotlib | 3.4.3 |
PySoundFile | 0.9.0.post1 |
PyAudio | 0.2.11 |
kivy | 2.1.0 |
Kivy-Garden | 0.1.5 |
コード
今回はボリュームが少し大きいので、コードの説明はアプリの動作確認動画を使いながら後半にまとめました。まずは全コードと簡単な説明を先に紹介します。
main.py
main.pyがメインのPythonファイルです。このファイルをPythonで実行してアプリを起動します。
class Root()がkivyのウィジェット動作に関連するクラスで、matplotlibとの連携、ボタンイベントやウィジェット状態の監視を行っています。
Root()をインスタンス化しているclass MyApp()でウィンドウサイズも設定しています。今回はデスクトップ環境による確認をしていますが、最終的にはモバイルアプリ化を目指しているため画面アスペクト比を16:9の縦長にしています。デスクトップ環境で使用する場合は使いやすいサイズに変更しても良いと思います。
my.kv
kvファイルを以下に示します。このファイルは各種ウィジェットをウィジェットツリーとして構成しています。今回はPageLayoutをルートウィジェットとし、BoxLayoutで各ButtonやLabel、TextInput、ToggleButtonを格納する構成です。
イベントに関連するウィジェットにはidの設定を行い、Pythonファイルで呼び出して制御します。
dspToolkit.py
dspToolkit.pyはWATLABブログで作成した各種信号処理(Digital Signal Processing)関連のメソッドをまとめたファイルです。このファイルで定義しているDspToolkitクラスをmain.pyの中でインスタンス化して使うことでmain.pyの構成をシンプルにしています。
「アプリ完成編:wxPythonで信号処理のGUIアプリをつくろう⑥」の記事で使ったものがありますが、今回全て使うわけではありません。
アプリの使い方とコードとの対応(動画多数)
画面構成とウィジェット構成
画面構成図を下に示します。本アプリ(WATLAB FFT)は3画面構成です。メイン画面は録音ボタンとmatplotlibのFigureが表示され、この画面が初期画面となります。
右にスワイプすると画面が切り替わり、2ページ目は軸設定画面です。このテキスト値を変更することで、プロットの軸の範囲が変更されます。テキストはfloat型に変換可能な文字しか入力を受け付けません。縦軸はオートスケールの仕様です。
さらに右にスワイプした3ページ目は分析条件設定とモード切り替え画面です。

スワイプ動作例の動画がこちら。右にスワイプして切り替えた画面は左にスワイプして戻ることができます。

各ページのウィジェット図と、設定があるものはid値を以下に示します。上記kvファイル(my.kv)と見比べることでkvファイルの学習にも使えると思いますのでご確認ください。

FigureCanvasKivyAgg(self.fig)はPythonコード上でadd.widgetするので、kvファイルにはありません。位置指定はBoxLayoutのid値を使い、self.ids.viewer.add_widget(widget)と書きます。
各LabelとTextInputの縦サイズはsize_hint_yを使って全体が1になるよう比率で指定します。こうすることで画面サイズを変更したとしても比率を維持できます。
PageLayoutでスワイプ機能を実装していますが、画面遷移先の背景が設定されていないと前の画面が見えてしまいます。これはcanvas.beforeを使ってウィジェットの後に背景を描画することで見やすくしました。背景色はrgbaで与え、アルファチャンネルを持つため若干の透明度を持つようにしてみました(これは好み)。
ToggleButtonはモード切り替えのために用意しましたが、これは通常のButtonと比べると押された時の状態を維持できるという違いがあります。アナログのボタンでたとえるとButtonがポチっと押して指を離すと戻るやつで、ToggleButtonがカチっと押して戻ってこないやつです(わかりにくい?)。
続いて機能の説明をします。
録音データの周波数波形とピーク値を確認する
RECボタンをタップするとmy.kvのon_releaseが検知され、設定した測定時間、フレームサイズ、オーバーラップ率に従ってPyAudioによる測定とSciPyによるフーリエ変換がmain.pyのon_button()により実行されます。各設定値の意味は以下のとおりです。
- 測定時間:Record time[s]
指定した時間長分データが取得されます。 - フレームサイズ:Frame size
録音時は1ループで取得するデータ数(チャンク)として、平均化フーリエ変換時は時間波形を切り出すデータ点数(フレームサイズ)として作用します。チャンクや平均化フーリエ変換については以下の2つの記事に詳細を記載しましたので、意味についてはそちらを参照してください。
・PythonのPyAudioで音声録音する簡単な方法
・PythonでFFT実装!SciPyのフーリエ変換まとめ - オーバーラップ率:Overlap[%]
平均化フーリエ変換をする際にフレームサイズでデータを切り出しますが、切り出す時の始点をずらして(オーバーラップさせて)フレーム数を多くとったり、ハニング窓の悪影響を小さくしたりします。デフォルトは75[%]です。
オーバーラップについては以下の記事に図解をまとめました。
・PythonでFFTをする前にオーバーラップ処理をしよう!
録音ボタンをタップして周波数波形を得た例を動画で示します。matplotlibのFigureにデータが表示されますが、デフォルトではピーク検出結果としてピーク点が重ね書きされます。さらにピークの情報(周波数, 振幅のデシベル値)がプロットの左下に大きい順で記載されるようにしました。

ピーク情報は最大10個検出されます。

ピーク検出アルゴリズムは以下の記事のとおり、SciPyのsignal.argrelmax()を使っています(DspToolkitクラスのfindpeaks())。orderは3固定という仕様ですが、もし読者の方でカスタマイズするのであればTextInputから入力できるようにすると面白いかも知れません。
・PythonでFFT波形から任意個数のピークを自動検出する方法
ピーク情報のON/OFF切り替え
密集したピーク情報はプロットを見にくくさせてしまいます。また、画面キャプチャをするのに点やテキストが邪魔な時もあるでしょう。その場合はPeak info.をOFFにすることで非表示にできます。また、再びONにすれば再描画がされます。
この動作はchange_peak_info()で実装しています。

時間波形の表示
本アプリのメインは周波数分析ですが、Display Modeをタップしてボタンの表示をTime waveformにすることで時間波形の表示も可能です。時間波形にすることで時間に対する振幅の変動成分を確認できます。
再度Display ModeをタップするとFFT waveformになり、いつでも周波数波形に戻すことができます。
これらの動作はchange_display_mode()で実装しています。

軸範囲の変更
周波数軸の範囲変更
周波数波形の軸範囲はMin freq[Hz]とMax freq[Hz]で変更可能です。Min freqがMax freqより大きくなったり、Max freqに0が指定されたりする場合は変更が反映されません。
軸範囲変更は周波数波形と時間波形で共通のchange_axis()で実装しました。

時間軸の範囲変更と測定時間変更時の動作
時間波形も軸範囲をMin Time[s]とMax Time[s]で変更可能です。先ほどと同様に無効な指定をすると変更が反映されません。また、測定時間を変更するとkvファイル内のon_textがトリガーとなりPythonファイルのchange_record_time()が実行され、Max Timeの値が測定時間に自動変更、Min Timeも0にセットされます。

周波数分解能の変更(フレームサイズの変更)
録音時にフレームサイズの変更ができることを上記で説明しましたが、フレームサイズの変更により周波数分解能が変化します。
より細かい周波数分解能を得たい場合はフレームサイズを大きくしてください。ただし、フレームサイズは測定時間とサンプリングレート(このアプリはサンプリングレートが44100[Hz]で固定)で決まる総データ点数より小さくしなければいけません。

画面サイズの変更について
本アプリは縦長画面を想定していますが、スマホを横にして横長画面にした時でもなんとかピーク情報の確認や設定ができるようなサイズ感を目指しました。スマホを想定する場合、あまりたくさんの設定を追加すると画面レイアウトが崩れそうなので要注意です。

そしてFigureCanvasKivyAgg(self.fig)やkivyの各種ウィジェットは動的なサイズ変更にも対応しています。是非自分の使いやすいサイズを見つけて自分だけのツールにカスタマイズしてみてください。

まとめ
今回はこれまでの記事で学んだ知識を使って(まぁほぼ偉大な先人達が蓄積したライブラリの力ですが…)なんちゃってFFTアナライザを作ってみました。
自分が欲しいと思う機能(ピーク検出、時間波形と周波数波形の確認、分析条件の変更)をできるだけ絞ってシンプルな構成にしたつもりです。
おそらくフィルタまでかけようとすると操作がちょっと難解になりそうなのでこの辺でやめました。
また、matplotlibはsubplotの設定で上下に時間波形と周波数波形を並べるといったこともできますが、モバイルを想定した小さい画面だと非常に小さくなってしまうので「切り替え」という手段を選択しました。
wavファイル保存やスペクトログラム表示くらいは実装しても良かったかも知れません(すぐできそう)。ただ、ファイルを仮にiPhoneに保存したとするとどこに保存されるんだろうという謎がまだ未調査です。
環境構築さえ整ってしまえば記事内3つのコードをコピペするだけで、誰でもお手元のPCで本アプリが使えるはずです。そのためこの記事では使い方や何の動作がどこのコード(メソッド)と対応しているかを重視した取扱説明書のような構成にしてみました。
このあとはパッケージングにもトライしたいですが、おそらく実行ファイル化やアプリ化の段階でビルドできないというエラーに悩まされると思います(多くの方がそこで挫折している模様)。果たして一介の機械系エンジニアはその壁を乗り越えることができるのか…。続く。
実行ファイル化
デスクトップアプリ
まずはデスクトップアプリとして実行ファイルにすることができました!以下の記事を参考にすることでおそらく読者の皆様も同じようにつくることができると思います。
kivyで作ったアプリをNuitkaでexe化する時のエラー対処例
コンパクトな構成ながらも何とか形になりました!やっぱりアプリ制作は楽しいですね!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!