Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

Python: PyQt5とPyAudioで作るBPM計測アプリ

www.youtube.com

PyQt5の習作として、PyQt5とPyAudioを使ったBPM計測アプリを作ってみました。

よくあるBPM計測器の仕様となっていて、ユーザが拍位置でボタンクリックやキータイピングをすることで、BPMを計測をすることができます。

インターフェースもアルゴリズムもシンプルなので、音楽アプリ制作の入門に良さげです。また、PyQt5やPyAudioのよい勉強になります。 それでは、実装の紹介について説明します。

アプリの設計

シンプルかつ実用的な仕様にするため、以下のように設計しました。

  • インターフェース
    • GUI表示
    • BPMの測定値を表示
    • BPMを計測するための、拍位置入力用ボタンを表示
    • キーボード入力をしても、拍位置入力用ボタンが押されたこととする
    • タッピング時に、音を鳴らす
  • アルゴリズム
    • ユーザが拍位置をを入力し、BPM値を計算して出力

今回、インターフェースはPyQt5PyAudioアルゴリズムNumPyベースで実装しました。

PyQt5 によるGUI作成

以下のPyQt5の関数を使いました。

  • QGridLayout()
    • BPM表示部QLCDNumberと拍位置入力ボタンQPushButtonを設置
  • QLCDNumber
    • BPMを値を表示部
  • QPushButton
    • タッピング用のボタン設置
  • QWidget().keyPressEvent()
    • キーボード入力で呼び出される
    • 今回は、オーバライドしてQWidget().buttonClicked()を呼ぶ
  • QWidget().buttonClicked()
    • 拍位置入力用のボタンのクリックで発火

PyAudoで、タッピング時に音を鳴らす

今回の実装では、AudioManager()というPyAudioのストリームと入力を管理するクラスを定義しています。

QWidget().buttonClicked()が押されたときにAudioManager()sound()を呼び出し、その中のstream.write()で短い時間区間分サイン波を書き込むことで音を鳴らしています(実装では0.1秒程度)。

注意点としては、音を鳴らしている間もGUIでの操作ができるように、threadingを使って、stream.write()を鳴らす関数を並列処理とします。

BPM計測アルゴリズム

BPMは、ユーザがタッピングにより入力した拍位置を元に計測しています。

BPMは、その名の通り、1分あたりの拍数(Beat Per Minute)を示します。従って、現在の拍の時刻と、1つの前の拍の時刻があれば計算できます。

具体的には、

  1. 現在の拍の時刻[sec]を取得
  2. 拍間の時刻差分[sec]を取得
    • (現在の拍の時刻)ー(1つ前の拍の時刻)
  3. BPMを計算
    • 60 / (拍間の時刻差分[sec])
      • (1秒あたりの拍数)=1 / (拍間の時刻差分[sec])
      • (1分あたりの拍数)=60 * (1秒あたりの拍数) 4.(1つ前の拍の時刻)に(1つ前の拍の時刻)の値をセット

という流れでBPMを算出します。

ただし、上記の実装だけだと、ユーザの拍の入力が安定しない場合には、BPMの算出も安定しません

そこで、今回の実装では2つの工夫をしています。

  • BPM を算出時に、拍間の時刻差分[sec]を鈍らせる
    • α * (現在の拍間の時刻差分[sec]) + (1.0 -α) * (1つ前の拍間の時刻差分[sec])
      • αは(0.0, 1.0]のスカラー
      • αは、値が小さいほど一つ前のBPM値に近づける効果がある
  • BPM算出後に、BPMの平均を取る
    • N-1個前までのBPM値を保持しておく
    • 現在のBPMと合わせて、N個のBPMの平均値を、ユーザに掲示するBPM値とする

ソースコード

github.com

f:id:Kurene:20191117111347p:plain