Почему этот код обнаружения ударов не может правильно зарегистрировать некоторые удары?

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

Так почему же ритмы не регистрируются должным образом?

Quincy
источник
2
Можете ли вы опубликовать сюжет об эволюции InstantEnergyList [index + 1] и historyBuffer с течением времени для одной группы? Два графика накладываются друг на друга. Это дало бы подсказку о том, в чем может быть проблема. Кроме того, энергия должна быть квадратом величины, не забывайте это.
CeeJay
Ах, да, это может раскрыть проблему, дай мне посмотреть, смогу ли я как-нибудь сделать некоторые графики
Куинси
2
Но этот сюжет - это просто historyBuffer или historyBuffer / numInBuffer * C? Похоже, у вас там огромный С. Глядя на код, historyBuffer должен иметь значения, аналогичные InstantEnergy, этот график может быть только в том случае, если C слишком велик или numInBuffer слишком низок (намного ниже 1), что, я думаю, не так.
CeeJay
7
Вопрос, который не умрет ...
Инженер
3
Попробуйте задать этот вопрос на dsp.stackexchange.com
Atav32

Ответы:

7

Я сделал удар по нему, который был глуп, потому что я не знаком с преобразованиями Фурье или теорией музыки. Итак, после некоторого изучения у меня нет решения, но я вижу несколько тревожных вещей:

  • Код для звука и Soundbuffer отсутствует и может быть легко виновником
  • Преобразования Фурье
    • Я не смог найти ту же библиотеку преобразований Фурье, прибегнув к поиску пространства имен и методов, что означает, что код может быть пользовательским и может быть источником проблемы.
    • Тот факт, что FastFourier.Calculate принимает массив коротких, необычен
  • Метод GetEnergyList принимает список ссылок, но этот список больше не используется?
  • В нескольких местах вы видите SampleSize, жестко запрограммированный на 1024, но не ясно, что это всегда так.
  • Вызывает беспокойство, что в комментарии для PlaceBeatMarkers отмечается, что N следует разделить на 1024, может быть, вызывающий код забыл это сделать?
  • Я очень подозрительно отношусь к тому, как historyBuffer манипулирует в PlaceMarkers, тем более что N передается, а затем используется для манипулирования historyBuffer.
  • Комментарий *// Fill the history buffer with n * instant energy*и код, который следует, не срабатывают.

Через некоторое время у меня появилось ощущение, что код на самом деле не очень хорошо организован, и это будет пустой тратой времени на попытки исправить. Если вы думаете, что это того стоит, то следующий шаг, который я предприму:

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

подсказки

  • Возможно, вы захотите сделать количество полос фиксированным, чтобы упростить логику петли.
  • Дайте переменным, таким как N, C и B хорошие имена, которые будут ясными и краткими, это поможет вам легче увидеть логические ошибки
  • Разбейте большие фрагменты кода на несколько вызванных методов, каждый из которых выполняет небольшой лаконичный шаг более крупного процесса и может иметь написанные модульные тесты, чтобы убедиться, что он работает правильно.
Лудингтон
источник
Я поклонник разгадывания загадок кода, если загадка хороша. Отсюда и награда. Я рад, что вы взяли его, и ваши ответы на поиск ошибок в коде - лучший ответ, который может получить загадка кода.
Сет Бэттин