pyqtgraph+PyAudioによるリアルタイムで音声プロット その2
前回書いた記事でのコードを改良し、時間的に流れるようにしました。
また、今回はついでにスペクトルアナライザーも作りましたのでヌルヌル具合をお確かめ下さい。
[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"のみ)]
[有効(pen="y", symbol="o", symbolPen="y", symbolBrush="b" )]
pyqtgraph+PyAudioによるリアルタイムで音声プロット
通常pythonでのプロットと言ったらmatplotlibですがプロッティングがとても遅い欠点があります。その分使いやすくてキレイなんですけどね。
リアルタイムでプロットしようとしたらメモリは喰うし遅いしで最悪(工夫次第ではそうでもないらしいですがその工夫がめんどくさい)ですので、高速なプロッティングが出来るとされるpyqtgraphを使ってリアルタイムで音声をプロッティングしました。
pyqtgraphは
・PyQt(Qtだけかも?)
・numpy
に依存してますのでそれだけ入れてしまえばオッケーです。
また今回は音声のプロットですので音声関係のライブラリを入れておく必要があります。
今回用いたのはPyAudioです。
# -*- coding:utf-8 -*- #プロット関係のライブラリ 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(10) #10msごとにupdateを呼び出し #音声データの格納場所(プロットデータ) self.data=np.zeros(self.CHUNK) def update(self): self.data=self.AudioInput() 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_()
これを実行すると以下の様な画像になります。
(ごめんなさい、本当は動画を上げたかったのですが動画の上げ方がわからずこのような形になってしまいました。上げ方が分かったら追記で動画を載せます。)
データの入出力あたりはPlotWindowクラスのupdateメソッドとAudioInputメソッドでコントロールしてますので
このあたりをテキトーにいじくってあげたり、FFTしてあげたりすればスペクトルアナライザーにもなります。
self.timer.start(10)を50にしてあげたりすればプロットスピードが落ちたりします。
pyqtgraphのリファレンスがちょっと充実度にかけるのでご参考になれば幸いです。
また、PyAudioを利用されたことのある方にお叱りを受けるかもしれませんが、今回streamなどを明示的に終了させてません。
ちゃんと動いて止まっておかしいことはなかったので大丈夫っちゃ大丈夫かな程度のテキトーなコードです。
ちゃんと明示的に止めるのであれば、PlotWindowクラスのpg.GraphicsWIndowがQtからの継承ですので、ウィンドウを閉じる時におそらくcloseEventを発するはずですので、オーバーライドしてstreamを止めればいいのでは。
ですので上記のコードはかなりテキトーです。
PyQt4メモ1
最近PyQt4を使い始めたのでテキトーにメモしていきます。
[参考にしているサイト]
PyQt's Modules
PyQt Class Reference
[環境]
OS: Windows7
IDE: Pyscripter
Python: Python2.7
PyQt: PyQt4
Pyscripterのモジュールのメソッドの補完については
(手順1)
Pyscripterの起動 -> ツールタブ -> Edit Start Scripts
とすると pyscripter_init.py と python_init.py が開かれるので
python_init.pyの方に
import sys import PyQt4.QtGui as QtGui import PyQt4.QtCore as QtCore
を追加してファイルを上書き保存。
(手順2)
Pyscripterの起動 -> ツールタブ -> オプション -> IDEオプション
とするとIDEオプションのウィンドウが開かれるので
Code Completionの表の中の Special packagesにPyQt4を追加する
Special packages os, wx, PyQt4
となっていればOK
numpy, scipy, matplotlibなどを使いたいって時も同じ手順で行います。
これは何をやっているのというと、
Pyscripter上にあるPythonインタプリタに、初期設定でpython_init.pyでimportしまっせということらしい。
そしてコード補完はPyscripter上のインタプリタで定義された変数やクラスにしか作用しないということらしいです。
なのでpython_init.pyを起動時に読み込んであげて、変数・クラスを定義しますよ ということらしいです。
外部モジュールをpython_init.pyで読み込むには,手順2で設定してあげましょうね、ということらしいです。
合ってるかはワカリマセン。
では以下よりPyQtの話題
まずは基本の雛形
[mamo1_1.py]
import sys import PyQt4.QtGui as QtGui import PyQt4.QtCore as QtCore #基本のウィンドウの作成 これの上にボタンとかを載せていく class MainWidget(QtGui.QWidget): def __init__(self): QtGui.QWidget.__init__(self, parent=None) self.initUI() def initUI(self): self.show() def main(): app = QtGui.QApplication(sys.argv) main_widget = MainWidget() sys.exit(app.exec_()) #app.exec_()でメインループの開始 if __name__=="__main__": main()
これでとりあえず画面は表示される。
何も設定しない状態でデスクトップのどこに表示されるのかなどを比較したかったのでデスクトップごと映しました。
何もしなくても中心あたりに表示された。
GUIを作るんだったら静的にではなく動的にいろいろいじくりたいのでまず、ウィンドウの状態や状態の変更の仕方を知りたいです。
そしてウィンドウの状態の定義の仕方はどんなウィジェットでも一貫性のあるもののはずです。きっと。応用が効きまくるはずです。
サイズだとか位置だとかが必ずクラスで定義されているはずだし、英語だとpositionだからそれっぽい名前だろうということで、上記の参考サイトで調べるとありました。
(GoogleChromeでのキーワードの検索はWebページ上で Ctrl+F で行えます。画像のようになります)
QWidgetの位置はQPointというクラスで定義されているようです。
さらにQPointクラスについて調べてみると
QPoint Class Reference
英語読めないよ、という方はWebブラウザの翻訳機能を使いましょう。少ししょぼい翻訳ですが英語だけで読み切るよりはマシなはずです。
Methodsの項目だけみてればとりあえずOK。それっぽいのがありました
QPoint.setX(self, int xpos) QPoint.setY(self, int ypos) QPoint.x(self) QPoint.y(self)
setX,setYはウィジェットの位置を決めるメソッド
x,yはウィジェットの位置を返すメソッド
だと分かります。
QWidgetのサイズはQSizeというクラスで定義されているようです。
QSize Class Reference
QSize.setHeight(self, int h) QSize.setWidth(self, int w) QSize.height(self) QSize.width(self)
確かめてみましょう。上記のmemo1_1.pyにprintを追加しただけです。
[memo1_2.py]
import sys import PyQt4.QtGui as QtGui import PyQt4.QtCore as QtCore class MainWidget(QtGui.QWidget): def __init__(self): QtGui.QWidget.__init__(self, parent=None) self.initUI() print "x:",self.x() print "y;",self.y() print "widht:", self.width() print "height:", self.height() def initUI(self): self.show() def main(): app=QtGui.QApplication(sys.argv) main_class=MainWidget() sys.exit(app.exec_()) if __name__ == '__main__': main()
これでなんとかウィンドウの状態が取得できました。めでたしめでたし。
今回はデスクトップ上での位置だったけど、ウィジェットにウィジェットを入れ子にするのが普通なのでその場合は位置はどうなるのか気になるところです。
多分ウィジェット上での位置になるんだろうけど。
vimのクリップボードとレジスタのコピーアンドペースト
[環境]
OS : LMDE2
端末: MATETerminal
vim: vim7.4
vimで書いたプログラムをコピーして記事に貼り付けたかったが、なぜかうまくいかなかった
ググってもMacでの記事ばかりでvim初心者には困る
とりあえず.vimrcに
set clipboard=unnamed
と書くといいらしいとかいろんなHPで言ってますがこれはどういう意味なんでしょう
ということでvim上で
:help clipboard
でいろいろ見てみました。
その結果
set clipboard=unnamed
は yankした文字をvim上の*レジスタ(無名レジスタ)に保存するようにする設定
set clipboard=unnamedplus
は yankした文字をvim上の+レジスタ(OSのクリップボード)に保存するようにする設定
ということが分かりました
これで解決だぁ
ということで.vimrcに
set clipboard=unnamed, unnamedplus
を加えました
ちなみにマウスでvim以外のソフトからコピーした文字はvim上の*レジスタと+レジスタに保存されていました なんとなく仕組みが見えた気がする