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++;
}
}
Respostas:
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];
(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:
E a energia é definida como o quadrado da magnitude.
Se chamarmos a = o [2k] eb = o [2k + 1], obtemos
Portanto
Desenrolando a coisa toda, se você obteve o [m] como saída do algoritmo FFT, a energia na banda k é:
(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, é
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:
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.
fonte
Aqui está uma ótima leitura sobre detecção de batida em jogos.
http://www.badlogicgames.com/wordpress/?p=99
É parte de uma série de blogs de 8 partes sobre o assunto.
http://www.badlogicgames.com/wordpress/?category_name=onset-detection-tutorial
fonte
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.
fonte