Detecção de batida e FFT

13

Estou trabalhando em um jogo de plataforma que inclui música com detecção de batida. Atualmente, estou detectando batidas verificando quando a amplitude atual excede uma amostra histórica. Isso não funciona bem com gêneros musicais, como o rock, que tem uma amplitude bastante constante.

Então eu olhei mais longe e encontrei algoritmos dividindo o som em várias bandas usando FFT ... então encontrei o algoritmo Cooley-Tukey FFt

O único problema que estou tendo é que sou bastante novo em áudio e não tenho idéia de como usá-lo para dividir o sinal em vários sinais.

Então, minha pergunta é:

Como você usa um FFT para dividir um sinal em várias bandas?

Também para os interessados, este é o meu algoritmo em 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++;
        }
    }
Quincy
fonte
Eu acho que um bom ponto de partida são as entradas FFT e DSP da wikipedia . A entrada de detecção de batida é esparso mas links para um artigo na gamedev.net
Tobias KIENZLER

Respostas:

14

Bem, se o seu sinal de entrada é real (como em cada amostra é um número real), o espectro será simétrico e complexo. Explorando a simetria, geralmente os algoritmos FFT compõem o resultado, devolvendo apenas a metade positiva do espectro. A parte real de cada banda está nas amostras pares e a parte imaginária nas amostras ímpares. Ou, às vezes, as partes reais são agrupadas na primeira metade da resposta e as partes imaginárias na segunda metade.

Nas fórmulas, se X [k] = FFT (x [n]), você atribui a ele um vetor i [n] = x [n] e obtém uma saída o [m];

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

(embora às vezes você obtenha X [k] = o [k] + j · o [k + K / 2], onde K é o comprimento da sua janela, 1024 no seu exemplo). A propósito, j é a unidade imaginária, sqrt (-1).

A magnitude de uma banda é calculada como a raiz do produto dessa banda com seu complexo conjugado:

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

E a energia é definida como o quadrado da magnitude.

Se chamarmos a = o [2k] eb = o [2k + 1], obtemos

X[k] = a + j·b

Portanto

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

Desenrolando a coisa toda, se você obteve o [m] como saída do algoritmo FFT, a energia na banda k é:

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

(Nota: usei o símbolo · para indicar multiplicação em vez do habitual * para evitar confusão com o operador de conjugação)

A frequência da banda k, assumindo uma frequência de amostragem de 44,1 Khz e uma janela de 1024 amostras, é

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

Assim, por exemplo, sua primeira banda k = 0 representa 0 Hz, k = 1 é 43 Hz e a última k = 511 é 22KHz (a frequência de Nyquist).

Espero que isso responda à sua pergunta sobre como você obtém a energia do sinal por banda usando a FFT.

Adendo : Respondendo à sua pergunta no comentário e supondo que você esteja usando o código do link que você postou na pergunta (O algoritmo Cooley-Tukey em C): Digamos que você tenha seus dados de entrada como um vetor de ints curtos:

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

Meu C está um pouco enferrujado (hoje em dia estou codificando em C ++), mas espero não ter cometido nenhum grande erro com esse código. Obviamente, se você estava interessado na energia de outras bandas, não faz sentido transformar a janela inteira para cada uma delas, isso seria uma perda de tempo da CPU. Nesse caso, faça a transformação uma vez e obtenha todos os valores necessários do xout.

CeeJay
fonte
Ah, acabei de dar uma olhada no código que você vinculou, ele já fornece os resultados na forma "complexa" e até fornece uma função para calcular a magnitude de um número complexo. Então você só precisa calcular o quadrado dessa magnitude para cada elemento do vetor de saída, não precisa se preocupar em classificar os resultados.
Ceejay
Como exemplo, se eu tenho todas as 1024 amostras da janela 0-1024 e as obtive como valores reais, não há parte complexa. e eu quero calcular a energia lá na faixa de frequência 43Hz. Como eu o integraria então? (Eu só preciso a parte de trás real, a parte postive) Se você poderia fazê-lo em algum pseudocódigo eu vou estar em profundidade de você para sempre e então eu pode realmente entender o conceito um pouco :)
Quincy
O código que escrevi está usando a biblioteca C que você vinculou, que já contém uma estrutura "complexa". Isso torna o desembrulhar eu descrevi na minha pergunta desnecessária (e o código reflete isso)
Ceejay
0

Eu não fiz isso ou li muito sobre isso sozinho, mas meu primeiro tiro é algo como isto:

Primeiro, você precisará aplicar uma função de janela para obter um espectro dependente do tempo com a FFT. A batida geralmente está nas frequências mais baixas; portanto, aplique outra FFT com uma janela de tempo maior nas intensidades de algumas dessas frequências (para simplificar, comece com apenas 1 a, por exemplo, 100 Hz e verifique se isso é confiável o suficiente). Encontre o pico neste espectro e essa frequência é um palpite para a batida.

Tobias Kienzler
fonte
Na verdade, não é com a detecção de batidas que estou tendo problemas, mas com o entendimento do funcionamento da FFT. Eu sou realmente novo no processamento de sinais e coisas como: "aplique uma função de janela para obter um espectro dependente do tempo com a FFT" não fazem nenhum sentido para mim. De qualquer forma, obrigado :) #
284 Quincy