【Python】 ぬるぬる動くスペクトルアナライザを作ろう!!
導入
スペクトルアナライザ
スペクトルアナライザというものを知っているでしょうか?かなり色々あると思いますが、ここでは単純に入力された音のスペクトルをリアルタイムで表示するシステムを考えます。Windows Media Player のエフェクトにもそれっぽいものがありますね(下図)。
Requirement
プロットが遅い matplotlib
やはりリアルタイムで表示するからにはぬるぬる動いてほしいですよね!!
Python でのプロッティングといえば、matplotlib がメジャーだと思います。実際、matplotlib でもリアルタイムに表示を更新していく仕組みはあるので、スペクトルアナライザを実装することは可能です。しかし、matplotlib には弱点があり、プロッティングがめちゃくちゃ遅いんですね。そのため、表示が音に対してどんどん遅れていったり、もしくは凄くカクカクな表示を行うことになります。そこで今回はこれを使わずに、pyqtgraph というライブラリを利用していきます。
必要なライブラリ(パッケージ)
今回の実装に必要なライブラリを紹介します。ちなみに NumPy / SciPy が入っていることは前提です。
- alsaaudio : マイクからの音声入力に使う
- PyQt4 : pyqtgraph が依存
- pyqtgraph : 高速なプロッティング
以上のライブラリが必要です。
alsaaudio、PyQt4 はこんな感じでインストールできるのではないでしょうか?
$ sudo apt-get install python-alsaaudio $ sudo apt-get install pyqt-tools libqt4-dev python-qt4-dev python-qt4
pyqtgraph はサイトからソースをダウンロードしてインストールしてください。
PyQtGraph - Scientific Graphics and GUI Library for Python
Qt (キュート) はGUIとかを作るための C++ ライブラリですね。PyQt4 はこれを Python に移植したものです。pyqtgraph は更にプロッティングの高速化のために作られたライブラリだと思います。使い方は PyQt4 とあまり変わらないはずです。
自分は PyQt4 を使わずに、いきなり pyqtgraph から入ったので実装の仕方があってるのかよく分かりませんが、もっといい実装の方法があれば教えてください。
実装
面倒くさかったので、細かい説明は省きますが、コードを見れば何となく何をしているか分かる気がします。pyqtgraph はなかなかドキュメントが少なく、自分も苦労したので、何かの参考になればと思います。# -*- coding: utf-8 -*- import sys import alsaaudio from scipy import arange, fft, fromstring, roll, zeros import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtCore fftLen = 2048 shift = 100 signal_scale = 1. / 2000 capture_setting = { "ch" : 1, "fs" : 16000, "chunk" : shift } def spectrumAnalyzer(): global fftLen, capture_setting, signal_scale # ======================== # Capture Sound from Mic # ======================== ch = capture_setting["ch"] fs = capture_setting["fs"] chunk = capture_setting["chunk"] inPCM = alsaaudio.PCM(alsaaudio.PCM_CAPTURE) inPCM.setchannels(ch) inPCM.setrate(fs) inPCM.setformat(alsaaudio.PCM_FORMAT_S16_LE) inPCM.setperiodsize(chunk) ### FFT する信号 signal = zeros(fftLen, dtype = float) # ======== # Layout # ======== ### アプリケーション作成 app = QtGui.QApplication([]) app.quitOnLastWindowClosed() ### メインウィンドウ mainWindow = QtGui.QMainWindow() mainWindow.setWindowTitle("Spectrum Analyzer") # Title mainWindow.resize(800, 300) # Size ### キャンパス centralWid = QtGui.QWidget() mainWindow.setCentralWidget(centralWid) ### レイアウト!! lay = QtGui.QVBoxLayout() centralWid.setLayout(lay) ### スペクトル表示用ウィジット specWid = pg.PlotWidget(name="spectrum") specItem = specWid.getPlotItem() specItem.setMouseEnabled(y = False) # y軸方向に動かせなくする specItem.setYRange(0, 1000) specItem.setXRange(0, fftLen / 2, padding = 0) ### Axis specAxis = specItem.getAxis("bottom") specAxis.setLabel("Frequency [Hz]") specAxis.setScale(fs / 2. / (fftLen / 2 + 1)) hz_interval = 500 newXAxis = (arange(int(fs / 2 / hz_interval)) + 1) * hz_interval oriXAxis = newXAxis / (fs / 2. / (fftLen / 2 + 1)) specAxis.setTicks([zip(oriXAxis, newXAxis)]) ### キャンパスにのせる lay.addWidget(specWid) ### ウィンドウ表示 mainWindow.show() ### Update while 1: length, data = inPCM.read() num_data = fromstring(data, dtype = "int16") signal = roll(signal, - chunk) signal[- chunk :] = num_data fftspec = fft(signal) specItem.plot(abs(fftspec[1 : fftLen / 2 + 1] * signal_scale), clear = True) QtGui.QApplication.processEvents() if __name__ == "__main__": spectrumAnalyzer()
y 軸方向のプロット範囲を 0~1000 に設定していますが、"signal_scale" でスペクトルの値がその中に収まるようにスケーリングしています(笑)...。
サウンド設定とかの入力レベルによって、値が大きくなりすぎたり小さくなりすぎたりすると思うので、適当に調整してください。"shift" が毎回読み込むフレーム数なので、これがぬるぬる度を決定しますが、64 以下にすると alsaaudio がエラーを吐くので気をつけてください。
デモ
一応、デモ映像を撮ってみました。(よく見たら x 軸が適当なままですが、気にしないでください)最初の曲は、youtube で「あいうえお」と調べて出てきたのを適当に選びました。最後のハーモニカは自分で吹いてます。
若干カクカクに見えるかもしれませんが、これは動画の問題であり、実際はもっとぬるぬると動いております。是非、自分で試してみてください。