2011-02-11 65 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

基頻不總是主導諧波。它決定了週期信號諧波之間的間隔。