音楽プログラミングの超入門(仮)

Python / 音楽情報処理 初心者が、初心者にも分かるような記事を書きたい。

【Python】 ぬるぬる動くスペクトルアナライザを作ろう!!

導入

スペクトルアナライザ

スペクトルアナライザというものを知っているでしょうか?かなり色々あると思いますが、ここでは単純に入力された音のスペクトルをリアルタイムで表示するシステムを考えます。Windows Media Player のエフェクトにもそれっぽいものがありますね(下図)。
f:id:yukara_13:20131204144931j:plain

Requirement

プロットが遅い matplotlib

やはりリアルタイムで表示するからにはぬるぬる動いてほしいですよね!!
Python でのプロッティングといえば、matplotlib がメジャーだと思います。実際、matplotlib でもリアルタイムに表示を更新していく仕組みはあるので、スペクトルアナライザを実装することは可能です。しかし、matplotlib には弱点があり、プロッティングがめちゃくちゃ遅いんですね。そのため、表示が音に対してどんどん遅れていったり、もしくは凄くカクカクな表示を行うことになります。そこで今回はこれを使わずに、pyqtgraph というライブラリを利用していきます。

必要なライブラリ(パッケージ)

今回の実装に必要なライブラリを紹介します。ちなみに NumPy / SciPy が入っていることは前提です。

  • alsaaudio : マイクからの音声入力に使う
  • PyQt4 : pyqtgraph が依存
  • pyqtgraph : 高速なプロッティング

以上のライブラリが必要です。
alsaaudioPyQt4 はこんな感じでインストールできるのではないでしょうか?

$ 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 で「あいうえお」と調べて出てきたのを適当に選びました。最後のハーモニカは自分で吹いてます。
若干カクカクに見えるかもしれませんが、これは動画の問題であり、実際はもっとぬるぬると動いております。是非、自分で試してみてください。