Perché questo codice di rilevamento beat non riesce a registrare correttamente alcuni beat?


38

Ho realizzato questa classe SoundAnalyzer per rilevare i ritmi nelle canzoni:

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

Per qualche motivo rileva solo battiti da 637 secondi a circa 641 secondi e non ho idea del perché. So che i battiti vengono inseriti da più bande poiché trovo duplicati e sembra che stia assegnando un battito a ciascun valore di energia istantanea tra questi valori.

È modellato su questo: http://www.flipcode.com/misc/BeatDetectionAlgorithms.pdf

Quindi perché i battiti non si registrano correttamente?


2
Puoi pubblicare una trama dell'evoluzione di instantEnergyList [indice + 1] e historyBuffer nel tempo per una band? I due grafici si sovrappongono l'uno sull'altro. Ciò darebbe indizi su quale potrebbe essere il problema. Inoltre, l'energia deve essere il quadrato della grandezza, non dimenticarlo.
CeeJay,

Ah sì, questo potrebbe svelare il problema, fammi vedere se posso in qualche modo creare dei grafici
Quincy,

2
Ma questa trama è solo historyBuffer o historyBuffer / numInBuffer * C? Sembra che tu abbia una C enorme lì dentro. Guardando il codice, historyBuffer dovrebbe avere valori simili a instantEnergy, quel grafico può essere solo se C è troppo alto o numInBuffer è troppo basso (molto al di sotto di 1), il che immagino non sia il caso.
CeeJay,

7
La domanda che non sarebbe morta ...
Ingegnere il

3
Prova a porre questa domanda su dsp.stackexchange.com
Atav32,

Risposte:


7

L'ho preso a pugni, che era stupido perché non avevo familiarità con le trasformazioni di Fourier o la teoria musicale. Quindi, dopo qualche studio non ho una soluzione, ma vedo diverse cose preoccupanti:

  • Manca il codice per Sound and Soundbuffer e potrebbe essere facilmente il colpevole
  • Le trasformazioni di Fourier
    • Non sono riuscito a trovare la stessa libreria di trasformazioni di Fourier cercando su Google lo spazio dei nomi e i nomi dei metodi, il che significa che il codice potrebbe essere personalizzato e potrebbe essere la fonte del problema
    • Il fatto che FastFourier.Calculate abbia una serie di short è insolito
  • Il metodo GetEnergyList accetta un elenco di riferimento ma questo elenco non viene più utilizzato?
  • In diversi punti viene visualizzato il codice di esempio SampleSize su 1024, ma non è chiaro che sia sempre così.
  • È preoccupante che il commento per PlaceBeatMarkers noti che N dovrebbe essere diviso per 1024, forse il codice chiamante ha dimenticato di farlo?
  • Sono molto sospettoso del modo in cui historyBuffer viene manipolato in PlaceMarkers, soprattutto perché N viene passato e quindi utilizzato per manipolare historyBuffer.
  • Il commento *// Fill the history buffer with n * instant energy*e il codice che segue non sono validi.

Dopo un po 'ho avuto la sensazione che il codice non fosse davvero ben organizzato e sarebbe una perdita di tempo cercare di risolvere. Se pensi che ne valga la pena, il prossimo passo che farei è:

  1. Analizzalo nella parte più semplice
  2. Riscrivi il codice nel modo più dettagliato, dai un nome a tutte le variabili nascoste
  3. Scrivi unit test per assicurarti che una piccola parte del codice funzioni correttamente
  4. Aggiungi un'altra piccola sezione di codice e ripeti fino a quando tutto funziona correttamente

Suggerimenti

  • Potresti voler rendere fisso il numero di bande per semplificare la logica del loop
  • Dai a variabili come N, C e B nomi chiari, chiari e concisi, questo ti aiuterà a vedere più facilmente gli errori logici
  • Suddividere grandi sezioni di codice in diversi metodi chiamati, ciascuno dei quali esegue una piccola fase concisa del processo più ampio e può disporre di unit test scritti per assicurarsi che funzioni correttamente.

Sono un fan di risolvere enigmi di codice, purché l'indovinello sia buono. Da qui la generosità. Sono contento che tu l'abbia preso e le tue risposte per trovare errori nel codice sono la migliore risposta che un indovinello del codice potrebbe ottenere.
Seth Battin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.