2011-02-11 63 views
10

我一直在试验FFT算法。我使用NAudio和来自互联网的FFT算法的工作代码。根据我对表演的观察,所得到的音高是不准确的。C#的FFT不准确性

发生什么是我有一个MIDI(从GuitarPro生成)转换为WAV文件(44.1khz,16位,单声道),包含从E2(最低吉他音符)开始到约E6的音高级数。低音(E2-B3附近)的结果通常非常错误。但是到达C4它有点正确,因为你已经可以看到正确的进程(下一个音符是C#4,然后是D4等)。然而,问题在于检测到的音高比实际音高低一半例如C4应该是注释,但显示D#4)。

您认为什么可能是错误的?如有必要,我可以发布代码。非常感谢!我仍然开始掌握DSP的领域。

编辑:这是一个什么Im做

byte[] buffer = new byte[8192]; 
int bytesRead; 
do 
{ 
    bytesRead = stream16.Read(buffer, 0, buffer.Length); 
} while (bytesRead != 0); 

然后粗糙从头开始:(waveBuffer是一个简单的类,它是有转换的字节[]为浮动[],因为函数只接受浮动[])

public int Read(byte[] buffer, int offset, int bytesRead) 
{ 
    int frames = bytesRead/sizeof(float); 
    float pitch = DetectPitch(waveBuffer.FloatBuffer, frames); 
} 

最后一点:(Smbpitchfft是具有FFT算法中的类......我相信那里有什么不妥的地方所以我不会在这里张贴)

private float DetectPitch(float[] buffer, int inFrames) 
{ 
    Func<int, int, float> window = HammingWindow; 
    if (prevBuffer == null) 
    { 
    prevBuffer = new float[inFrames]; //only contains zeroes 
    } 

    // double frames since we are combining present and previous buffers 
    int frames = inFrames * 2; 
    if (fftBuffer == null) 
    { 
    fftBuffer = new float[frames * 2]; // times 2 because it is complex input 
    } 

    for (int n = 0; n < frames; n++) 
    { 
    if (n < inFrames) 
    { 
     fftBuffer[n * 2] = prevBuffer[n] * window(n, frames); 
     fftBuffer[n * 2 + 1] = 0; // need to clear out as fft modifies buffer 
    } 
    else 
    { 
     fftBuffer[n * 2] = buffer[n - inFrames] * window(n, frames); 
     fftBuffer[n * 2 + 1] = 0; // need to clear out as fft modifies buffer 
    } 
    } 
    SmbPitchShift.smbFft(fftBuffer, frames, -1); 
    } 

并解释结果:

float binSize = sampleRate/frames; 
int minBin = (int)(82.407/binSize); //lowest E string on the guitar 
int maxBin = (int)(1244.508/binSize); //highest E string on the guitar 

float maxIntensity = 0f; 
int maxBinIndex = 0; 

for (int bin = minBin; bin <= maxBin; bin++) 
{ 
    float real = fftBuffer[bin * 2]; 
    float imaginary = fftBuffer[bin * 2 + 1]; 
    float intensity = real * real + imaginary * imaginary; 
    if (intensity > maxIntensity) 
    { 
     maxIntensity = intensity; 
     maxBinIndex = bin; 
    } 
} 

return binSize * maxBinIndex; 

UPDATE(如果有人仍有意):

所以,下面陈述的答案之一是从FFT频率峰值并不总是等同于间距。我明白那个。但是我想为自己尝试一些事情(假设有时候频率峰值是最终的音调)。所以基本上,我得到了2个软件(DewResearch的SpectraPLUS和FFTProperties;对他们的评分),能够显示音频信号的频域。

因此,这里有在时域频率峰值的结果: enter image description here

这是使用测试注意做到:

SpectraPLUS

SpectraPLUS

和FFT属性A2(大约110Hz)。在查看这些图像时,他们的频谱峰值在SpectraPLUS 102-112 Hz范围内,FFT Properties 108 Hz范围内。在我的代码中,我得到了104Hz(我使用8192块,采样率为44.1khz ... 8192然后加倍使其成为复杂的输入,所以最终我得到了5Hz左右的binins,与SpectraPLUS的10Hz binsize相比)。

所以现在我有点困惑,因为在软件上他们似乎返回正确的结果,但在我的代码,我总是得到104Hz(注意,我已经比较了我使用的FFT函数,如Math.Net这似乎是正确的)。

您是否认为这个问题可能与我对数据的解释有关?或者在显示频谱之前软件是否做了其他的事情?谢谢!

+0

嗨!我为maxBinIndex得到的值是在bin 20(大约100-104 Hz),这导致在G#周围,这是从假设的A开始的一半的音符。这与其他.wav文件一致,有时是整个步骤下。 – user488792 2011-02-23 03:54:50

+0

@eryksun谢谢!你最后的观点很有趣。我会试着去研究它。 – user488792 2011-02-23 07:14:47

+0

@eryksun嗨!非常感谢你!这似乎是问题所在。我的代码现在可以工作并返回正确的频率。似乎我错过了Paul R答案的解决方案,因为那时我还没有做过很多有关FFT的内容。但是,我已经学到了很多,谢谢你的帮助。再次感谢! – user488792 2011-02-23 12:30:52

回答

11

这听起来像你可能有FFT输出的解释问题。几个随机点:

  • 的FFT具有有限的分辨率 - 每个输出存放箱具有Fs/N,其中Fs是采样速率和N分辨率是FFT

  • 的用于纸币的尺寸在连续音符之间的频率差异相对较小,因此您需要足够大的N来区分两个音符之间的音符(见下面注1)

  • 第一个音符(索引0)包含能在0Hz为中心,但包括能量+/- Fs/2N

  • i包含集中精力在i * Fs/N,但包括能量+/- Fs/2N该中心频率两侧

  • 你会得到spectral leakage从相邻支路 - 多么糟糕,这是取决于你使用的什么window function - 没有窗口(==矩形窗口)和频谱泄漏将是非常糟糕的(非常宽的峰值) - 对于频率估计,你想选择一个窗口函数,让你有尖锐的峰值

  • pitch is不与频率相同 - 音高是一种感知,频率是一种物理量 - 根据乐器类型的不同,乐器感知的音高可能与基本频率略有不同(某些乐器甚至不会在其乐器上产生大量能量基频,但我们仍然认为他们的音调,仿佛根本存在)

从可用但有限的信息,我最好的猜测是,也许你是“关闭一个”的地方在你的bin指数转化为频率,或者您的FFT太小而无法为低音提供足够的分辨率,并且您可能需要增加N.您可以改进通过倒谱分析等多种技术,或者通过查看FFT输出的相位分量并将其与连续FFT进行比较(这允许在给定的FFT大小下在一个分箱内进行更准确的频率估计),来实现您的音调估计。


(1)只要把一些数字上这个,E2是82.4赫兹,F2是87.3赫兹,所以你需要一个分辨率稍好于5赫兹最低的两个音之间区分在一把吉他上(如果你真的想要做的话,比如精确调音,这要比这更精细)。在一个44.1 kHz的采样点上,你可能需要一个至少为N = 8192的FFT来给出足够的分辨率(44100/8192 = 5.4 Hz),可能N = 16384会更好。

1

我有一个similar question和我的答案是使用Goertzel而不是FFT。如果你知道你正在寻找什么音色(MIDI)Goertzel能够在一个正弦波(一个周期)内检测到音调。它通过生成声音的正弦波并“将其放在原始数据的顶部”来查看它是否存在。 FFT对大量数据进行采样以提供近似频谱。

1

音乐音高不同于频率峰值。音高是一种心理感知现象,可能更多地取决于泛音等。在实际的信号频谱中,人类称之为音调的频率可能会丢失或很小。

频谱中的频率峰值可能与任何FFT bin中心不同。 FFT仓中心频率的频率和间距将根据FFT长度和采样率而变化,而不是数据中的频谱。

所以你至少有两个问题需要抗衡。有大量关于频率估计的学术论文以及音高估计的单独主题。从那里开始。

3

我认为这可能对你有帮助。我制作了吉他的6个开放琴弦的一些情节。该代码是在使用Python pylab,我建议用于试验:

# analyze distorted guitar notes from 
# http://www.freesound.org/packsViewSingle.php?id=643 
# 
# 329.6 E - open 1st string 
# 246.9 B - open 2nd string 
# 196.0 G - open 3rd string 
# 146.8 D - open 4th string 
# 110.0 A - open 5th string 
# 82.4 E - open 6th string 

from pylab import * 
import wave 

fs = 44100.0 
N = 8192 * 10 
t = r_[:N]/fs 
f = r_[:N/2+1] * fs/N 
gtr_fun = [329.6, 246.9, 196.0, 146.8, 110.0, 82.4] 

gtr_wav = [wave.open('dist_gtr_{0}.wav'.format(n),'r') for n in r_[1:7]] 
gtr = [fromstring(g.readframes(N), dtype='int16') for g in gtr_wav] 
gtr_t = [g/float64(max(abs(g))) for g in gtr] 
gtr_f = [2 * abs(rfft(g))/N for g in gtr_t] 

def make_plots(): 
    for n in r_[:len(gtr_t)]: 
     fig = figure() 
     fig.subplots_adjust(wspace=0.5, hspace=0.5) 
     subplot2grid((2,2), (0,0)) 
     plot(t, gtr_t[n]); axis('tight') 
     title('String ' + str(n+1) + ' Waveform') 
     subplot2grid((2,2), (0,1)) 
     plot(f, gtr_f[n]); axis('tight') 
     title('String ' + str(n+1) + ' DFT') 
     subplot2grid((2,2), (1,0), colspan=2) 
     M = int(gtr_fun[n] * 16.5/fs * N) 
     plot(f[:M], gtr_f[n][:M]); axis('tight') 
     title('String ' + str(n+1) + ' DFT (16 Harmonics)') 

if __name__ == '__main__': 
    make_plots() 
    show() 

字符串1,基本= 329.6赫兹:

String 1, f0 = 329.6 Hz

字符串2,基本= 246.9赫兹:

enter image description here

字符串3,基本= 196.0赫兹:

enter image description here

字符串4,基本= 146.8赫兹:

enter image description here

字串5,基本= 110.0赫兹:

enter image description here

字符串6,基本= 82.4赫兹:

enter image description here

基频不总是主导谐波。它决定了周期信号谐波之间的间隔。