はじめに
前回の記事にて、python-sounddeviceというライブラリを使用して、マイク入力の信号をリアルタイムにプロットすることを試しました。
今回はこれを応用して、リアルタイムなスペクトラムアナライザを作成してみたいと思います。
スペクトラムアナライザ、通称スペアナは信号データを周波数成分別に表示するものです。
よくオーディオ機器の画面に表示されていたりするやつです。
今回は高速フーリエ変換(FFT)を用いてリアルタイムに振幅スペクトルを表示できるようにしてみたいと思います。
sounddeviceによる音声のリアルタイムプロット
前回使ったコードを少し変えるだけでスペアナの実装はできそうです。
↓詳細はこちら
参考までに前回のコードを少し修正したものを載せておきます。
import sounddevice as sd import numpy as np from matplotlib.animation import FuncAnimation import matplotlib.pyplot as plt device_list = sd.query_devices() print(device_list) sd.default.device = [1, 6] # Input, Outputデバイス指定 def callback(indata, frames, time, status): # indata.shape=(n_samples, n_channels) global plotdata data = indata[::downsample, 0] shift = len(data) plotdata = np.roll(plotdata, -shift, axis=0) plotdata[-shift:] = data def update_plot(frame): """This is called by matplotlib for each plot update. """ global plotdata line.set_ydata(plotdata) return line, downsample = 10 length = int(1000 * 44100 / (1000 * downsample)) plotdata = np.zeros((length)) fig, ax = plt.subplots() line, = ax.plot(plotdata) ax.set_ylim([-1.0, 1.0]) ax.set_xlim([0, length]) ax.yaxis.grid(True) fig.tight_layout() stream = sd.InputStream( channels=1, dtype='float32', callback=callback ) ani = FuncAnimation(fig, update_plot, interval=30, blit=True) with stream: plt.show()
FFT解析
スペクトラムアナライザは前回作成したコードのグラフを更新する関数にて、ただ信号をプロットするのではなく、FFTを行えばいいだけです。
FFT解析の実装方法については過去記事にて詳しくまとめてますので参照ください。
ほぼ以下の記事のコードのコピペで何とかなりそうです。
リアルタイムで収音された信号データはplotdataという配列の後ろからどんどん更新されていきますのでFFT解析で使用するデータはplotdataの後ろから2048個のデータとします。
高速フーリエ変換なので2の累乗のデータ数としています。
実装
それではさっそく実装していきましょう。
前回のコードでいじるのは、グラフの初期化の部分と、グラフ更新関数の部分です。
グラフはx軸に周波数、y軸に振幅スペクトルをプロットします。
全コードは以下です。
import sounddevice as sd import numpy as np from matplotlib.animation import FuncAnimation import matplotlib.pyplot as plt from scipy import signal device_list = sd.query_devices() print(device_list) sd.default.device = [1, 6] # Input, Outputデバイス指定 def callback(indata, frames, time, status): # indata.shape=(n_samples, n_channels) global plotdata data = indata[::downsample, 0] shift = len(data) plotdata = np.roll(plotdata, -shift, axis=0) plotdata[-shift:] = data def update_plot(frame): """This is called by matplotlib for each plot update. """ global plotdata, window x = plotdata[-N:] * window F = np.fft.fft(x) # フーリエ変換 F = F / (N / 2) # フーリエ変換の結果を正規化 F = F * (N / sum(window)) # 窓関数による補正 Amp = np.abs(F) # 振幅スペクトル line.set_ydata(Amp[:N // 2]) return line, downsample = 1 # FFTするのでダウンサンプリングはしない length = int(1000 * 44100 / (1000 * downsample)) plotdata = np.zeros((length)) N =2048 # FFT用のサンプル数 fs = 44100 # 音声データのサンプリング周波数 window = signal.hann(N) # 窓関数 freq = np.fft.fftfreq(N, d=1 / fs) # 周波数スケール fig, ax = plt.subplots() line, = ax.plot(freq[:N // 2], np.zeros(N // 2)) ax.set_ylim([0, 1]) ax.set_xlim([0, 3000]) ax.set_xlabel('Frequency [Hz]') ax.set_ylabel('Amplitude spectrum') fig.tight_layout() stream = sd.InputStream( channels=1, dtype='float32', callback=callback ) ani = FuncAnimation(fig, update_plot, interval=30, blit=True) with stream: plt.show()
結果はこちら。
適当にマイクでしゃべった結果です。
動画ではカクついてますが、実際はカクつきは感じないです。
matplotlibはプロットが遅いとよく言われますが、30msecごとのプロットであれば違和感なくプロットできているように思います。
おわりに
matplotlibでスペクトラムアナライザを作成するのは難しいかと思ってましたが、案外ちゃんと機能するものができました。
もっとオーディオ機器の画面みたいにビニングして表示してみるのも面白そうです。