プログラム日記φ(..)

おもにPython関係のプログラムメモ

librosaで楽曲のテンポ(BPM)を推定してみる


Pythonのオーディオおよび音楽解析のためのライブラリーlibrosaで、実際の楽曲のテンポを正しく抽出できるか、ちょっと実験してみました。

 

ところで、テンポとは音楽楽曲に含まれる一定間隔に刻まれるリズム(拍)の速さのことで、 一分間の拍数をテンポというそうで、BPM(Beats Per Minute)とも表されるそうですが

 

以下の家入レオさんの『Shine』という曲からlibrosaでテンポが推定できるか、まずは調べてみることに。

 

と、その前に!

 

何らかのソフトを使って楽曲のテンポを調べる前に、まず、この曲のテンポがわかってないことにはソフトの出力結果が正しいかどうかも判断できないので、人力で楽曲のテンポを求めるやりかたですが

 

いたって簡単で。

以下の楽曲動画を聴きながら1分間拍数を数えてもいいのですが、忙しい方のために

  1. スマフォのストップウォッチのアプリを使い、曲を聴きながらリズムに合わせて1から11まで数字を数え、その間10泊分の秒数を測ります。(1を唱えながらストップウォッチをスタートし、11を数えながらストップウォッチをストップする)
  2. で、1分間である60秒を1の結果の秒数で割ると、1分間に10拍が何回あるかの数字が出ますので、それに10を掛ければ1分間の拍数がわかります。

 

実際やってみると、ストップウォッチを人力で押すので誤差は出ますが、以下の曲だと10拍4.68秒ぐらいなので、10*60/4.6でテンポは 128.2BPMぐらいでしょうか。

 

といううことで、こういう場合、お試しスクリプトの実行にはjupyter notebookやIPythonを使うと便利ですが、実際にlibrosaで家入レオさんの『Shine』のテンポを求めてみたのが以下で、129.19921875と人力ストップウォッチの結果と大体あってるようで、正しいようですね。

 

In [1]: import librosa
   ...: import numpy as np
   
In [2]: # mp3読み込み
   ...: y, sr = librosa.load('./mp3/0000000571.mp3')
In [3]: # テンポとビートの抽出
   ...: tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)

In [4]: tempo
Out[4]: 129.19921875

 

 

と、他の曲で試しても大体大丈夫のようなのですが、現実の音楽の場合は全てで上手くゆくわけではなく、実際のテンポの倍数のテンポを曲のBPMとして結果を返してくる場合があるようです。

 

以下はTVアニメ『一週間フレンズ。』のエンディングテーマ。雨宮天さんのスキマスイッチ『奏』のカバーバージョンですが、人間の耳で拍数を数えると10拍7.93秒ぐらいですので、10*60/7.93 = 75.66ぐらいだと思うのですが、

 

librosaによる結果は以下で、実際の拍数の倍になってます。

 In [5]: # mp3読み込み
   ...: y, sr = librosa.load('./mp3/0000000002.mp3')

In [6]: # テンポとビートの抽出
   ...: tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr)

In [7]: tempo
Out[7]: 151.99908088235293
   

 

Librosaには音の開始地点(オンセット)を検出するlibrosa.onset.onset_strength()関数があるので、これはどういうことなんだろうと調べてみると、4分の4拍子の曲だからっていつも4分音符の音ばかりでなく、8分音符や付点4分音符に相当する音だってあるわけですから、音の開始点の間隔からテンポを推定する際に、4分の4拍子の曲だと8分音符に相当する音の強弱をベースの間隔だと勘違いするところからくるのですかね。

 

実際、楽曲の中に含まれている音の開始地点(オンセット)を検出して、どのテンポにピークがあるかを図示してみると以下で

 

# onset検出
onset_env = librosa.onset.onset_strength(y, sr=sr)

mport matplotlib.pyplot as plt
hop_length = 512
ac = librosa.autocorrelate(onset_env, 2 * sr // hop_length)
freqs = librosa.tempo_frequencies(len(ac), sr=sr,hop_length=hop_length)
plt.figure(figsize=(8,4))
plt.xlim(10, 300)
plt.plot(freqs,ac,label='Onset autocorrelation')
plt.axvline(tempo, 0, 1, color='r', alpha=0.75, linestyle='--',
           label='Tempo: {:.2f} BPM'.format(tempo))
plt.grid()
plt.title('Static tempo estimation')
plt.legend(frameon=True)
plt.xlabel('Tempo (BPM)')
plt.show()

 

f:id:memomemokun:20190909154024p:plain

 

 

151.99908088以外にも実際のテンポに等しい75.99954044にもピークがあるのがわかりますね。

 

In [15]: from scipy.signal import argrelmax

In [16]: extremum=argrelmax(ac, order=1) #極大値

In [17]: freqs[extremum]
Out[17]: 
array([287.109375  , 151.99908088,  99.38401442,  75.99954044,
        60.09265988,  49.69200721,  43.06640625,  37.44904891,
        33.55823864])

 

ということで、librosaで楽曲のテンポ(BPM)を推定する際には、上の図のようなオンセットの間隔から、頻度の極大値を求めて、別途、テンポを推定してやるということも必要かもしれませんね???

 

つづく、