音量差分情報による音楽のテンポ推定(Tempo Estimation)


音楽情報解析をいろいろ勉強中です。まず、<b>テンポ推定(Tempo Estimation)</b>をテーマにしていろいろ勉強を進めています。テンポ推定は、その名の通り音楽を入力としてその曲のテンポを推定するというもので、DAWの機能にもあったり、単独で<a href="https://www.mixmeister.com/bpm.php">BPM Analyzer</a>というソフトがあったりもします。

そもそも、テンポ推定というのは<font color="blue">音楽情報処理</font>という分野のようで、テンポ推定のアプローチもいろいろあるみたいです。テンポ推定という問題は、<font color="red"><b>『拍はほぼ等間隔であり、拍位置において和音が変わりやすい』</font></b>と<font color="red"><b>『打楽器音や各ノートが拍位置で発音されることが多い』</font></b>という2点が問題解決のヒントになるようです。音楽情報処理については、<a href="http://www.kecl.ntt.co.jp/people/kameoka.hirokazu/publications/Kameoka2015IEICE06published.pdf">こちらの論文</a>にいろいろ情報がありました。追々、この中身も勉強していきたいです。

今回試したのは、後者の特徴から<b>『拍位置で発音されることが多い』→『拍位置では音量が大きく変わりやすい』</b>という点に着目した手法です。参考にしたのは<a href="http://hp.vector.co.jp/authors/VA046927/tempo/tempo.html">こちら</a>です。C/C++のサンプルコードとかもあります。



#処理について

ざっくり以下の流れです。

1. 音声信号の取り込み
2. 信号のモノラル化
3. 信号を一定間隔で区切り(フレーム化)、<a href="https://ja.wikipedia.org/wiki/%E4%BA%8C%E4%B9%97%E5%B9%B3%E5%9D%87%E5%B9%B3%E6%96%B9%E6%A0%B9">RMS</a>を取り音量情報に変換
4. 直前フレームとの差分情報に変換
5. BPMに対応したsin/cos波と乗算を行い、テンポの推定を行う

1から4までに、波形は下図のように処理されます。
![tenpo_est_ov.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112872/5f472b25-b2ad-8f56-4e33-c3038e5ff814.png)

#本手法の性能について

手持ちの曲をいくつか試してみました。楽譜記載のBPM(楽譜があるなら)とBPM Analyzerによる結果、そして本手法での結果を比較していきます。まぁ、楽譜もオフィシャルなものではないので、楽譜が正解というわけでもないです。

## Deep Purple - Smoke on the water

<a href = "https://www.youtube.com/watch?v=zUwEIt9ez7M">Youtube</a> / <a href="https://musescore.com/user/25871/scores/1703056">Score</a>
誰もが一度は聞いたことがあるリフで有名な曲。コード進行がシンプルなので本手法に向いているかと思った。

||楽譜記載|BPM Analyzer|本手法|
|:----|:--------:|:--------:|:--------:|
|BPM|115|114.96|228.4|

![bpm_smoke.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112872/d4b8c01f-e155-a705-c1c6-c1323e7435bd.jpeg)

228.4がTopに来ているのですが、2番目のピークに113.8があるため、遠からずといった結果になっています。本手法に限らず、テンポ推定では倍数の周波数が出てくることは頻繁にあります。イントロとか間奏のハイハットに引っ張られてるのかも。

## Undertale OST - Megalovania

<a href="https://www.youtube.com/watch?v=ZcoqR9Bwx1Y">Youtube</a> / <a href="https://musescore.com/jestermusician/undertale_megalovania">Score</a>

名作UndertaleからMegalovania。ヴォーカルがないし、打ち込みがメインだとおもうのでテンポ推定しやすいかなぁと。

||楽譜記載|BPM Analyzer|本手法|
|:----|:--------:|:--------:|:--------:|
|BPM|120|119.99|210|

![bpm_megalovania.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112872/76b451cc-cd0e-a75a-de84-99d315550a19.jpeg)


<b>なんでや！！</b>　ピーク自体は、Smoke on the waterと比較するとしっかりでています。ですが、こちらでも本来のBPMは2番目で、Topは210という倍数ではないBPMとなっています。わかんねえ....。

