Wizard Notes

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

Python:音の広がりや位相を見るフェーズスコープをPyQtGraphとPyAudioで実装

Pythonにおけるリアルタイム音楽信号処理アプリのデモ/教材として,音の広がりや位相を見るフェーズスコープをPyQtGraphとPyAudioで実装してみました.

起動すると,PC上で音楽を再生しながらフェーズスコープを描画することができます

ソースコードは以下にあります.

github.com

少し複雑なソースコードなので,この記事ではソースコード理解の助けとなる実装方法の要点とノウハウを書きたいと思います.

実装方法の要点・ノウハウ

Pythonにおけるリアルタイム音楽再生・分析方法

Pythonでリアルタイムに音楽信号を再生・分析する方法としては,以下の3つが考えられます.

  1. Pythonで音楽再生部(音楽プレーヤー)を実装
  2. PCに接続したマイクで収音した音を入力信号とする
  3. ループバック機能を利用し,PC上の音を入力信号とする

今回はPC上で再生している音を可視化ので,3 の方針で実装しました.

3 を実現するためのモジュールとしては PyAudio もしくは pysoundcard がありますが,今回は PyAudio を使いました.

www.wizard-notes.com

www.wizard-notes.com

ループバック機能を利用した,PC上の音のリアルタイム取得

ループバックには,VB-Audio社の仮想ミキサ "Voice Meeter" を使いました.

www.wizard-notes.com

上記の記事でPyAudioを使って実装したAudioInputStreamクラスを利用しています.

データのプロット方法

今回は滑らかな描画がしたいので,30fps以上の更新が必要と考え,PyQtGraphを採用しました.

PyQtGraph にはたくさんのプロットの種類がありますが,今回は比較的多くのデータ点でもリアルタイムでプロットできる実装方法を採用しています.

詳しくは以下の記事をご覧ください.

www.wizard-notes.com

www.wizard-notes.com

フェーズスコープの計算アルゴリズム

ステレオ信号のL,Rチャネルの信号に対して,サンプルごとにデータをプロットするような実装となっています.

具体的な計算方法は以下の記事をご参照ください.

www.wizard-notes.com

www.wizard-notes.com

実行方法

実行スクリプトは以下のようになっています.

# -*- coding: utf-8 -*-
from rasp_audio_stream import AudioInputStream
from pqg_phasescope import PQGPhaseScope


# PyAudioストリーム入力取得クラス
ais = AudioInputStream(CHUNK=1024) #, input_device_keyword="Real")
# フェイズスコープ用クラス
phasescope = PQGPhaseScope( (ais.CHANNELS, ais.CHUNK) )

# AudioInputStreamは別スレッドで動かす
import threading
thread = threading.Thread(target=ais.run, args=(phasescope.callback_sigproc,))
thread.daemon=True
thread.start()

# フェイズスコープ起動
phasescope.run_app()

各機能の実行順序ですが,PQGPhaseScopeクラス,すなわちPyQtGraphのGUIアプリ起動を最後にしてください.

なお,GUIアプリをメインスレッドとするため,threadding を使って入力信号取得は別スレッドで行っています.

フェーズスコープ描画のノウハウ

  • 振幅値 (rad) の生の値は強弱が激しすぎる
    • 平方根(0.5乗)とかで鈍らせる
  • 振幅値が小さいときに点が重なって色が濃く見える
    • ⇒ 振幅値が小さいときは透明度alphaを下げる
  • 位相差0度の時に y=xとなるので分かりにくい
    • x=0 となるように45度(0.25π)回転する(見やすくするため)

pqg_phasescope.py

    def update(self):
        ...

        # 値をいい感じに調整
        rad **= 0.5
        phase += 0.25 * np.pi
        
        alpha = np.mean(rad)**2
        alpha_max = 0.2
        alpha = alpha_max if alpha_max > 0.2 else alpha
        
        ...

動作テスト

Audacityを使って,位相やLRの音量バランスが異なるサイン波を手動で制御・出力し,正しく動作することを目検で確認しました.