Pourquoi ce code de détection de battement ne parvient-il pas à enregistrer correctement certains battements?

38

J'ai créé cette classe SoundAnalyzer pour détecter les battements dans les chansons:

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

Pour une raison quelconque, il détecte uniquement les temps compris entre 637 et 641 secondes, et je ne sais pas pourquoi. Je sais que les battements sont insérés dans plusieurs bandes car je trouve des doublons, et il semble assigner un battement à chaque valeur d'énergie instantanée entre ces valeurs.

Son modèle est le suivant: http://www.flipcode.com/misc/BeatDetectionAlgorithms.pdf

Alors pourquoi les battements ne s'enregistrent-ils pas correctement?

Quincy
la source
2
Pouvez-vous poster un graphique de l'évolution d'instantEnergyList [index + 1] et de historyBuffer au fil du temps pour un groupe? Les deux graphiques se superposent. Cela donnerait des indices sur le problème. En outre, l'énergie doit être le carré de la magnitude, ne l'oubliez pas.
CeeJay
Ahh oui ça pourrait dévoiler le problème, laisse-moi voir si je peux en quelque sorte faire des graphiques
Quincy
2
Mais cette intrigue est juste historyBuffer, ou historyBuffer / numInBuffer * C? Il semble que vous ayez un énorme C dedans. En regardant le code, historyBuffer devrait avoir des valeurs similaires à instantEnergy, ce graphique ne peut l'être que si C est trop élevé ou numInBuffer est trop bas (bien au-dessous de 1), ce qui, je suppose, n'est pas le cas.
CeeJay
7
La question qui ne mourrait pas ...
Ingénieur
3
Essayez de poser cette question sur dsp.stackexchange.com
Atav32

Réponses:

7

J'ai essayé, ce qui était idiot parce que je ne connaissais pas les transformations de Fourier ou la théorie musicale. Donc, après quelques études, je n'ai pas de solution, mais je vois plusieurs choses troublantes:

  • Le code pour Sound and Soundbuffer est manquant et pourrait être facilement le coupable
  • Les transformations de Fourier
    • Je ne pouvais pas trouver la même bibliothèque de transformations de Fourier en recherchant dans l'espace de noms et les noms de méthodes, ce qui signifie que le code pouvait être personnalisé et pouvait être la source du problème.
    • Le fait que FastFourier.Calculate prenne un tableau de short est inhabituel
  • La méthode GetEnergyList prend une liste de références mais cette liste n'est pas utilisée à nouveau?
  • Vous voyez plusieurs fois le code SampleSize codé en dur à 1024, mais il n’est pas évident que ce soit toujours le cas.
  • Il est troublant que le commentaire de PlaceBeatMarkers indique que N devrait être divisé par 1024, peut-être que le code d'appel a oublié de le faire?
  • Je suis très méfiant vis-à-vis de la façon dont historyBuffer est manipulé dans PlaceMarkers, surtout depuis que N est transmis puis utilisé pour manipuler historyBuffer.
  • Le commentaire *// Fill the history buffer with n * instant energy*et le code qui suit ne jive pas.

Au bout d’un moment, j’ai l’impression que le code n’est pas très bien organisé et que ce serait une perte de temps d’essayer de le réparer. Si vous pensez que cela en vaut la peine, la prochaine étape serait de:

  1. Décomposer à la partie la plus simple
  2. Réécrivez le code de la manière la plus prolixe, nommez toutes les variables cachées
  3. Ecrivez des tests unitaires pour vous assurer que la petite partie du code fonctionne correctement
  4. Ajoutez une autre petite section de code et répétez l'opération jusqu'à ce que tout fonctionne correctement.

Conseils

  • Vous voudrez peut-être fixer le nombre de bandes pour simplifier la logique de boucle
  • Donnez aux variables comme N, C et B des noms clairs et concis, cela vous aidera à voir les erreurs logiques plus facilement
  • Divisez de grandes sections de code en plusieurs méthodes appelées qui effectuent chacune une petite étape concise du processus plus important et peuvent avoir des tests unitaires écrits pour s’assurer qu’il fonctionne correctement.
Ludington
la source
Je suis un partisan de la résolution des énigmes de code, tant que l'énigme est bonne. D'où la prime. Je suis heureux que vous l'ayez pris, et vos réponses pour trouver des erreurs dans le code sont la meilleure réponse qu'une énigme de code pourrait obtenir.
Seth Battin