public List <int> Cepstrum()
        {
            int        chunkSize   = this.chunkSize;
            List <int> frequencies = new List <int>();

            float[][] parts;

            // ustawiamy chunkSize jako potêge 2 dla póŸniejszego FFT
            chunkSize = SoundUtil.MakePowerOf2(chunkSize);
            // dzielimy pobrane próbki dŸwiêku na fragmenty
            parts = SoundUtil.ChunkArrayPowerOf2(dataNormalized, chunkSize);

            foreach (float[] buffer in parts)
            {
                // konwertujemy dane wejœciowe spróbkowanego sygna³u na postaæ zespolon¹
                Complex[] complexSound = SoundUtil.SignalToComplex(buffer);

                // przepuszczamy dane przez okno Hamminga
                Complex[] complexWindows = SoundUtil.HammingWindow(complexSound);

                // pierwsze FFT
                Complex[] fftComplex = SoundUtil.FFT(complexWindows);

                // zapamiêtujemy wynik pierwszego FFT w celu wyœwietlenia wykresu
                fftDataForSpectrumChart = new Complex[fftComplex.Length];
                fftComplex.CopyTo(fftDataForSpectrumChart, 0);

                // przepuszczamy modu³ widma przez logarytm
                for (int i = 0; i < fftComplex.Length; ++i)
                {
                    fftComplex[i] = new Complex(10.0f * (float)Math.Log10(fftComplex[i].Modulus() + 1), 0);
                }

                // drugie FFT, w celu uzyskania cepstrum
                fftComplex = SoundUtil.FFT(fftComplex);

                // zapamiêtujemy wynik cepstrum w celu wyœwietlenia wykresu
                cepstrum = new Complex[fftComplex.Length];
                fftComplex.CopyTo(cepstrum, 0);

                // bierzemy 1/4 wyników bo po ka¿dym FFT po³owa wyników jest taka sama a FFT wykonujemy 2 razy
                fftComplex = fftComplex.Take(fftComplex.Length / 4).ToArray();

                // modu³y wartoœci z FFT
                double[] dataArray = new double[chunkSize];

                // zapisanie modu³ów wartoœci z FFT do pierwszego wymiaru bufora
                for (int i = 0; i < fftComplex.Length; ++i)
                {
                    dataArray[i] = fftComplex[i].Modulus();
                }

                List <int> localMaxIndexes = new List <int>();
                //promieñ, zakres w którym badamy czy wartoœæ jest maksimum lokalnym
                int range = 10;

                for (int i = range; i < dataArray.Length - range; i++)
                {
                    int biggerValue = 0;
                    // badamy otoczenie punktu
                    for (int j = i - range; j < i + range; ++j)
                    {
                        // je¿eli bie¿¹ca wartoœæ z otoczenia jest mniejsza od punktu,
                        // który sprawdzamy wtedy zliczamy je
                        if (dataArray[j] <= dataArray[i] && i != j)
                        {
                            biggerValue++;
                        }
                    }

                    // jeœli w otoczeniu nie ma mniejszych wartoœci to
                    // dodajemy numer wartoœci do tablicy lokalnych maksimów
                    if (biggerValue == (range * 2) - 1)
                    {
                        localMaxIndexes.Add(i);
                    }
                }

                // odrzucanie wysokich wartoœci ale nie stromych
                // musza opadaæ w obu kierunkach
                for (int index = 0; index < localMaxIndexes.Count;)
                {
                    int i = localMaxIndexes[index], leftIndexOffset = 0, rightIndexOffset = 0;

                    // badamy lewe zbocze w poszukiwaniu wartoœci najni¿szej wartoœci,
                    // w której zmienia siê omnotonicznoœæ
                    while ((i - leftIndexOffset - 1) >= 0)
                    {
                        if ((dataArray[i - leftIndexOffset - 1] <= dataArray[i - leftIndexOffset]))
                        {
                            ++leftIndexOffset;
                        }
                        else
                        {
                            break;
                        }
                    }

                    // badamy prawe zbocze w poszukiwaniu najwy¿szej wartoœci
                    // w której zmienia siê omnotonicznoœæ
                    while ((i + rightIndexOffset + 1) < dataArray.Length)
                    {
                        if ((dataArray[i + rightIndexOffset + 1] <= dataArray[i + rightIndexOffset]))
                        {
                            ++rightIndexOffset;
                        }
                        else
                        {
                            break;
                        }
                    }

                    // progowanie co do najwiêkszego peaku
                    // wybieramy punktu o wiêkszej wartoœci spoœród znalezionych po lewej i prawej stronoe
                    double maxmin = Math.Max(dataArray[i - leftIndexOffset], dataArray[i + rightIndexOffset]);

                    // porównujemy wartoœæ maksymaln¹ minimów z wczeœniej znalezionych punktów i porówujemy je z lokalnym maksem
                    // je¿eli najwiêksza wartoœæ minimalna jest wiêksza od bie¿¹co rozpatrywanego poziomu maksimum
                    if (maxmin > dataArray[i] * 0.2)
                    {
                        // wtedy usuwamy to maksimum
                        localMaxIndexes.RemoveAt(index);
                    }
                    else
                    {
                        index++;
                    }
                }

                // szukanie pozycji maksymalnej wartoœci z listy lokalnych maksimów
                int max_ind = SoundUtil.MaxFromLocalMaxList(localMaxIndexes, dataArray);

                // przeszukanie listy maksimów w celu usuniêcia tych znajduj¹cych siê ponad progiem
                for (int index = 0; index < localMaxIndexes.Count;)
                {
                    // je¿eli bie¿¹ca wartoœæ lokalnego maksimum z listy jest ponad progiem znalezionej wartoœci maksymalnej na liœcie
                    if (dataArray[localMaxIndexes[index]] > dataArray[max_ind] * 0.4)
                    {
                        index++;
                    }
                    else
                    {
                        // w przeciwnym wypadku usuwamy maksimum z listy
                        localMaxIndexes.RemoveAt(index);
                    }
                }

                // zmienne maj¹ce zapamiêtaæ dwa indeksy z listy maksimów
                int max_b, max_a;

                // szukanie pozycji maksymalnej wartoœci z listy lokalnych maksimów
                max_b = SoundUtil.MaxFromLocalMaxList(localMaxIndexes, dataArray);

                int a = 0, b = 0;
                while (localMaxIndexes.Count > 1)
                {
                    // usuwamy wczeœniej zapamiêtane maksimum z listy
                    localMaxIndexes.Remove(max_b);

                    // szukanie pozycji maksymalnej wartoœci z listy lokalnych maksimów
                    max_a = SoundUtil.MaxFromLocalMaxList(localMaxIndexes, dataArray);

                    // zapamiêtanie wczeœniej pozycji wczeœniej usuniêtego maksimum
                    // i tego znaleziono po nim
                    a = max_a; b = max_b;

                    // je¿eli indeksy maksimów nie s¹ w kolejnoœci rosn¹æ zamieniamy je
                    if (a > b)
                    {
                        int tmp = a;
                        a = b;
                        b = tmp;
                    }

                    for (int i = 0; i < localMaxIndexes.Count;)
                    {
                        int num = localMaxIndexes[i];

                        // je¿eli indeks bie¿¹cego maksimum z listy jest poza przedzia³em
                        // wyznaczonym przez poprzednie zapisane maksima
                        if (num < a || num > b)
                        {
                            // usuwamy indeks maksimum z listy
                            localMaxIndexes.RemoveAt(i);
                        }
                        else
                        {
                            i++;
                        }
                    }

                    // najstarszey zapamiêtany indeks maksimum zastêpujemy kolejnym maksimmum po nim
                    max_b = max_a;
                }

                // wyliczamy ró¿nicê miêdzy znalezionymi indeksami maksimów
                max_ind = Math.Abs(b - a);

                // je¿eli lokalnym maksimum z listy jest pierwszy indeks i jedyny indeks na liœcie
                if (max_ind == 0 && localMaxIndexes.Count == 1)
                {
                    max_ind = localMaxIndexes[0];
                }

                // czêstotliwoœæ podstawowa wyliczana jest jako iloraz czêstotliwoœci próbkowania
                // i pozycji maksimum (która tak naprawde wyznacza okres)
                int frequency = (int)(sampleRate / (double)max_ind);
                frequencies.Add(frequency);
            }

            return(frequencies);
        }