2011-02-11 18 views
10

Eksperymentowałem z algorytmem FFT. Używam NAudio wraz z działającym kodem algorytmu FFT z Internetu. W oparciu o moje obserwacje wydajności, wynikowa wysokość jest niedokładna.Niedokładność FFT dla C#

Co się dzieje, to że mam MIDI (generowane z GuitarPro) przekonwertowane na plik WAV (44,1 kHz, 16 bitów, mono), który zawiera progresję od E2 (najniższa nuta gitary) do około E6. Jakie są wyniki dla niższych dźwięków (wokół E2-B3) jest generalnie bardzo źle. Ale osiągnięcie C4 jest nieco poprawne, ponieważ widzisz już prawidłową progresję (następną nutą jest C# 4, a następnie D4 itd.) Jednak problem polega na tym, że wykrytą tonację jest o połowę nić niższa od rzeczywistej wysokości dźwięku (np. C4 powinno być notatką, ale wyświetlany jest D # 4).

Co Twoim zdaniem może być nie tak? W razie potrzeby mogę opublikować kod. Dziękuję bardzo! Nadal zaczynam rozumieć pole DSP.

Edycja: Tutaj jest szorstka zarysowanie co robię

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

a następnie: (waveBuffer to po prostu klasa, która jest tam, aby przekonwertować byte [] do pływaka [], ponieważ funkcja przyjmuje tylko pływak [])

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

i wreszcie: (Smbpitchfft jest klasa, która ma FFT algo ... wierzę tam nic złego, że tak im nie umieszczenie go tutaj)

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

I interpretacji wynik:

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 (jeśli ktoś jest nadal zainteresowany):

Tak, jedną z odpowiedzi poniżej zaznaczono, że szczyt częstotliwości z FFT nie zawsze jest równoznaczne z boiska . Rozumiem, że. Ale chciałem spróbować czegoś dla siebie, jeśli tak było (przy założeniu, że są momenty, w których szczyt częstotliwości jest wynikiem). Zasadniczo otrzymałem 2 oprogramowanie (SpectraPLUS i FFTProperty wg DewResearch; kredyty do nich), które są w stanie wyświetlić domenę częstotliwości dla sygnałów audio.

Więc oto wyniki szczytów częstotliwości w dziedzinie czasu:

SpectraPLUS

SpectraPLUS

i właściwości FFT: enter image description here

Dokonano tego za pomocą wiadomości testowej A2 (około 110 Hz). Po obejrzeniu obrazów mają one wartości szczytowe częstotliwości w zakresie 102-112 Hz dla SpectraPLUS i 108 Hz dla właściwości FFT. Na moim kodzie dostaję 104Hz (używam 8192 bloków, a próbnik 44,1 khz ... 8192 jest podwajany, aby zrobić złożone wejście, więc w końcu otrzymuję około 5 Hz dla binsize, w porównaniu do 10Hz binsize z SpectraPLUS).

Teraz jestem trochę zdezorientowany, ponieważ w oprogramowaniu wydają się zwracać poprawny wynik, ale na moim kodzie zawsze dostaję 104 Hz (zauważ, że porównałem funkcję FFT, którą użyłem z innymi, takimi jak Math.Net i wydaje się być poprawne).

Czy uważasz, że problem może dotyczyć mojej interpretacji danych? Lub czy oprogramowanie robi jakieś inne rzeczy przed wyświetleniem częstotliwości-spektrum?Dzięki!

+0

Witam! Wartość, którą otrzymuję dla maxBinIndex jest w bin 20 (około 100-104 Hz), co skutkuje około G #, który jest w połowie zapisany w dół od przypuszczalnego A. Jest to zgodne z innymi plikami .wav, czasami będącym krokiem całego na dół. – user488792

+0

@eryksun Dzięki! Twój ostatni punkt jest interesujący. Spróbuję i zajrzę do tego. – user488792

+0

@eryksun Cześć! Dziękuję Ci bardzo! To wydaje się być problemem. Mój kod działa teraz i zwraca poprawną częstotliwość. Wygląda na to, że przegapiłem to rozwiązanie z odpowiedzi Paula R., ponieważ w tamtym czasie nie robiłem zbyt wiele na temat FFT. Jednak wiele się nauczyłem dzięki waszej pomocy. Jeszcze raz dzięki! – user488792

Odpowiedz

11

Wygląda na to, że możesz mieć problem z interpretacją wyjścia FFT. Kilka losowych punktów:

  • FFT ma skończoną rozdzielczość - każdy odbiornik ma rozdzielczość Fs/N, gdzie Fs jest częstotliwość próbkowania i N jest rozmiar FFT

  • notatek, które są niskie w skali muzycznej, różnica w częstotliwości pomiędzy kolejnymi nutami jest stosunkowo mała, więc będziesz potrzebował wystarczająco dużego N, aby odrzucić nutę, która jest półtonem od siebie (patrz uwaga 1 poniżej)

  • pierwszy pojemnik (indeks 0) zawiera energia koncentruje się na 0 Hz ale obejmuje energię z +/- Fs/2N

  • bin i zawiera energię skoncentrowany na i * Fs/N ale obejmuje energię z +/- Fs/2N obu stronach tej częstotliwości środkowej

  • dostaniesz spectral leakage z sąsiednich pojemników - jak zły jest to zależy co window function użyć - bez okna (== prostokątne okna) oraz spektralną wyciek będzie bardzo złych (bardzo szerokie piki) - dla estymacji częstotliwości chcesz wybrać funkcję okna, który daje ostre szczyty

  • Boisko jest nie to samo, co częstotliwość - wysokość jest percepcją, częstotliwość jest wielkością fizyczną - odbierana wysokość instrumentu muzycznego może nieznacznie różnić się od częstotliwości podstawowej, w zależności od rodzaju instrumentu (niektóre instrumenty nawet nie wytwarzają znaczącej energii w ich podstawowa częstotliwość, jeszcze wciąż postrzegają swoje boisko jakby podstawowym były obecne)

Mój najlepszy przypuszczenie z ograniczonych informacji dostępnych jest jednak, że być może jesteś „off przez jednego” gdzieś w konwersji indeksu bin Częstotliwość, lub może FFT jest zbyt mała, aby zapewnić wystarczającą rozdzielczość dla niskich dźwięków, i może być konieczne zwiększenie N.

Możesz również poprawić ve oszacowanie wysokości tonu za pomocą kilku technik, takich jak analiza cepstralna lub poprzez obserwację komponentu fazy wyjścia FFT i porównanie go z kolejnymi FFT (pozwala to na dokładniejsze oszacowanie częstotliwości w pojemniku dla danego rozmiaru FFT).


Uwagi

(1) Wystarczy umieścić kilka numerów na to, E2 wynosi 82,4 Hz, F2 jest 87,3 Hz, więc trzeba uchwałę nieco lepiej niż 5 Hz do rozróżniania pomiędzy najniższymi dwie nuty na gitarze (i dużo subtelniejszej, jeśli naprawdę chcesz zrobić, powiedzmy, dokładne strojenie). Przy próbce 44,1 kHz prawdopodobnie potrzebujesz FFT co najmniej N = 8192, aby uzyskać wystarczającą rozdzielczość (44100/8192 = 5,4 Hz), prawdopodobnie N = 16384 byłoby lepiej.

+1

+1 za wspaniałą odpowiedź. –

+0

Cześć Paul! Dziękuję bardzo za odpowiedź! Obecnie używam okna Hamminga dla funkcji okna i używam N = 4096. Ale powodem jest to, że korzystam z przeplatania, aby bufor wejściowy dla FFT był o wiele większy. Ogólnie przeplatają zera z bufora wejściowego. Zamierzam spróbować niektórych rzeczy, aby spróbować i sprawdzić, czy poprawia dokładność. Dzięki! – user488792

+2

@ user488792: OK - wygląda na to, że i tak dobrze się spisałeś - Hamming jest rozsądnym wyborem okna, ale pamiętaj, że wypychanie zer w danych, aby uzyskać bardziej * pozorną * rozdzielczość, nic nie kupuje - to po prostu interpoluje wynikowe wyjście FFT, które sprawia, że ​​wygląda gładko, ale nie ma dodatkowych * informacji * (nie ma czegoś takiego jak darmowy lunch!). –

1

Miałem similar question, a dla mnie odpowiedzią było użycie Goertzel zamiast FFT. Jeśli wiesz, jakich dźwięków szukasz (MIDI) Goertzel jest w stanie wykryć dźwięki w obrębie jednej fali zatokowej (jeden cykl). Czyni to, generując falę sinusoidalną dźwięku i "umieszczając go na szczycie surowych danych", aby sprawdzić, czy istnieje. Próbki FFT pobierają duże ilości danych, aby zapewnić przybliżone widmo częstotliwości.

+0

Cześć! Dzięki za sugestię! Jednak pracuję z plikami WAV, więc myślę, że FFT byłby lepszy w tym przypadku.Dodatkowo, próbuję go uruchomić i nauczyć się go lepiej, ponieważ w przyszłości będę używać go do wykrywania akordów (wraz z innymi algorytmami oczywiście) .Dziękuję! – user488792

1

Skok muzyczny różni się od szczytowej częstotliwości.Pitch jest zjawiskiem psycho-percepcyjnym, które może zależeć bardziej od podtekstów i tym podobnych. Częstotliwość tego, co człowiek nazwałby tonem, mogłaby być niewystarczająca lub całkiem mała w rzeczywistych widmach sygnału.

A szczyt częstotliwości w widmie może być różny od dowolnego centrum bin FFT. Częstotliwości środkowego kosza FFT zmieniają się w zakresie częstotliwości i odstępów w zależności tylko od długości FFT i częstotliwości próbkowania, a nie od widm w danych.

Masz co najmniej 2 problemy, z którymi możesz się zmagać. Jest mnóstwo prac naukowych na temat szacowania częstotliwości oraz osobny temat oceny wysokości dźwięku. Zacznij tam.

3

Pomyślałem, że to może ci pomóc. Zrobiłem kilka wątków z 6 otwartych strun gitary. Kod jest w Pythonie pomocą pylab, które zalecamy dla eksperymentów:

# 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() 

ciąg 1, podstawowy = 329,6 Hz:

String 1, f0 = 329.6 Hz

ciąg 2, podstawowym = 246,9 Hz:

enter image description here

Łańcuch 3, podstawowy = 196,0 Hz:

enter image description here

Ciąg 4 podstawowym = 146,8 Hz:

enter image description here

Ciąg 5 podstawowym = 110,0 Hz:

enter image description here

ciąg 6 podstawowym = 82,4 Hz:

enter image description here

Podstawowa częstotliwość nie zawsze jest dominującą harmoniczną. Określa odstępy między harmonicznymi sygnału okresowego.

+0

Cześć! Dziękuję bardzo wiele za to i doceniam twój wysiłek.To naprawdę przyda się do studiowania i do dalszej analizy.Dzięki! – user488792

+0

Bardzo fajnie - może będę musiał zajrzeć do Pythona i PyLab –

+0

On llo! Zrobiłem kilka aktualizacji o tym, jak radzę sobie. Czy możesz rzucić okiem na to? Wielkie dzięki! – user488792