8

I have implemented Demetri's Pitch Detector project for the iPhone and hitting up against two problems. 1) any sort of background noise sends the frequency reading bananas and 2) lower frequency sounds aren't being pitched correctly. I tried to tune my guitar and while the higher strings worked - the tuner could not correctly discern the low E.

The Pitch Detection code is located in RIOInterface.mm and goes something like this ...

// 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 then goes on to determine the 'dominant' frequency as follows:

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)];

To start with, I believe I need to apply a LOW PASS FILTER ... but I'm not an FFT expert and not sure exactly where or how to do that against the data returned from the vDSP functions. I'm also not sure how to improve the accuracy of the code in the lower frequencies. There seem to be other algorithms to determine the dominant frequency - but again, looking for a kick in the right direction when using the data returned by Apple's Accelerate framework.

UPDATE:

The accelerate framework actually has some windowing functions. I setup a basic window like this

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); 

which I then apply by inserting

vDSP_vmul(outputBuffer, 1, window, 1, transferBuffer, 1, windowSize); 

before the vDSP_ctoz function. I then change the rest of the code to use 'transferBuffer' instead of outputBuffer ... but so far, haven't noticed any dramatic changes in the final pitch guess.

4

4 回答 4

8

音高与峰值幅度频率箱不同(这是 Accelerate 框架中的 FFT 可能直接给您的)。因此,任何峰值频率检测器对于基音估计都不可靠。当音符缺少或非常弱的基本音(在某些人声、钢琴和吉他声音中很常见)和/或在其频谱中有大量强大的泛音时,低通滤波器将无济于事。

查看您的音乐声音的宽带频谱或频谱仪,您会发现问题所在。

通常需要其他方法来更可靠地估计音高。其中一些包括自相关方法(AMDF、ASDF)、倒谱/倒谱分析、谐波积谱、相位声码器和/或复合算法,例如 RAPT(音高跟踪的鲁棒算法)和 YAAPT。FFT 仅用作上述某些方法的子部分。

于 2011-08-24T20:35:38.350 回答
3

在计算 FFT 之前,您至少需要对时域数据应用窗口函数。如果没有这一步,功率谱将包含伪影(请参阅:频谱泄漏),这会干扰您提取音高信息的尝试。

一个简单的Hann(又名Hanning)窗口就足够了。

于 2011-08-25T08:30:51.930 回答
1

iPhone 的频率响应函数在 100 - 200 Hz 以下下降(参见http://blog.faberacoustical.com/2009/ios/iphone/iphone-microphone-frequency-response-comparison/示例)。

如果您尝试检测低音吉他弦的基本模式,麦克风可能会充当滤波器并抑制您感兴趣的频率。如果您有兴趣使用 fft 数据,有几个选项可以获得 -您可以在您尝试检测的音符周围的频域中将数据窗口化,以便您可以看到的只是第一个模式,即使它的幅度低于更高的模式(即有一个切换来调整第一个字符串并放置它在这种模式下)。

或者您可以对声音数据进行低通滤波 - 您可以在时域中执行此操作,甚至更容易,因为您已经在频域中拥有频域数据。一个非常简单的时域低通滤波器就是做一个时间移动平均滤波器。一个非常简单的频域低通滤波器是将您的 fft 幅度乘以在低频范围内为 1 的向量,并在较高频率内线性(甚至是步进)斜坡下降。

于 2012-08-21T04:48:18.407 回答
1

您的采样频率和块大小是多少?低 E 约为 80 Hz,因此您需要确保您的捕获块足够长,以便在此频率下捕获许多周期。这是因为傅立叶变换将频谱划分为多个区间,每个区间为几赫兹宽。例如,如果您以 44.1 kHz 采样并有 1024 点时域采样,则每个 bin 的宽度将为 44100/1024 = 43.07 Hz。因此,低 E 将在第二个箱中。由于一系列原因(与频谱泄漏和有限时间块的性质有关),实际上您应该非常怀疑地考虑 FFT 结果中的前 3 或 4 个数据箱。

如果将采样率降低到 8 kHz,相同的块大小会为您提供 7.8125 Hz 宽的 bin。现在低 E 将在第 10 或第 11 个 bin 中,这要好得多。您也可以使用更长的块大小。

正如Paul R 指出的那样,您必须使用窗口来减少频谱泄漏。

于 2011-08-25T09:14:02.947 回答