なんとなくですができました。
考え方。拍の部分は他より音圧が大きいので、時系列の音圧データから周期的に(何個かおきに)値を取り出して平均をとったとすると、取り出した値が全て拍の部分だったときに平均値は最大になる。逆の言い方をすると、時系列音圧データから色々な周期(間隔)で値を取り出しそれぞれの場合の平均値を求める。(拍と曲の開始の時間的なずれも考慮しなくてはならない。)この平均値が最大になるときの周期からBPMを算出することができる(たぶん)
音楽知識がろくにないため一部適当にごまかしましたが、K5 Auto Tempo Getterというテンポ取得ソフトの結果と比べても近い値を出します。
同じフォルダにVBMP3.dllが必要です。
#include "d3m.hsp"
#uselib "vbmp3.dll"
#func vbmp3_init "vbmp3_init"
#func vbmp3_free "vbmp3_free"
#func vbmp3_open "vbmp3_open" sptr, sptr
#func vbmp3_close "vbmp3_close"
#func vbmp3_play "vbmp3_play"
#func vbmp3_restart "vbmp3_restart"
#func vbmp3_pause "vbmp3_pause"
#func vbmp3_getSpectrum "vbmp3_getSpectrum" sptr, sptr
#const SAMPLENUM 1000 //判定に使うサンプル数
#const LOW 100 //BPM検出範囲
#const HIGH 199
#const BEAT 2 //拍数?(よくわからないが2にすると合うみたい)
#const AMP 2 //表示倍率
screen 0, SAMPLENUM, 250
onexit *Exit
onkey *Analyze
dim specL, 256
dim specR, 256
sdim InputInfo, 272
vbmp3_init
dialog "", 16, "mp3;*.wav"
if stat=0 { end }
vbmp3_open refstr, varptr(InputInfo)
vbmp3_play
vbmp3_pause
*Analyze
dim data, SAMPLENUM
color : boxf
color 64, 64, 64 : line ginfo_winx, ginfo_winy-20, 0, ginfo_winy-20
color 255, 0, 0
pos 0
vbmp3_restart
time = d3timer()
repeat SAMPLENUM
i = cnt
vbmp3_getSpectrum varptr(specL), varptr(specR)
repeat 256
data(i) += (specL(cnt)+specR(cnt))/2
loop
data(i) /= 256 //音圧
redraw 0
line i, ginfo_winy-20-data(i)*AMP
redraw 1
wait 1
loop
time = d3timer()-time //全サンプルの時間(ms)
vbmp3_pause
dt = double(time)/SAMPLENUM //1サンプルの時間(ms)
i1 = 60.0*1000/HIGH*BEAT/dt //検出BPM範囲をサンプル数に換算
i2 = 60.0*1000/LOW*BEAT/dt
maxval = 0
repeat i2-i1, i1
i = cnt
repeat i //全てのずれのパターンを試す
j = cnt
val = 0
k = 0
repeat SAMPLENUM
if (cnt\i=0)&(cnt+j<SAMPLENUM) {
val += data(cnt+j)
k++
}
loop
val = val/k
if val>maxval { maxval = val : interval = i : shift = j }
loop
loop
color 0, 255, 0
if interval=0 { title "failed" : stop }
repeat SAMPLENUM/interval+1
line interval*cnt+shift, 0, interval*cnt+shift, ginfo_winy
loop
color 255, 255, 255 : pos 0, 0
title ""+strf("%.1f", (double(SAMPLENUM)/interval)/(double(time)/1000/60)*BEAT) + "BPM"
stop
*Exit
vbmp3_close
vbmp3_free
end