前回までのあらすじ
壊れたアコーディオンを買った.
アコーディオンを修理する技能や知識はないので,音響処理を用いて演奏可能な状態に持っていこうと思う.
おそらく壊れているのは,リードを鳴らすための空気弁で,
空気を送ると,常に何かしら一定の音が鳴る状態.
今回はどの音階が常に鳴っているのかを分析する.
したいこと「処理対象の音を分析」
常に音がなるということは,いずれかの鍵盤を押していない間,なっている音を消す必要がある.
消す対象を知るために,処理するべき音がどの音階なのか/どの周波数なのかを知っておく必要がある.
そこで,librosaライブラリを使用する.
librosaは,音響信号処理のライブラリで,
音の周波数や音階を調べるための音響信号処理ができる.
今回,楽器の分析をするにあたって,音階として処理することも重要なので利用するに至った.
分析における注意:収録音について
今回,アコーディオンの演奏音を収録したが,演奏音は,アコーディオンの音のみを拾っているわけではない.
音が鳴る前の空調などのノイズ,鍵盤を押すクリック音などが入っていることに注意する.
また,アコーディオンなどの楽器音は純正弦波とは異なることにも注意する.
スペクトルを表示すると,メインで鳴らしている周波数のみがエネルギーを示すのではなく,
音色を表現する周波数も存在している.
実装(単音の場合)
実装は主に3つで,入力→処理→結果表示の3つに分かれる.
入力はwavファイルの収録音とし,
事前に必要な部分のみを切り出すしてファイルにするのが面倒なので,入力の段階で処理区間を指定する.
処理として,音階と基本周波数の推定を実装する.
結果の表示は,区間のクロマグラフ(音階ごとのスペクトログラム)・音階・基本周波数とする.
実装の言語はpythonだが,環境は jupyter notebook を利用している.
必要なライブラリなど
matplotlibはデフォルトだと日本語を入力すると文字化けするので
日本語対応のフォントを指定しておく.
import librosa
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
font_path = "日本語対応のフォントファイルへのパス"
japanese_font = fm.FontProperties(fname=font_path)
音階の推定
音階は,librosa.feature.chroma_stft
でクロマグラフを表示し,
各時間単位でのエネルギーの中央値を出して,最も高いエネルギーを示している物を音階と定義する.
def estimate_chroma(file_path, start_time, end_time):
# 音声ファイルを音響信号として読み込む
y, samplingrate = librosa.load(file_path, offset=start_time, duration=end_time - start_time)
# 音響信号を音階ごとのスペクトログラム(クロマグラム)に変換する
chroma = librosa.feature.chroma_stft(y=y, sr=samplingrate)
# クロマグラムの表示
plt.figure(figsize=(10, 4))
librosa.display.specshow(chroma, y_axis='chroma', x_axis='time')
plt.title('クロマグラム', fontproperties=japanese_font)
plt.colorbar()
plt.tight_layout()
plt.xlabel('時間(s)', fontproperties=japanese_font)
plt.ylabel('音階', fontproperties=japanese_font)
plt.show()
# クロマグラムから中央値を取得して音階を推定する
median_chroma = np.median(chroma, axis=1)
estimated_scale_number = np.argmax(median_chroma)
# 音階番号を音階に変換する用
scale_names = {
0: 'C ド',
1: 'C# / Db ドのシャープ ',
2: 'D レ',
3: 'D# / Eb レのシャープ',
4: 'E ミ',
5: 'F ファ',
6: 'F# / Gb ファのシャープ',
7: 'G ソ ',
8: 'G# / Ab ソのシャープ',
9: 'A ラ',
10: 'A# / Bb ラのシャープ',
11: 'B シ'
}
estimated_chroma = scale_names[estimated_scale_number]
return estimated_chroma
基本周波数の推定
ファイルを読み込んで,librosa.core.pitch.yin
を用いて基本周波数を推定する.
各時間単位ごとに基本周波数が求められるので,その中央値を基本周波数として定義する.
def estimate_pitch(file_path,start_time, end_time):
# 音声ファイルを音響信号として読み込む
y, samplingrate = librosa.load(file_path, offset=start_time, duration=end_time - start_time)
# YINアルゴリズムによる推定
pitch = librosa.core.pitch.yin(y=y, sr=samplingrate, fmin=50, fmax=2000)
# 中央値を代表の基本周波数とする
ave_pitch = np.median(pitch)
return ave_pitch
main部分
開始秒と終了秒を指定して各関数に突っ込む形式にした.
file_path = 'wav/non.wav'
start_time = 1
end_time = 3
result_chroma = estimate_chroma(file_path,start_time,end_time)
result_pitch = estimate_pitch(file_path,start_time,end_time)
print(f"音階: {result_chroma}")
print(f"基本周波数: {result_pitch:.3f} Hz")
動作させる
実際に以下のファイルを読み込んで動作させる.
前半が蛇腹を開くとき,後半が蛇腹を閉じるときである.
それぞれの結果は以下のようになった.
開いた際になっているのは,D (レ)の音かつ294Hzと出力された.
正弦波と聞き比べてみるとあっているように思う.
閉じた際になっているのは, D (レ)の音かつ58Hzと出力された
これは明らかに違う.閉じる時は開いた時の音に加えてもう1音鳴っているため出力結果が合わない.
クロマグラフを見ると,AとBの間,ラのシャープが鳴っているように見える.
ラのシャープであること分かったが,どの音の高さを持つのかが分からないままである.
基本周波数はフレームに対して1つであるため,複数の音階が鳴っている場合は正しく求めることができない.
まとめ
今回,librosaを用いることで,アコーディオンの蛇腹を開く際に常に出る音が,D (レ)の音かつ294Hzであることが分かった.
一方,蛇腹を閉める際に出る音が複数の音階であるため,レとラのシャープが鳴っていることはわかったが,ラのシャープの周波数を求めることができなかった.
次回は,複数音が鳴っている環境下で特定の音の周波数を求める方法を検討する.