我已经为iPhone实现了Demetri的Pitch Detector项目,并遇到了两个问题。1)任何类型的背景噪音都会使频率读数变差;2)较低频率的声音音调不正确。我试着调音吉他,虽然高弦行得通,但调音师无法正确辨别低e。
音高检测代码位于RIOInterface中。嗯,是这样的…
// get the data
AudioUnitRender(...);
// convert int16 to float
Convert(...);
// divide the signal into even-odd configuration
vDSP_ctoz((COMPLEX*)outputBuffer, 2, &A, 1, nOver2);
// apply the fft
vDSP_fft_zrip(fftSetup, &A, stride, log2n, FFT_FORWARD);
// convert split real form to split vector
vDSP_ztoc(&A, 1, (COMPLEX *)outputBuffer, 2, nOver2);
Demetri接着确定"主导"频率如下:
float dominantFrequency = 0;
int bin = -1;
for (int i=0; i<n; i+=2) {
float curFreq = MagnitudeSquared(outputBuffer[i], outputBuffer[i+1]);
if (curFreq > dominantFrequency) {
dominantFrequency = curFreq;
bin = (i+1)/2;
}
}
memset(outputBuffer, 0, n*sizeof(SInt16));
// Update the UI with our newly acquired frequency value.
[THIS->listener frequencyChangedWithValue:bin*(THIS->sampleRate/bufferCapacity)];
首先,我认为我需要应用低通滤波器…但我不是FFT专家,也不确定在哪里或如何针对vDSP函数返回的数据做到这一点。我也不确定如何提高代码在较低频率的准确性。似乎还有其他算法可以确定主导频率,但同样,在使用苹果加速框架返回的数据时,要寻找正确的方向。
更新:加速框架实际上有一些窗口函数。我设置了一个像这样的基本窗口
windowSize = maxFrames;
transferBuffer = (float*)malloc(sizeof(float)*windowSize);
window = (float*)malloc(sizeof(float)*windowSize);
memset(window, 0, sizeof(float)*windowSize);
vDSP_hann_window(window, windowSize, vDSP_HANN_NORM);
然后通过插入
应用vDSP_vmul(outputBuffer, 1, window, 1, transferBuffer, 1, windowSize);
vDSP_ctoz函数前的。然后我改变其余的代码使用'transferBuffer'而不是outputBuffer…但到目前为止,我还没有注意到最终的音高猜测有任何戏剧性的变化。
音高与峰值幅度频率bin不一样(这是加速框架中的FFT可能直接给你的)。因此任何峰值频率检测器对于基音估计都是不可靠的。当一个音符缺少或非常弱的基本音(在一些人声、钢琴和吉他的声音中很常见)和/或频谱中有很多强大的泛音时,低通滤波器将不起作用。
看看你的音乐声音的宽带频谱或光谱仪,你就会发现问题所在。
通常需要其他方法来更可靠地估计音高。其中一些包括自相关方法(AMDF, ASDF),倒谱/倒谱分析,谐波积谱,相位声码器和/或复合算法,如RAPT(稳健的音高跟踪算法)和YAAPT。FFT仅作为上述某些方法的子部分是有用的。
在计算FFT之前,您至少需要对您的时域数据应用窗口函数。如果没有这一步,功率谱将包含伪影(参见:频谱泄漏),这将干扰您提取音高信息的尝试。
一个简单的Hann(又名Hanning)窗口就足够了。
你的样本频率和块大小是多少?低E约为80 Hz,因此您需要确保捕获块足够长,以便在此频率下捕获许多周期。这是因为傅里叶变换将频谱分成几个箱,每个箱宽几赫兹。例如,如果您以44.1 kHz采样并具有1024点时域采样,则每个bin的宽度将为44100/1024 = 43.07 Hz。因此,低E会在第二个箱子里。由于一系列原因(与频谱泄漏和有限时间块的性质有关),实际上,您应该以极端怀疑的态度考虑FFT结果中的前3或4箱数据。
如果你将采样率降低到8 kHz,相同的块大小会给你7.8125 Hz宽的箱子。现在low E会在第10或者第11个bin,这样会好很多。您也可以使用更长的块大小。
正如Paul R指出的那样,你必须使用一个窗口来减少光谱泄漏。
iPhone的频响函数在100 - 200hz以下下降(例如http://blog.faberacoustical.com/2009/ios/iphone/iphone-microphone-frequency-response-comparison/)
如果你试图检测低音吉他弦的基本模式,麦克风可能会起到过滤器的作用,抑制你感兴趣的频率。如果你对使用fft数据感兴趣,你可以有几个选择——你可以在频域内的数据窗口周围的音符,你试图检测,所以你可以看到的是第一模态,即使它的幅度低于高模态(即。有一个开关来调整第一个字符串,并把它放在这个模式)。
或者你可以对声音数据进行低通滤波——你可以在时域或更容易地做到这一点,因为你已经有了频域数据,在频域中。一个很简单的时域低通滤波器就是做一个时间移动平均滤波器。一个非常简单的频域低通滤波器是将你的fft幅值乘以一个在低频范围内为1的矢量,在高频范围内线性(甚至阶跃)下降。