音符起始点检测(音频节奏检测)(5)

原文链接:Onset Detection Part 5: The (Discrete) Fourier Transform

警告:这是我理解离散傅里叶变换的方法,在一些地方可能是错误的。对于傅里叶变换的正确解释,我建议阅读

http://www.dspguide.com/ch8.htm

(译注:上面的链接我没打开,个人推荐看下面这一篇)

http://www.opticsjournal.net/Mobile/postdetails/PT160728000122iOlRn?code=3&from=singlemessage&isappinstalled=0

最后我们来看看可怕的傅里叶变换。让我们复习一些我们已经知道的东西。首先要记住的是,我们的音频数据是从某种录音设备中获取的,或者是数字合成的。在任何情况下,我们所拥有的是测量离散时间点的振幅,我们称之为样本。我们可以有单声道样本也可以有立体声样本,在后一种情况下,每个时间点有两个样本而不是一个。采样频率是我们每秒测量的采样数(对于立体声,我们将左右通道的采样同时计算为一个)。以下是音符 A (440Hz)的1024个样本,采样频率为44100Hz:

图片来自原文,谁能告诉我咋把水印去了?

注意:左边的垂直线不是实际图像的一部分。是wordpress搞砸了

采样速率为44100Hz的1024个样本的时间跨度为4307ms。从这张图中我们可以看到,这些样本形成了类似于正弦波的东西。事实上,这些样本是使用Math.sin()生成的,就像我们在本系列第一篇文章中所做的那样,它证实了声音只不过是不同频率和振幅的正弦波的混合物。

一个叫傅里叶的聪明数学家曾经提出一个定理,所有可能的函数都可以用一系列所谓的正弦曲线来近似,正弦曲线是具有不同频率和振幅的正弦波(实际上高斯已经知道了这一点,但傅里叶得到了名声)。他为所谓的连续函数做了所有这些魔术你们会在学校的分析课上记得(你们记得分析,对吧?)它被推广到离散函数中,你猜怎么着,我们的样本就是,一个时间上的离散函数。现在,傅里叶变换把一个信号分解成一组不同频率和振幅的正弦信号。如果我们对离散音频信号进行傅里叶变换我们就得到了时域音频信号的频域表示。这个变换是可逆的,所以我们可以这样或那样做。从现在开始,我们只考虑音频信号上的离散傅里叶变换,以免使问题过于复杂。

时域信号的频域告诉我们什么?简单地说,它告诉我们频率对时域信号有多大的影响。频域信号可以告诉我们音频信号中是否有440Hz的音符A以及这个音符相对于整首曲子有多高。这是非常棒的,因为它允许我们调查特定的频率。从上一篇文章的频率图中我们知道各种乐器可以产生不同的频率。如果我们想专注于唱歌我们可以看看频率范围比如80Hz到1000Hz。当然,乐器的频率范围是重叠的,这可能会导致一些问题。但是我们稍后会讲到。

现在,我们用离散傅里叶变换来分析频率。给定一个离散时间信号,例如以特定采样率测量的样本,我们得到一个离散频域。作为一个中间阶段,我们得到了频域的实部和虚部。最后一句中有一些可怕的术语,我们快速过一遍。傅里叶变换将时间信号分解为正弦信号的系数。实部包含特定频率余弦的傅里叶系数(余弦和正弦相等)以及相同频率正弦的傅里叶系数。对于我们的用例来说,这种表示并不那么重要。我们要用的是所谓的频谱它是由原始傅里叶变换的实部和虚部计算出来的。频谱告诉我们每个频率对原始时域音频信号的贡献。从现在开始,我们假设已经计算了这个频谱,这个频谱在框架中是由一个名为FFT(取自Minim)的类来完成的,它可以减轻一些你当下的痛苦。如果你想了解如何从傅里叶变换的实部和虚部转换到频谱,请阅读本篇开始时我提供的链接。

