Mengapa kode deteksi ketukan ini gagal mendaftarkan beberapa ketukan dengan benar?

38

Saya membuat kelas SoundAnalyzer ini untuk mendeteksi ketukan dalam lagu:

class SoundAnalyzer
{
    public SoundBuffer soundData;
    public Sound sound;
    public List<double> beatMarkers = new List<double>();

    public SoundAnalyzer(string path)
    {
        soundData = new SoundBuffer(path);
        sound = new Sound(soundData);
    }

    // C = threshold, N = size of history buffer / 1024  B = bands
    public void PlaceBeatMarkers(float C, int N, int B)
    {
        List<double>[] instantEnergyList = new List<double>[B];
        GetEnergyList(B, ref instantEnergyList);
        for (int i = 0; i < B; i++)
        {
            PlaceMarkers(instantEnergyList[i], N, C);
        }
        beatMarkers.Sort();
    }

    private short[] getRange(int begin, int end, short[] array)
    {
        short[] result = new short[end - begin];
        for (int i = 0; i < end - begin; i++)
        {
            result[i] = array[begin + i];
        }
        return result;
    }

    // get a array of with a list of energy for each band
    private void GetEnergyList(int B, ref List<double>[] instantEnergyList)
    {
        for (int i = 0; i < B; i++)
        {
            instantEnergyList[i] = new List<double>();
        }
        short[] samples = soundData.Samples;

        float timePerSample = 1 / (float)soundData.SampleRate;
        int sampleIndex = 0;
        int nextSamples = 1024;
        int samplesPerBand = nextSamples / B;

        // for the whole song
        while (sampleIndex + nextSamples < samples.Length)
        {
            complex[] FFT = FastFourier.Calculate(getRange(sampleIndex, nextSamples + sampleIndex, samples));
            // foreach band
            for (int i = 0; i < B; i++)
            {
                double energy = 0;
                for (int j = 0; j < samplesPerBand; j++)
                    energy += FFT[i * samplesPerBand + j].GetMagnitude();

                energy /= samplesPerBand;
                instantEnergyList[i].Add(energy);

            }

            if (sampleIndex + nextSamples >= samples.Length)
                nextSamples = samples.Length - sampleIndex - 1;
            sampleIndex += nextSamples;
            samplesPerBand = nextSamples / B;
        }
    }

    // place the actual markers
    private void PlaceMarkers(List<double> instantEnergyList, int N, float C)
    {
        double timePerSample = 1 / (double)soundData.SampleRate;
        int index = N;
        int numInBuffer = index;
        double historyBuffer = 0;

        //Fill the history buffer with n * instant energy
        for (int i = 0; i < index; i++)
        {
            historyBuffer += instantEnergyList[i];
        }

        // If instantEnergy / samples in buffer < instantEnergy for the next sample then add beatmarker.
        while (index + 1 < instantEnergyList.Count)
        {
            if(instantEnergyList[index + 1] > (historyBuffer / numInBuffer) * C)
                beatMarkers.Add((index + 1) * 1024 * timePerSample); 
            historyBuffer -= instantEnergyList[index - numInBuffer];
            historyBuffer += instantEnergyList[index + 1];
            index++;
        }
    }
}

Untuk beberapa alasan hanya mendeteksi detak dari 637 detik menjadi sekitar 641 detik, dan saya tidak tahu mengapa. Saya tahu ketukan sedang dimasukkan dari beberapa band karena saya menemukan duplikat, dan sepertinya itu memberikan ketukan untuk setiap nilai energi instan di antara nilai-nilai itu.

Ini dimodelkan setelah ini: http://www.flipcode.com/misc/BeatDetectionAlgorithms.pdf

Jadi mengapa ketukan tidak bisa didaftarkan dengan benar?

Quincy
sumber
2
Bisakah Anda memposting plot evolusi instantEnergyList [index + 1] dan historyBuffer dari waktu ke waktu untuk satu band? Dua grafik saling bertindihan. Itu akan memberi petunjuk tentang apa masalahnya. Juga, energi harus kuadrat besarnya, jangan lupa itu.
CeeJay
Ahh ya itu mungkin mengungkap masalah, biarkan saya melihat apakah saya bisa membuat beberapa grafik
Quincy
2
Tapi plot ini hanya historyBuffer, atau historyBuffer / numInBuffer * C? Sepertinya Anda memiliki C besar di sana. Melihat kode, historyBuffer harus memiliki nilai yang mirip dengan instantEnergy, grafik itu hanya dapat jika C terlalu tinggi atau numInBuffer terlalu rendah (jauh di bawah 1), yang saya kira bukan itu masalahnya.
CeeJay
7
Pertanyaan yang tidak akan mati ...
Insinyur
3
Coba ajukan pertanyaan ini di dsp.stackexchange.com
Atav32

Jawaban:

7

Saya menikamnya, yang bodoh karena saya tidak terbiasa dengan transformasi Fourier atau teori musik. Jadi, setelah beberapa penelitian saya tidak punya solusi, tetapi saya melihat beberapa hal yang meresahkan:

  • Kode untuk Sound dan Soundbuffer hilang dan bisa dengan mudah menjadi biang keladinya
  • Transformasi Fourier
    • Saya tidak dapat menemukan pustaka transformasi Fourier yang sama dengan googling namespace dan nama-nama metode, yang berarti bahwa kode mungkin custom dan bisa menjadi sumber masalah
    • Fakta bahwa FastFourier.Calculate mengambil array pendek tidak biasa
  • Metode GetEnergyList mengambil Daftar ref tetapi daftar ini tidak digunakan lagi?
  • Di beberapa tempat Anda melihat sampel kerasSizeSize ke 1024, tetapi tidak jelas yang selalu terjadi.
  • Sangat mengganggu bahwa komentar untuk PlaceBeatMarkers mencatat bahwa N harus dibagi dengan 1024, mungkin kode panggilan lupa melakukannya?
  • Saya sangat curiga dengan cara historyBuffer dimanipulasi di PlaceMarkers, terutama karena N dilewatkan dan kemudian digunakan untuk memanipulasi historyBuffer.
  • Komentar *// Fill the history buffer with n * instant energy*dan kode yang mengikuti tidak bercampur aduk.

Setelah beberapa saat saya baru merasa kode tidak benar-benar terorganisir dengan baik dan akan membuang-buang waktu untuk memperbaikinya. Jika Anda pikir itu sepadan, langkah selanjutnya yang akan saya ambil adalah:

  1. Hancurkan menjadi bagian yang paling sederhana
  2. Tulis ulang kode dengan cara yang paling jelas, beri nama semua variabel yang disembunyikan
  3. Tulis pengujian unit untuk memastikan bahwa sebagian kecil kode berfungsi dengan benar
  4. Tambahkan satu bagian kecil kode lagi dan ulangi sampai semuanya benar

Kiat

  • Anda mungkin ingin membuat jumlah band tetap untuk menyederhanakan logika loop
  • Berikan variabel seperti N, C, dan B nama baik yang jelas dan ringkas, ini akan membantu Anda keberatan melihat kesalahan logis lebih mudah
  • Pecah bagian besar kode menjadi beberapa metode yang disebut yang masing-masing melakukan langkah singkat kecil dari proses yang lebih besar dan dapat memiliki unit test tertulis untuk memastikan itu berfungsi dengan benar.
Ludington
sumber
Saya penggemar memecahkan teka-teki kode, selama teka-teki itu bagus. Karena itu hadiahnya. Saya senang Anda mengambilnya, dan jawaban Anda untuk menemukan kesalahan dalam kode adalah jawaban terbaik yang bisa didapat dari teka-teki kode.
Seth Battin