前段时间在研究节拍检测,网上的都解释得不怎么详细,但是都有原文链接,靠着划词翻译勉强看懂了,下面写一些自己的理解。
所谓节拍检测其实就是峰值判断,原理是设定一个时间范围(假设是1ms),用当前1ms内的“音量值”与前后1ms的“音量值”做比较(暂时叫音量值比较好理解),如果比前后都要大很多(实际不是那么简单),那么这1ms就可以标记为节拍。比如鼓点听起来是不是比较大声。
时间范围(窗口):
一般音乐每秒采样44100次,一秒就会有44100个采样,我们以1024个采样为一个窗口(可能人能感觉到的最短声音时间是1024/44100秒,),一秒大约就会有44个窗口,一首3分钟的歌就大约会有7920个窗口,我们要做的是根据窗口的“音量值”来进行一些操作,然后判断该窗口是不是一个节拍。
音量值的获取(光谱通量):
在时域上,音乐信号是杂乱无章的波形,就算分割成时间片也难以做出比较,所以我们就要将窗口里的信号采样通过快速傅里叶转换到频域上再进行比较(fft会调用函数就可以),1024个样本转换到频域上会有513个频率点(频率箱),然后再将该窗口上的频谱与上一个窗口的频谱差分,就会得到该窗口的“音量值”,也就是光谱通量(我觉得应该叫频谱通量但spectral翻译是光谱。。)。
以下是差分算法:
spectrum是该窗口的频谱,spectralFlux用来存放整一首歌每个窗口的光谱通量。
`
float flux = 0;
for( int i = 0; i < spectrum.length; i++ )
{
float value = (spectrum[i] - lastSpectrum[i]);
flux += value < 0? 0: value;//value<0则等于0
}
spectralFlux.add( flux ); `
节拍点的获取:
现在我们有了每个窗口的光谱通量,接下来我们可以判断该窗口是不是节拍点了,不是简单的前后比较,而是使用阈值(threshold)方法判断该窗口是不是节拍点,就是把该窗口的后10个窗口(这个可以自己定)的光谱通量加起来取一个平均值,平均值再乘一个比例就是阈值,然后判断该窗口的光谱通量是否大于阈值,如果是,那么基本就是节拍点了。
获取阈值:
for( int i = 0; i < spectralFlux.size(); i++ )
{
int start = Math.max( 0, i - THRESHOLD_WINDOW_SIZE );
int end = Math.min( spectralFlux.size() - 1, i + THRESHOLD_WINDOW_SIZE );
float mean = 0;
for( int j = start; j <= end; j++ )
mean += spectralFlux.get(j);
mean /= (end - start);
threshold.add( mean * MULTIPLIER );
}
红线是光谱通量,绿线是阈值
比较大小:
for( int i = 0; i < threshold.size(); i++ )
{
if( threshold.get(i) <= spectralFlux.get(i) )
prunnedSpectralFlux.add( spectralFlux.get(i) - threshold.get(i) );
else
prunnedSpectralFlux.add( (float)0 );
}
阈值处理后,我们再判断一下后面一格有没有大于当前的值,没有的话就真的是节拍点了:peaks用来存放整一首歌每个窗口的节拍点
for( int i = 0; i < prunnedSpectralFlux.size() - 1; i++ )
{
if( prunnedSpectralFlux.get(i) > prunnedSpectralFlux.get(i+1) )
peaks.add( prunnedSpectralFlux.get(i) );
else
peaks.add( (float)0 );
}
最后的效果:
参考文献
[1] http://www.badlogicgames.com/wordpress/?p=161
[2] https://blog.csdn.net/highmiao_19/article/details/85010391
这里面有个unity的demo 写得非常好 [3]https://blog.csdn.net/frankfchaaa/article/details/82224048