はじめに
FFT(高速フーリエ変換)を使って、音声データを周波数に分解し、リアルタイムにグラフにプロットする。
音声データを周波数に分解して解析することで、楽器の音色の特性を見たい。(例えば弦楽器と管楽器では同じ音程でも音色から受ける印象は全く異なるなど。)
FFTについて詳細は以下参照。
https://ja.wikipedia.org/wiki/%E9%AB%98%E9%80%9F%E3%83%95%E3%83%BC%E3%83%AA%E3%82%A8%E5%A4%89%E6%8F%9B
入力形式はwavファイルとしています。
以下のサンプルとして、入力しているのはバイオリンソロの曲です。
実行結果
以下に実行時の断面の画像を記載します。
Mac環境だと少し再描画時にゴミが残ってしまっている。(Ubuntuでは問題はなかった。)
画像上段:音声データをそのままプロットしたもの(縦軸は振幅、横軸は時間)
画像下段:音声データをFFTした結果(縦軸は振幅、横軸は周波数)
画像からバイオリンの1つの音程にも複数の周波数(倍音)が混ざっていることがわかります。
この周波数の分布がそれぞれの楽器特有の音色を構成していると推測されます。
複数のライブラリを使用しています。
詳細はソースコードを参照ください。
ソースコード
いろいろ雑なところがあるので、後日リファクタリングします。
# coding: UTF-8
import wave
import pyaudio
from numpy import *
from scipy import fft, zeros, arange
from PyQt4 import QtGui
import pyqtgraph as pg
def printWaveInfo(wf):
print("チャンネル数: " + str(wf.getnchannels()))
print("サンプル幅: " + str(wf.getsampwidth()))
print("サンプリング周波数: " + str(wf.getframerate()))
print("フレーム数: " + str(wf.getnframes()))
print("パラメータ: " + str(wf.getparams()))
print("長さ(秒): " + str(float(wf.getnframes()) / wf.getframerate()))
class ButtonBox(QtGui.QWidget) :
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent = parent)
if __name__=="__main__":
wf = wave.open("Partita2.wav", "rb")
printWaveInfo(wf)
buffer = wf.readframes(wf.getnframes())
print(len(buffer))
data = frombuffer(buffer, dtype="int16")
wf = wave.open("Partita2.wav", "rb")
p = pyaudio.PyAudio()
stream = p.open(
format = p.get_format_from_width(wf.getsampwidth()) ,
channels = wf.getnchannels () ,
rate = wf.getframerate () ,
output = True
)
app = QtGui.QApplication([])
app.quitOnLastWindowClosed()
w = QtGui.QWidget() #top level wodgit
w.resize(850, 700)
layout = QtGui.QGridLayout()
w.setLayout(layout)
w.setWindowTitle("Spectrum Analyzer")
pw = pg.PlotWidget()
pw2 = pg.PlotWidget()
pw.getPlotItem()
pw.setYRange(-12000, 12000)
pw.setXRange(0, 440)
pw2.getPlotItem()
pw2.setYRange(0,6000)
pw2.setXRange(0, 1024, padding = 0)
fs = wf.getframerate()
#グラフ描画設定
pw2Axis = pw2.getAxis("bottom")
pw2Axis.setLabel("Frequency [Hz]")
pw2Axis.setScale(fs / 1024)
hz_interval = 500
newXAxis = (arange(int(fs / 2 / hz_interval)) + 1) * hz_interval
oriXAxis = newXAxis / (fs / 2. / (1024 + 1))
pw2Axis.setTicks([zip(oriXAxis, newXAxis)])
layout.addWidget(pw, 0, 1)
layout.addWidget(pw2, 1, 1)
w.show()
num = wf.getnframes()
x = arange(0, num, 2)
chunk = wf.getframerate() / 12
d = wf.readframes(chunk)
data2 = data[::2]
signal_scale = 1. / 500
signal = zeros(chunk, dtype = float)
for i in range(0, num, chunk) :
pw.plot(x[0:chunk], data2[i: i + chunk], clear = True) #波形
signal = data2[i: i+chunk] #フーリエ変換
fftspec = fft(signal)
pw2.plot(abs(fftspec[0: chunk / 2] * signal_scale), clear = True) #スペクトル
stream.write(d)
QtGui.QApplication.processEvents()
d = wf.readframes(chunk)
app.exec_()