3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

音声データを周波数に分解して描画する

Last updated at Posted at 2019-08-12

はじめに

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つの音程にも複数の周波数(倍音)が混ざっていることがわかります。
この周波数の分布がそれぞれの楽器特有の音色を構成していると推測されます。
FFT.png

複数のライブラリを使用しています。
詳細はソースコードを参照ください。

ソースコード

いろいろ雑なところがあるので、後日リファクタリングします。

# 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_()

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?