##Alan Walker - Faded

<a href="https://www.youtube.com/watch?v=60ItHLz5WEA">Youtube</a> / <a href="https://musescore.com/user/5596391/scores/4787193">Score</a>

BPM120前後が続いたので、少し遅めの曲を。エレクトロ・ハウスに分類されるのかな？　結構すき。

||楽譜記載|BPM Analyzer|本手法|
|:----|:--------:|:--------:|:--------:|
|BPM|90|90|90|

![bpm_fadedjpg.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112872/8369fa5c-1311-fd32-b675-64b65152acc3.jpeg)

<font color="blue"><b>ようやく一致。</b></font> ピークといえるピークは2か所のみで、BPMとその倍数のみ。Youtubeで聞いてても、テンポは非常に取りやすい。

##Iron Maiden - The Trooper

<a href="https://www.youtube.com/watch?v=X4bgXH3sJ2Q">Youtube</a> / <a href="https://musescore.com/user/8847566/scores/1969606">Score</a>

イギリスのHR/HMのレジェンドであるIron Maidenから1曲。ツインギターに主張の激しいベースと情報量がこれまでの楽曲よりも大きく増えたと思う。

||楽譜記載|BPM Analyzer|本手法|
|:----|:--------:|:--------:|:--------:|
|BPM|150|158.03|98.8 / 138.2|

![bpm_trooper.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112872/17ec19da-110f-d5c1-db1f-5ac87b5d80d8.jpeg)


自分で聞きながらBPM測定した感じ、BPM AnalyzerのBPM158というのが正解だと思う。これだけ情報量が多い楽曲だと、音の起伏が激しいので本手法では難しいのかも。

#まとめ

他にもいろいろな楽曲を試してみたが、あんまり正答率は高くなかった。テンポを音量差分だけで推定するのは楽曲との相性が大きいと思う。Daft punkとかも相性が良かったので、エレクトロ系は相性がいいかも。

逆に、ロック系は厳しかった印象。Smoke on the waterにしても明確なピークが立っていたわけではないし、音の情報量が多いとかなり厳しいのかも。

次は、別のアプローチでのテンポ推定やってみます。

#ソースコード
```tempo_est.m
tic;

% 各種初期パラメータ

% fname = './mp3/10 Smoke On The Water.mp3';
% fname = './mp3/Undertale - Megalovania.mp3';
% fname = './mp3/Alan Walker - Faded.mp3';
fname = './mp3/06 The Trooper.mp3'; 

fr = 1024;
bpm_llim = 40;
bpm_spc  = 0.1;
bpm_ulim = 260;

% 音楽データの読み込み
[dat, fs] = audioread(fname);

% 音楽データ処理 (音量データ -> 音量差分データ)
len = floor(length(dat)/fr);
dat = mean(dat,2); % Lch/Rchで平均をとりモノラルへ

dat_fr = zeros(len,1);
for i = 1:len
    dat_fr(i) = rms(dat(1+(i-1)*fr:i*fr));
end

dat_fr_diff = diff(dat_fr);


% BPM マッチの計算
N = length(dat_fr);
s = fs/fr;
bpm_seq   = bpm_llim : bpm_spc : bpm_ulim;
bpm_match = zeros(length(bpm_seq),1);

for i = 1:length(bpm_seq)
    bpm = bpm_seq(i);
    a_bpm = 0;
    b_bpm = 0;
    for j = 1:N
        a_bpm = a_bpm + dat_fr(j)*cos(2*pi*(bpm/60)*j/s);
        b_bpm = b_bpm + dat_fr(j)*sin(2*pi*(bpm/60)*j/s);
    end
    bpm_match(i) = rms([a_bpm, b_bpm]);
end

bpm_match = bpm_match ./ max(bpm_match);


% 結果表示
plot(bpm_seq, bpm_match);
grid on;
xlim([bpm_llim, bpm_ulim]);
ylim([0, 1.1]);
title(fname);
xlabel('BPM');
ylabel('Matching Ratio (Normalized.)');

toc;
```