由于变换的离散性,并非所有可能的频率都会出现在频谱中。频率被绑定,也就是说多个频率被合并成一个单独的值。离散傅里叶变换能给出的最大频率也就是奈奎斯特频率。它等于时域信号采样率的一半。假设我们有一个采样频率为44100Hz的音频信号,尼奎斯特频率为22050Hz。当我们把信号转换到频域,我们得到频率箱(frequency bins)高达22050Hz。有多少个这样的箱?样本数的一半加一。当我们对1024个样本进行变换时,我们得到513个频率箱,每个频率箱的带宽(频率范围)为奈奎斯特频率,除以除第一个和最后一个有一半带宽的桶外的桶数。假设我们有1024个样本以44100Hz采样。第一个存储箱将表示0到22050 / 513 / 2~ 21.5Hz的频率(请记住,第一个存储箱只有正常带宽的一半)。下一个方框表示21.5Hz到21.5Hz的频率加上22050 / 513 ~ 43Hz == 64.5Hz(再一次21.5Hz到64.5Hz,带宽为43Hz)。下一个范围是64.5Hz到107.5Hz,以此类推。

当我们对音频数据进行离散傅里叶变换时,我们对样本窗口进行离散傅里叶变换,例如1024个样本是一个常见的窗口大小。窗口的大小决定了得到的频谱的分辨率,即频谱中频率箱的数量将随着我们变换样本的数量线性增加。样本窗口越大,容器的粒度越细,它们的带宽就越小。为了计算频谱,我们使用了一种特殊的算法,称为快速傅里叶变换(FFT),它运行在O(n log n)中,与采用O(n*n)的朴素傅里叶变换相比,它非常快。几乎所有的FFT实现都要求示例窗口的大小为2的整数次幂。256 512 1024 2048可以,273不行。我们在框架中使用的FFT实现也有这个“限制”。

因此,无需赘言,我将向您展示上图中描述的样本的光谱

注意:左右的垂直线不是实际图像的一部分。是wordpress搞砸了

是的,就是这样。x轴表示频率箱,y轴表示频率箱的振幅。我们清楚地看到在左边的一个peek,其余的频谱是零。这个峰值对应于包含频率440Hz的频率库,我们为其中的音符A生成1024个样本。我们还看到它不是一个干净的峰值,左边和右边的箱子也接收到一些振幅。这叫做泄漏,是我们无法解决的问题。在我们的例子中,这不是一个大问题,但在其他应用程序场景中,它可能是一个大问题。下面是生成上述两幅图像的程序:

public class FourierTransformPlot 
{
   public static void main( String[] argv )
   {
      final float frequency = 440; // Note A		
      float increment = (float)(2*Math.PI) * frequency / 44100;		
      float angle = 0;		
      float samples[] = new float[1024];      

      for( int i = 0; i < samples.length; i++ )
      {
         samples[i] = ((float)Math.sin( angle ));
         angle += increment;			
      }
		
      FFT fft = new FFT( 1024, 44100 );
      fft.forward( samples );

      Plot plotSamples = new Plot( "Samples", 512, 512 );
      plotSamples.plot( samples, 2, Color.white );

      Plot plotSpectrum = new Plot( "Spectrum", 512, 512);
      plotSpectrum.plot(fft.getSpectrum(), 1, Color.white );
   }
}

头几行应该很熟悉,我们只是以44000Hz的采样率生成1024个样本。实际上这个程序中只有两行有趣的代码。第一个是实例化FFT对象的地方。构造函数需要两个参数,我们用来生成频谱的采样率 和样本窗口大小。下一行执行傅里叶变换和频谱计算。我们所要做的就是传入一个浮点数组,其中包含的样本大小与我们在FFT构造函数中指定的大小相同。这就是我们需要做的来得到我们宝贵的频率箱。剩下的是绘制样本和光谱。注意对fft.getSpectrum()的调用。这个方法将返回我们调用FFT.forward()所做的上一个傅里叶变换的频谱。

哎呀,那太恶心了。我建议大家多读一下离散傅里叶变换。它是所有音频工程师必备的数学工具。虽然大多数音频工程师会像我们一样使用工具箱,但熟悉周围的环境并没有坏处。

现在我们已经装备好了所有我们需要的东西来启动我们的起点/节奏检测器。请继续关注下一篇文章。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值