Rilevamento delle battute e FFT


13

Sto lavorando a un gioco platform che include musica con rilevazione dei battiti. Attualmente sto rilevando i battiti controllando quando l'ampiezza corrente supera un campione storico. Questo non funziona bene con i generi musicali, come il rock, che hanno un'ampiezza piuttosto costante.

Quindi ho guardato oltre e ho trovato algoritmi che dividevano il suono in più bande usando FFT ... poi ho trovato l' algoritmo Cooley-Tukey FFt

L'unico problema che sto riscontrando è che sono abbastanza nuovo nell'audio e non ho idea di come usarlo per dividere il segnale in più segnali.

Quindi la mia domanda è:

Come si usa un FFT per dividere un segnale in più bande?

Anche per i ragazzi interessati, questo è il mio algoritmo in c #:

// C = threshold, N = size of history buffer / 1024
    public void PlaceBeatMarkers(float C, int N)
    {
        List<float> instantEnergyList = new List<float>();
        short[] samples = soundData.Samples;

        float timePerSample = 1 / (float)soundData.SampleRate;
        int sampleIndex = 0;
        int nextSamples = 1024;

        // Calculate instant energy for every 1024 samples.
        while (sampleIndex + nextSamples < samples.Length)
        {

            float instantEnergy = 0;

            for (int i = 0; i < nextSamples; i++)
            {
                instantEnergy += Math.Abs((float)samples[sampleIndex + i]);
            }

            instantEnergy /= nextSamples;
            instantEnergyList.Add(instantEnergy);

            if(sampleIndex + nextSamples >= samples.Length)
                nextSamples = samples.Length - sampleIndex - 1;

            sampleIndex += nextSamples;
        }


        int index = N;
        int numInBuffer = index;
        float 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++;
        }
    }

Immagino che un buon punto di partenza siano le voci FFT e DSP di Wikipedia . La voce di rilevamento delle battute è scarsa ma si collega a un articolo su gamedev.net
Tobias Kienzler

Risposte:


14

Bene, se il tuo segnale di ingresso è reale (come in, ogni campione è un numero reale), lo spettro sarà simmetrico e complesso. Sfruttando la simmetria, in genere gli algoritmi FFT racchiudono il risultato restituendoti solo la metà positiva dello spettro. La parte reale di ogni banda è nei campioni pari e la parte immaginaria nei campioni dispari. O a volte le parti reali sono raggruppate nella prima metà della risposta e le parti immaginarie nella seconda metà.

Nelle formule, se X [k] = FFT (x [n]), gli dai un vettore i [n] = x [n] e ottieni un output o [m], quindi

X[k] = o[2k] + j·o[2k+1]

(anche se a volte ottieni X [k] = o [k] + j · o [k + K / 2], dove K è la lunghezza della tua finestra, 1024 nel tuo esempio). A proposito, j è l'unità immaginaria, sqrt (-1).

La grandezza di una banda viene calcolata come radice del prodotto di questa banda con il suo complesso coniugato:

|X[k]| = sqrt( X[k] · X[k]* )

E l'energia è definita come il quadrato della grandezza.

Se chiamiamo a = o [2k] e b = o [2k + 1], otteniamo

X[k] = a + j·b

perciò

E[k] = |X[k]|^2 = (a+j·b)·(a-j·b) = a·a + b·b

Srotolando il tutto, se si ottiene o [m] come output dall'algoritmo FFT, l'energia nella banda k è:

E[k] = o[2k] · o[2k] + o[2k+1] · o[2k+1]

(Nota: ho usato il simbolo · per indicare la moltiplicazione invece del solito * per evitare confusione con l'operatore di coniugazione)

La frequenza della banda k, ipotizzando una frequenza di campionamento di 44,1 Khz e una finestra di 1024 campioni, è

freq(k) = k / 1024 * 44100 [Hz]

Quindi, ad esempio, la tua prima banda k = 0 rappresenta 0 Hz, k = 1 è 43 Hz e l'ultima k = 511 è 22 KHz (la frequenza di Nyquist).

Spero che questo risponda alla tua domanda su come ottenere l'energia del segnale per banda usando l'FFT.

Addendum : rispondere alla tua domanda nel commento e supporre che tu stia utilizzando il codice dal link che hai pubblicato nella domanda (l'algoritmo di Cooley-Tukey in C): supponiamo che tu abbia i tuoi dati di input come vettore di brevi brevi:

// len is 1024 in this example.  It MUST be a power of 2
// centerFreq is given in Hz, for example 43.0
double EnergyForBand( short *input, int len, double centerFreq)
{
  int i;
  int band;
  complex *xin;
  complex *xout;
  double magnitude;
  double samplingFreq = 44100.0; 

  // 1. Get the input as a vector of complex samples
  xin = (complex *)malloc(sizeof(struct complex_t) * len);

  for (i=0;i<len;i++) {
    xin[i].re = (double)input[i];
    xin[i].im = 0;
  }

  // 2. Transform the signal
  xout = FFT_simple(xin, len);

  // 3. Find the band ( Note: floor(x+0.5) = round(x) )
  band = (int) floor(centerFreq * len / samplingFreq + 0.5); 

  // 4. Get the magnitude
  magnitude = complex_magnitude( xout[band] );

  // 5. Don't leak memory
  free( xin );
  free( xout );

  // 6. Return energy
  return magnitude * magnitude;
}

La mia C è un po 'arrugginita (al giorno d'oggi sto scrivendo codice in C ++), ma spero di non aver commesso un grosso errore con questo codice. Naturalmente, se tu fossi interessato all'energia di altre bande, non ha senso trasformare l'intera finestra per ognuna di esse, sarebbe una perdita di tempo della CPU. In tal caso, esegui la trasformazione una volta sola e ottieni tutti i valori necessari da xout.


Oh, ho appena dato un'occhiata al codice che hai collegato, ti dà già i risultati in forma "complessa" e ti fornisce persino una funzione per calcolare la grandezza di un numero complesso. Quindi dovresti solo calcolare il quadrato di quella grandezza per ogni elemento del vettore di output, non devi preoccuparti di ordinare i risultati.
CeeJay,

Ad esempio, se ho tutti i 1024 campioni dalla finestra 0-1024 e li ottengo come valori reali, quindi nessuna parte complessa. e voglio calcolare l'energia lì dentro sulla banda di frequenza 43Hz. Come lo integrerei allora? (Ho solo bisogno della parte reale indietro, la parte postiva) Se potessi farlo in qualche pseudocodice ti sarò in profondità per sempre e poi potrei effettivamente afferrare un po 'il concetto :)
Quincy

Il codice che ho scritto sta usando la libreria C che hai collegato, che contiene già una struttura "complessa". Questo rende superfluo lo
scartamento


0

Non ho fatto questo o letto molto su di me da solo, ma il mio primo scatto è qualcosa del genere:

Prima di tutto, dovrai applicare una funzione finestra per ottenere uno spettro dipendente dal tempo con la FFT. Il battito di solito risiede nelle frequenze più basse, quindi applica un altro FFT con una finestra temporale più grande sulle intensità di alcune di queste frequenze (per semplicità inizia con solo 1 ad es. 100 Hz e vedi se è abbastanza affidabile). Trova il picco in questo spettro e quella frequenza è un'ipotesi per il ritmo.


In realtà non è il rilevamento del battito con cui ho problemi, ma capire come funziona FFT. Sono davvero nuovo a segnalare il processamento e cose come: "applica una funzione finestra per ottenere uno spettro dipendente dal tempo con la FFT" non ha alcun senso per me. Grazie comunque :)
Quincy
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.