PyQtGraphの複数の線グラフプロットを利用して,12音平均律で音高ごとに色を変えてライブプロットしたら綺麗&役立つかなと思い作ってみました.
もう少しブラッシュアップしようと思ったのですが,後述の理由でお蔵入りになったので,供養としてソースコードと実装方法を公開します.
デモ動画・ソースコード
https://github.com/Kurene/pyqtgraph-app/blob/main/pqg_pitchlines.py
実装の流れ
PyQtGraphやスペクトル算出といった実装の流れとしては,以下の記事で紹介した方法と同じです.
以下では,差分である2点について説明します.
PyQtGraphにおける複数のカーブプロット
このビジュアライズでは,イメージプロットではなく複数のカーブプロットを利用しています.
PyQtGraphで実現するには,まず,複数のplotdataitem
オブジェクトと作成します.
class PQGPitchLines(): def __init__(self): ... self.plots = [] for k in range(self.n_chroma): self.plots.append( self.plotitem.plot(pen=pg.mkPen((k, self.n_chroma), width=3)) ) ...
ここで,12音のカーブプロットを区別できるように,pyqtgraph.mkPen
を使って色を変えています.
プロットデータのアップデートも同様に,12個のplotdataitem
について,plotdataitem.setData()
で新しいデータをセットしています.
class PQGPitchLines(): def update(self): ... for k in range(self.n_chroma): alpha = self.y[idx,k] * 0.9 alpha = alpha if pw > 1e-3 else 0.0 self.plots[k].setAlpha(alpha, False) self.y[idx,k] += k self.plots[k].setData( self.x, np.r_[self.y[pos:self.n_frames,k],self.y[0:pos,k]] ) ...
ここで,self.y[:,k]
に音高k
のエネルギー値が入っています.
可視化のポイントとしては,現在の音高k
のエネルギー値self.y[idx,k]
に応じて線の透明度を設定しています.その結果,エネルギー値が高い音高の線のみ見えるようになっています.
12音平均律のエネルギー抽出
今回はメルフィルタバンクではなく,12音のエネルギー抽出(クロマ)フィルタバンクを利用しています.
具体的な算出方法は、以下の記事を参考にしてください.
リアルタイムの場合,以下のようなという線形写像をnp.dot
で毎フレーム計算するイメージです.
class PQGPitchLines(): def update(self): ... self.sig_mono[:] = 0.5 * (self.sig[0] + self.sig[1]) pw = np.sqrt(np.mean(self.sig_mono**2)) self.sig_mono[:] = self.sig_mono[:] * self.window self.specs[:] = np.abs(self.fft(self.sig_mono))**2 self.chroma[:] = np.dot(self.chromafb, self.specs) self.chroma[:] = self.chroma / (np.max(self.chroma)+1e-16) self.chroma[:] = 0.3*self.chroma+0.7*self.chroma_pre self.y[idx] = self.chroma[:] ...
実装ノウハウ:
- フレームごとに,12音のエネルギーの最大値で正規化
- どの音高が鳴っているか見やすくするため
- 過去のクロマベクトル
self.chroma_pre
とブレンド- そのまま使うと感度が高すぎるので,鈍らせるため