たけし備忘録

自分の好奇心の赴くままに勉強メモ LL系が大好き Python bash Julia C

pyqtgraph+PyAudioによるリアルタイムで音声プロット その2

takeshid.hatenadiary.jp

前回書いた記事でのコードを改良し、時間的に流れるようにしました。
また、今回はついでにスペクトルアナライザーも作りましたのでヌルヌル具合をお確かめ下さい。


www.youtube.com



[AudioPlot.py]

#プロット関係のライブラリ
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import sys

#音声関係のライブラリ
import pyaudio
import struct


class PlotWindow:
    def __init__(self):
        #プロット初期設定
        self.win=pg.GraphicsWindow()
        self.win.setWindowTitle(u"リアルタイムプロット")
        self.plt=self.win.addPlot() #プロットのビジュアル関係
        self.plt.setYRange(-1,1)    #y軸の上限、下限の設定
        self.curve=self.plt.plot()  #プロットデータを入れる場所

        #マイクインプット設定
        self.CHUNK=1024             #1度に読み取る音声のデータ幅
        self.RATE=44100             #サンプリング周波数
        self.audio=pyaudio.PyAudio()
        self.stream=self.audio.open(format=pyaudio.paInt16,
                                    channels=1,
                                    rate=self.RATE,
                                    input=True,
                                    frames_per_buffer=self.CHUNK)

        #アップデート時間設定
        self.timer=QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        self.timer.start(50)    #10msごとにupdateを呼び出し

        #音声データの格納場所(プロットデータ)
        self.data=np.zeros(self.CHUNK)

    def update(self):
        self.data=np.append(self.data, self.AudioInput())
        if len(self.data)/1024 > 5:     #5*1024点を超えたら1024点を吐き出し
            self.data=self.data[1024:]
        self.curve.setData(self.data)   #プロットデータを格納

    def AudioInput(self):
        ret=self.stream.read(self.CHUNK)    #音声の読み取り(バイナリ)
        #バイナリ → 数値(int16)に変換
        #32768.0=2^16で割ってるのは正規化(絶対値を1以下にすること)
        ret=np.frombuffer(ret, dtype="int16")/32768.0
        return ret

if __name__=="__main__":
    plotwin=PlotWindow()
    if (sys.flags.interactive!=1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

[SpectrumAnalyzer.py]

#プロット関係のライブラリ
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import sys

#音声関係のライブラリ
import pyaudio
import struct


class PlotWindow:
    def __init__(self):
        #マイクインプット設定
        self.CHUNK=1024            #1度に読み取る音声のデータ幅
        self.RATE=16000             #サンプリング周波数
        self.update_seconds=50      #更新時間[ms]
        self.audio=pyaudio.PyAudio()
        self.stream=self.audio.open(format=pyaudio.paInt16,
                                    channels=1,
                                    rate=self.RATE,
                                    input=True,
                                    frames_per_buffer=self.CHUNK)

        #音声データの格納場所(プロットデータ)
        self.data=np.zeros(self.CHUNK)
        self.axis=np.fft.fftfreq(len(self.data), d=1.0/self.RATE)

        #プロット初期設定
        self.win=pg.GraphicsWindow()
        self.win.setWindowTitle("SpectrumAnalyzer")
        self.plt=self.win.addPlot() #プロットのビジュアル関係
        self.plt.setYRange(0,100)    #y軸の制限

        #アップデート時間設定
        self.timer=QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        self.timer.start(self.update_seconds)    #10msごとにupdateを呼び出し

    def update(self):
        self.data=np.append(self.data,self.AudioInput())
        if len(self.data)/1024 > 10:
            self.data=self.data[1024:]
        self.fft_data=self.FFT_AMP(self.data)
        self.axis=np.fft.fftfreq(len(self.data), d=1.0/self.RATE)
        self.plt.plot(x=self.axis, y=self.fft_data, clear=True, pen="y")  #symbol="o", symbolPen="y", symbolBrush="b")

    def AudioInput(self):
        ret=self.stream.read(self.CHUNK)    #音声の読み取り(バイナリ) CHUNKが大きいとここで時間かかる
        #バイナリ → 数値(int16)に変換
        #32768.0=2^16で割ってるのは正規化(絶対値を1以下にすること)
        ret=np.frombuffer(ret, dtype="int16")/32768.0
        return ret

    def FFT_AMP(self, data):
        data=np.hamming(len(data))*data
        data=np.fft.fft(data)
        data=np.abs(data)
        return data

if __name__=="__main__":
    plotwin=PlotWindow()
    if (sys.flags.interactive!=1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

[SpectrumAnalyzerで注意すべき点]

今回、スペクトルアナライザーを載せましたが注意すべき点が少々あります。
pyqtgraphやnumpyのFFTは非常に高速なのですが、いかんせんPyAudioが非常に遅いのです。
ですのでプロット点数を増やす→CHUKを増やす と安易にするととてつもなくプロッティングが遅くなります。
ですので上記のスペクトルアナライザーでは少し工夫をしてあります。
AudioInputメソッドのself.stream.read(PyAudioの部分)するところでのCHUNKは1024点ですが、updateメソッドの中で音声データを貯めてからFFTをしてあげています。
これによりpyqtgraphとnumpyの恩恵を受けれるようになります。

[pyqtgraphのデザイン関係について]

 def update(self):
        self.data=np.append(self.data,self.AudioInput())
        if len(self.data)/1024 > 10:
            self.data=self.data[1024:]
        self.fft_data=self.FFT_AMP(self.data)
        self.axis=np.fft.fftfreq(len(self.data), d=1.0/self.RATE)
        self.plt.plot(x=self.axis, y=self.fft_data, clear=True, pen="y")  #symbol="o", symbolPen="y", symbolBrush="b")

ここの部分のself.plt.plotです。
コメントアウトしてありますが、symbol, symbolPen, symbolBrushというものがあります。

penは線の色、
symbolはプロット点の形(oで丸, tで三角など),
symbolPenはプロット点の形の周の色,
symbolBrushはプロット点の中の色です。

それぞれ順に pen=黄色, symbol=丸, symbolPen=黄色, symbolBrush=青
としておきました。色関係はRGB指定も出来るそうです。
これらを有効にしてあげたものを貼っておきます。

[無効(pen="y"のみ)]
f:id:takeshiD:20151127044353p:plain

[有効(pen="y", symbol="o", symbolPen="y", symbolBrush="b" )]
f:id:takeshiD:20151127044435p:plain