Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

楽曲のBPMを自動算出するプログラムの作り方(Pythonサンプルコード付き)

はじめに

音楽分析では,楽曲のテンポを表す BPM (Beats Per Minute) は重要な情報です。

例えば、音楽ジャンルごとに典型的なBPM/テンポがあるので、BPM/テンポはその楽曲がどんな音楽ジャンルに属するかを知る手がかりとなります。

一般的ないくつかのジャンルに典型的なテンポは次のようになっています。
・ダブ:60~90 bpm.
・ヒップホップ:60~100 bpm.
・ハウス:115~130 bpm.
・テクノ/トランス:120~140 bpm.
ダブステップ:135~145 bpm.
ドラムンベース:160~180 bpm.
テンポとジャンル | Learning Music (Beta)

BPMは自分でカウントすることで測定できますが、楽曲数が多いとなかなか大変です。

そこで今回は、BPM を自動算出するプログラムの作り方を紹介します。

BPM の定義

BPM は、楽曲のテンポ(=進行速度)を表す尺度で す。

その名の通り、1分間に拍(ビート)がいくつ含まれるかを示しています。
端的に言えば、メトロノームの音が1分間に鳴る回数です。

つまり、楽曲の拍(ビート)を検出し、1分間に含まれる拍をカウントすることで
BPMを求めることができます。

BPM 自動算出アルゴリズムの設計方針

以下は、4/4拍子の8ビートのドラムの波形です。
選択範囲内で4つの拍を検出できれば、正しいBPMを算出することができます。
波形を見てみると、音圧(音量)が大きいところを拍とすれば
拍の数を算出できそう
だということが分かります。

ただし、実際の波形には細かい音量の起伏があります。
そのため、生の波形で音量の大きなところを見つけるのではなく、
音圧の、おおよその形状から拍を検出する方が良さそうです。

また、細かい音圧のピークはハイハットであり、
こういった拍検出に関係のない成分は無視/除去することが重要だと気付きます。

f:id:Kurene:20191010163924p:plain

従って、元波形より、

  1. 拍検出に邪魔な成分(音)を除去
  2. 音圧の変化が検知しやすい、滑らかな信号(=音圧の、おおよその形状)の算出

することで、拍検出がしやすい信号を生成することで拍検出/BPM算出ができます。

ただし、拍の数を数えるのは音圧のピークに閾値を設定することになるため実用的ではありません。
一般的には、BPMを算出する=数百ミリ~数秒程度の周期を推定すると考え、
拍検出がしやすい信号に対して周波数分析を行うことでBPMを算出します。

例:BPM=120とすると、120/60=2、すなわち1秒間に拍が2個あるため、2.0Hz周期の信号と考えられます。

参考:http://hp.vector.co.jp/authors/VA046927/tempo/tempo.html

BPM自動算出アルゴリズムの例

処理の流れ

先ほどの方針に則ったアルゴリズムを紹介します。
処理の流れは以下の通りです。

  1. サンプリング周波数を44.100 Hz から200Hz変換(ローパスフィルタ)
  2. 振幅絶対値 or パワー値の信号に変換
  3. BPMに対応する複素正弦波とのマッチング(内積を計算)
  4. 内積が最も大きい複素正弦波に対応するBPMを、BPM推定値として出力する

1., 2.により、拍検出が容易な信号を生成しています。

コード

import numpy as np
import librosa
import matplotlib.pyplot as plt

duration = 30
x_sr = 200
bpm_min, bpm_max = 60, 240

# 楽曲の信号を読み込む
filepath = "Perfume_FLASH.mp3"
y, sr = librosa.load(filepath, offset=38, duration=duration, mono=True)

# ビート検出用信号の生成
# リサンプリング & パワー信号の抽出
x = np.abs(librosa.resample(y, sr, x_sr)) ** 2
x_len = len(x)

# 各BPMに対応する複素正弦波行列を生成
M = np.zeros((bpm_max, x_len), dtype=np.complex)
for bpm in range(bpm_min, bpm_max): 
    thete = 2 * np.pi * (bpm/60) * (np.arange(0, x_len) / x_sr)
    M[bpm] = np.exp(-1j * thete)

# 各BPMとのマッチング度合い計算
#(複素正弦波行列とビート検出用信号との内積)
x_bpm = np.abs(np.dot(M, x))

# BPM を算出
bpm = np.argmax(x_bpm)
print(bpm)

結果

PerfumeのFLASHBPMを算出してみました。
BPM=128のところに最大ピークがあります。
f:id:Kurene:20191010221729p:plain

まとめ

Python を用いて、楽曲のBPMの算出を行いました。
楽曲の拍 (ビート) を検出し、その周期性を計算することでBPMを算出することができます。

より高度なBPM算出や拍検出については、以下の資料が参考になります。
https://www.audiolabs-erlangen.de/resources/MIR/2017-GI-Tutorial-Musik/2017_MuellerWeissBalke_GI_BeatTracking.pdf

なお、Python向けの音楽信号分析ライブラリ libROSA ではBPMを算出する関数が用意されているので、BPM自動算出のアプリ/プログラムをさくっと作りたい方は、以下の記事もご覧いただくことをオススメします

www.wizard-notes.com