Warum registriert dieser Beat-Erkennungscode einige Beats nicht richtig?

38

Ich habe diese SoundAnalyzer-Klasse erstellt, um Beats in Songs zu erkennen:

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

Aus irgendeinem Grund werden nur Beats zwischen 637 und 641 Sekunden erkannt, und ich habe keine Ahnung, warum. Ich weiß, dass die Beats von mehreren Bändern eingefügt werden, da ich Duplikate finde, und es scheint, als würde jedem momentanen Energiewert zwischen diesen Werten ein Beat zugewiesen.

Es ist nach folgendem Vorbild aufgebaut: http://www.flipcode.com/misc/BeatDetectionAlgorithms.pdf

Warum werden die Beats nicht richtig registriert?

Quincy
quelle
2
Können Sie einen Plot der Entwicklung von instantEnergyList [index + 1] und historyBuffer über die Zeit für eine Band veröffentlichen? Die beiden Diagramme überlagerten sich. Das würde Hinweise darauf geben, wo das Problem liegen könnte. Auch Energie muss das Quadrat der Größe sein, vergessen Sie das nicht.
CeeJay
Ahh ja, das könnte das Problem aufdecken, lassen Sie mich sehen, ob ich irgendwie ein paar Grafiken machen kann
Quincy
2
Aber dieser Plot ist nur historyBuffer oder historyBuffer / numInBuffer * C? Es sieht so aus, als hättest du ein massives C drin. Betrachtet man den Code, sollte historyBuffer ähnliche Werte wie instantEnergy haben. Dieses Diagramm kann nur angezeigt werden, wenn C zu hoch oder numInBuffer zu niedrig ist (weit unter 1), was meiner Meinung nach nicht der Fall ist.
CeeJay
7
Die Frage, die nicht sterben würde ...
Ingenieur
3
Versuchen Sie, diese Frage auf dsp.stackexchange.com
Atav32 20.07.12

Antworten:

7

Ich habe es probiert, was dumm war, weil ich mit Fourier-Transformationen oder Musiktheorie nicht vertraut war. Nach einigen Studien habe ich keine Lösung, aber ich sehe einige beunruhigende Dinge:

  • Der Code für Sound und Soundbuffer fehlt und könnte leicht der Schuldige sein
  • Die Fourier-Transformationen
    • Ich konnte dieselbe Fourier-Transformationsbibliothek nicht finden, indem ich den Namespace und die Methodennamen googelte. Dies bedeutet, dass der Code möglicherweise benutzerdefiniert ist und die Ursache des Problems sein könnte
    • Es ist ungewöhnlich, dass FastFourier.Calculate eine Reihe von Kurzschlüssen akzeptiert
  • Die Methode GetEnergyList nimmt eine ref-Liste auf, aber diese Liste wird nicht wieder verwendet?
  • An mehreren Stellen sehen Sie die fest auf 1024 codierte SampleSize, aber es ist nicht klar, dass dies immer der Fall ist.
  • Es ist beunruhigend, dass der Kommentar für PlaceBeatMarkers besagt, dass N durch 1024 geteilt werden sollte. Vielleicht hat der aufrufende Code vergessen, dies zu tun?
  • Ich bin sehr misstrauisch, wie historyBuffer in PlaceMarkers manipuliert wird, insbesondere, da N übergeben und dann zum Manipulieren des historyBuffers verwendet wird.
  • Der Kommentar *// Fill the history buffer with n * instant energy*und der folgende Code sind nicht lebendig.

Nach einer Weile hatte ich das Gefühl, dass der Code nicht wirklich gut organisiert ist und es eine Zeitverschwendung wäre, zu versuchen, ihn zu reparieren. Wenn Sie denken, dass es sich lohnt, ist der nächste Schritt, den ich machen würde:

  1. Brechen Sie es auf den einfachsten Teil herunter
  2. Schreiben Sie den Code auf die ausführlichste Weise um und benennen Sie alle versteckten Variablen
  3. Schreiben Sie Komponententests, um sicherzustellen, dass ein kleiner Teil des Codes ordnungsgemäß funktioniert
  4. Fügen Sie einen weiteren kleinen Codeabschnitt hinzu und wiederholen Sie den Vorgang, bis alles richtig funktioniert

Tipps

  • Möglicherweise möchten Sie die Anzahl der Bänder festlegen, um die Schleifenlogik zu vereinfachen
  • Geben Sie Variablen wie N, C und B gute Namen, die klar und prägnant sind. Auf diese Weise können Sie logische Fehler leichter erkennen
  • Teilen Sie große Codeabschnitte in mehrere aufgerufene Methoden auf, von denen jede einen kleinen, präzisen Schritt des größeren Prozesses ausführt und Unit-Tests schreiben lässt, um sicherzustellen, dass dieser ordnungsgemäß funktioniert.
Ludington
quelle
Ich bin ein Fan von Coderätseln, solange das Rätsel gut ist. Daher die Gabe. Ich bin froh, dass Sie es angenommen haben, und Ihre Antworten zum Auffinden von Fehlern im Code sind die beste Antwort, die ein Code-Rätsel erhalten könnte.
Seth Battin