著者:石塚 美伶
今回は、音楽の「耳コピ」を支援する音源可視化ツールをPythonで制作する方法について紹介します。制作するツールは、WAVファイルを読み取って音を判別し、その音をピアノの鍵盤で示すGIFアニメーションを生成します。
シェルスクリプトマガジン Vol.69は以下のリンク先でご購入できます。
図4 「main.py」ファイルに記述する内容(その1)
1 2 3 4 5 6 7 8 |
import scipy.io.wavfile # WAVファイルを読み込むために使用 from scipy import signal # 極大値を求めるために使用 import numpy as np # データの整形や高速フーリエ変換に使用 import pandas as pd # ピアノの音階辞書を作成するために使用 import matplotlib.pyplot as plt # 図の作成に使用 import matplotlib.animation as anm # アニメーション作成に使用 import matplotlib.patches as pat # 長方形を描画するために使用 import time # 時間を表示するために使用 |
図5 「main.py」ファイルに記述する内容(その2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
wav_filename = "./source.wav" # 音声ファイルの読み込み rate, data = scipy.io.wavfile.read(wav_filename) print("サンプリング周波数:", rate) print("データ数:", len(data)) if data.ndim < 2: # 配列の次元数 print("モノラル") else: print("ステレオ") data = np.ravel(data)[::2] # 連結して偶数要素を抽出する #(振幅)の配列を作成 (「-1」~「1」の範囲に正規化) data = data / 32768 # データを0.1秒ごとに分割 split_datas = np.array_split(data, int(len(data) / (rate * 0.1))) |
図6 「main.py」ファイルに記述する内容(その3)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
count = 0 ex_freqency = [] # 抽出したデータを格納するために用意 for short_data in split_datas: ex_freqency.append([]) # データを格納するために空リストを追加 # フーリエ変換により周波数成分と振幅を取得 fft_short_data = np.abs(np.fft.fft(short_data)) freqList = np.fft.fftfreq(short_data.shape[0], d=1.0/rate) maxid = signal.argrelmax(fft_short_data, order=2) # 極大値を求める for i in maxid[0]: if fft_short_data[i] > 10 and 25 < freqList[i] < 4200: ex_freqency[count].append(freqList[i]) # 周波数を格納 count += 1 |
図8 「main.py」ファイルに記述する内容(その4)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
piano_dic = pd.read_csv("./piano_dict.csv", encoding="utf-8") print(piano_dic) black_keys = piano_dic[piano_dic["scaleNameEn"].str.contains("#")].index print(black_keys) count = 0 keys = [] # 含まれる周波数の行 for row in ex_freqency: keys.append([]) # 各フレームの周波数を格納するために空リストを追加 for i in row: # 差が最小の音階 key = piano_dic.loc[abs(piano_dic.frequency - i).idxmin(), "keyNumber"] if (key in keys[count]) == False: keys[count].append(key) # 重複してなければ音階を追加 count += 1 print(keys) |
図9 「main.py」ファイルに記述する内容(その5)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
fig, ax = plt.subplots(figsize = (10, 2)) # 各フレームの描画 def update(i, fig_title, data_list, ax): if i != 0: plt.cla() # 現在描写されているグラフを消去 ax.axis("off") # 軸と目盛りを削除 ax.set_xlim(0, 52.1) ax.set_ylim(-0.5, 2.5) skip = False white_count = 0 plt.title(fig_title + time.strftime("%M:%S", time.gmtime(i * 0.1))) for j in range(0, 88): if skip == True: skip = False # フラグを降ろす continue # 既に描画した白鍵をスキップする if j in black_keys: # 黒鍵の右側の白鍵を描画 color = "white" if j + 1 in data_list[i]: color = "red" # 音が鳴っていれば色を赤にする # 長方形を作成 rec = pat.Rectangle(xy = (white_count, 0), \ width = 1, height = 1.5, \ fc = color, ec = "black") ax.add_patch(rec) # Axesに長方形を追加 skip = True # スキップフラグを立てる # 白鍵の上に黒鍵を描画 color = "gray" x, y = white_count - 0.3, 0.5 w, h = 0.6, 1 else: # 白鍵を描画 color = "white" x, y = white_count, 0 w, h = 1, 1.5 if j in data_list[i]: color = "red" # 音が鳴っていれば色を赤にする # 長方形を作成 rec = pat.Rectangle(xy = (x, y), width = w, \ height = h, fc = color, ec = "black") ax.add_patch(rec) # Axesに長方形を追加 white_count += 1 # 白鍵の数をカウント # アニメーションを生成 ani = anm.FuncAnimation(fig, update, fargs=("Mimicopy ", keys, ax), \ interval=100, frames=len(keys)) # GIFファイルとして保存 ani.save("Sample.gif", writer="pillow") |