Чому цей код виявлення ритму не вдається зареєструвати деякі ритми належним чином?


38

Я зробив цей клас SoundAnalyzer для виявлення ударів у піснях:

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

Чомусь він виявляє лише удари від 637 сек до приблизно 641 сек, і я не знаю, чому. Я знаю, що удари вставляються з декількох діапазонів, оскільки я знаходжу дублікати, і здається, що це значення присвоюється кожному миттєвому значенню енергії між цими значеннями.

Його моделюють після цього: http://www.flipcode.com/misc/BeatDetectionAlgorithms.pdf

То чому б удари не реєструються належним чином?


2
Чи можете ви опублікувати сюжет еволюції instantEnergyList [індекс + 1] та historyBuffer за часом для однієї групи? Два графіки накладені один на одного. Це дало б підказки щодо проблеми. Також енергія повинна бути квадратом величини, не забувайте про це.
CeeJay

Ага так, це може розкрити проблему, дозвольте мені побачити, чи зможу я якось зробити графіки
Квінсі

2
Але цей сюжет є лише historyBuffer, або historyBuffer / numInBuffer * C? Схоже, у вас є масивний С там. Дивлячись на код, HistoryBuffer повинен мати подібні значення до instantEnergy, цей графік може бути, лише якщо C зависокий або numInBuffer занадто низький (шлях нижче 1), що, мабуть, це не так.
CeeJay

7
Питання, яке не помре ...
Інженер

3
Спробуйте задати це питання на dsp.stackexchange.com
Atav32

Відповіді:


7

Я взяв на нього удар, який був німий, бо я не знайомий з перетвореннями Фур'є або теорією музики. Отже, після деякого дослідження у мене немає рішення, але я бачу кілька тривожних речей:

  • Код для Sound and Soundbuffer відсутній і може бути легко винуватцем
  • Фур'є перетворює
    • Я не міг знайти ту саму бібліотеку перетворень Фур’є, гуггіруючи простір імен та імена методів, а це означає, що код може бути на замовлення і може стати джерелом проблеми
    • Те, що FastFourier.Calculate має масив коротких, є незвичним
  • Метод GetEnergyList бере перелік списку, але цей список знову не використовується?
  • У кількох місцях ви бачите жорсткий код із кодом SampleSize до 1024, але незрозуміло, що це завжди так.
  • Тривожно, що в коментарі до PlaceBeatMarkers зазначається, що N слід розділити на 1024, можливо, викликовий код забув це зробити?
  • Мені дуже подобається спосіб маніпулювання HistoryBuffer у PlaceMarkers, тим більше, що N передається та використовується для маніпулювання історією.
  • Коментар *// Fill the history buffer with n * instant energy*та код, що випливає, не змінюються.

Через деякий час я просто відчув, що код насправді не добре організований, і це буде марною витратою часу на спроби виправити. Якщо ви думаєте, що воно того варте, наступним кроком я б став:

  1. Розбийте його до найпростішої частини
  2. Перепишіть код найбільш докладно, назвіть усі приховані змінні
  3. Напишіть одиничні тести, щоб переконатися, що мала частина коду працює правильно
  4. Додайте ще один невеликий розділ коду і повторюйте, поки у вас все не буде правильно

Поради

  • Ви можете встановити кількість смуг, щоб виправити логіку циклу
  • Дайте чіткі та стислі назви змінних, таких як N, C та B, це допоможе вам простіше бачити логічні помилки
  • Розбийте великі розділи коду на кілька закликаних методів, кожен з яких робить невеликий стислий крок більшого процесу і може скласти одиничні тести, щоб переконатися, що він працює правильно.

Я прихильник розв’язування кодових загадок, доки загадка хороша. Звідси щедрота. Я радий, що ти сприйняв це, і твої відповіді на пошук помилок у коді - найкраща відповідь, яку може отримати загадка коду.
Сет Баттін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.