概要¶
Pythonを使って音楽ファイルを読み込み、サウンドスペクトログラムを作る手順を整理しました。大まかな手順は以下です。
サウンドスペクトログラムとは以下のような横軸時間、縦軸周波数スペクトルをとったヒートマップです。
スペクトログラムって何?ということについては↓このあたりがイメージつかみやすいかと思います。
http://blog.media.teu.ac.jp/2015/05/post-0ec1.html
音声ファイルはスーパーマリオ64のウォーターランドの曲を使わせていただきました。
https://www.nintendo.co.jp/ds/series/dsi/menu/sound/download.html
以下のモジュールをインポートしておきます。(ない場合にはpip等でインストールおねがいします)
from pydub import AudioSegment
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
なお、pydubを使うにはffmpegが必要になるので、準備しておきましょう。以下記事等を参考にしてみてください。
Pythonのmatplotlibでgifアニメを作成する
pydubを使って音楽ファイルを読み込み¶
読み込みは以下1行で実行できます。以下例ではm4aを対象にしていますが、mp3やwav等でも同様のコード(m4a部分を変える)で読み込むことができるはずです。
sound = AudioSegment.from_file("ウォーターランド.m4a", "m4a")
基本情報は以下で取得できます。
https://qiita.com/itoru257/items/8af2902d8ce851ae74ea
#チャンネル数(1:mono, 2:stereo)
sound.channels
#サンプルレート(Hz)
sound.frame_rate
#再生時間(秒)
sound.duration_seconds
get_array_of_samplesを使うことで、生データをListで受け取ることができます。後々のことを考えて、numpyで読み込み、加えてステレオ音声から片側だけの音を抽出しておきます。
samples = np.array(sound.get_array_of_samples())
sample = samples[::sound.channels]
簡単にプロットするとこんな感じ。
plt.plot(sample[::10])
numpyを使ってフーリエ変換を実施し、スペクトログラムを作成¶
まずはスペクトルを計算してみる¶
フーリエ変換はnumpyの機能を使って簡単に実施することができます。
https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.fft.html
#フーリエ変換自体は以下1行を実行するだけ
spec = np.fft.fft(sample)
#スペクトルの値(複素数)が入っている。
spec
スペクトル(複素数)の取り扱いを簡単に列挙しておきます。
#実数部分を取り出し
spec.real
#虚数部分を取り出し
spec.imag
#絶対値を取り出し
np.abs(spec)
#偏角を取り出し
np.angle(spec)
#絶対値と偏角から複素数を復元
np.exp(1j*np.angle(spec))*np.abs(spec)
#逆変換
np.fft.ifft(spec)
#周波数
np.fft.fftfreq(sample.shape[0], 1.0/sound.frame_rate)
#スペクトルをプロット表示
freq = np.fft.fftfreq(sample.shape[0], 1.0/sound.frame_rate)
plt.plot(freq, np.abs(spec))
負の領域は使わないことや、周波数0の値が2倍されていること、対数メモリのほうが見やすことに留意して以下のように表示します。
freq = freq[:int(freq.shape[0]/2)]
spec = spec[:int(spec.shape[0]/2)]
spec[0] = spec[0] / 2
#plt.xscale("log")
plt.yscale("log")
plt.plot(freq, np.abs(spec))
どうやら100〜1000Hzのスペクトルが強いことが分かりました。
スペクトログラムを作成¶
上では全体の波形を使って1つのスペクトルを算出しましたが、これを一定の幅を取り、それを刻み幅ずつ動かしながら算出していくことでスペクトログラムが作成できます。
#窓幅
w = 1000
#刻み
s = 500
#スペクトル格納用
ampList = []
#偏角格納用
argList = []
#刻みずつずらしながら窓幅分のデータをフーリエ変換する
for i in range(int((sample.shape[0]- w) / s)):
data = sample[i*s:i*s+w]
spec = np.fft.fft(data)
spec = spec[:int(spec.shape[0]/2)]
spec[0] = spec[0] / 2
ampList.append(np.abs(spec))
argList.append(np.angle(spec))
#周波数は共通なので1回だけ計算(縦軸表示に使う)
freq = np.fft.fftfreq(data.shape[0], 1.0/sound.frame_rate)
freq = freq[:int(freq.shape[0]/2)]
#時間も共通なので1回だけ計算(横軸表示に使う)
time = np.arange(0, i+1, 1) * s / sound.frame_rate
#numpyの配列にしておく
ampList = np.array(ampList)
argList = np.array(argList)
上記でスペクトログラムのデータを作成することができました。
matplotlibとseabornでスペクトログラムを可視化¶
次に実際に可視化をしていきます。
df_amp = pd.DataFrame(data=ampList, index=time, columns=freq)
#seabornのheatmapを使う
plt.figure(figsize=(20, 6))
sns.heatmap(data=np.log(df_amp.iloc[:, :100].T),
xticklabels=100,
yticklabels=10,
cmap=plt.cm.gist_rainbow_r,
)
100Hz〜500Hzくらいが高いという、かなりはっきりした傾向がありますね。
偏角の方も可視化してみます。
df_arg = pd.DataFrame(data=argList, index=time, columns=freq)
plt.figure(figsize=(20, 6))
sns.heatmap(data=df_arg.iloc[:, :100].T,
xticklabels=100,
yticklabels=10,
cmap=plt.cm.gist_rainbow_r,
)
こちらはほぼランダムに見えますね。それとも、スペクトルと何か相関があるのでしょうか。
まとめ¶
ちょっと今さから感もありますが、Pythonでサウンドスペクトログラムを作ってみました。引き続き、スペクトルと偏角に関係があるかどうか検討していこうと思います。