Обнаружение биений и БПФ

13

Я работаю над платформерной игрой, которая включает в себя музыку с обнаружением ударов. В настоящее время я обнаруживаю биения, проверяя, превышает ли текущая амплитуда историческую выборку. Это плохо работает с жанрами музыки, такими как рок, которые имеют довольно устойчивую амплитуду.

Поэтому я посмотрел дальше и нашел алгоритмы, разделяющие звук на несколько полос, используя FFT ... затем я нашел алгоритм Cooley-Tukey FFt

Единственная проблема, с которой я столкнулся, это то, что я совершенно новичок в аудио, и я понятия не имею, как его использовать, чтобы разделить сигнал на несколько сигналов.

Итак, мой вопрос:

Как вы используете БПФ для разделения сигнала на несколько полос?

Также для интересующихся ребят, это мой алгоритм в 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
источник
Я думаю, что хорошей отправной точкой являются записи FFT и DSP в Википедии . Запись об обнаружении ударов редкая, но ссылки на статью на gamedev.net
Тобиас Кинцлер,

Ответы:

14

Что ж, если ваш входной сигнал является реальным (например, каждый образец является действительным числом), спектр будет симметричным и сложным. Используя симметрию, обычно алгоритмы FFT упаковывают результат, возвращая вам только положительную половину спектра. Действительная часть каждой полосы находится в четных выборках, а мнимая часть - в нечетных. Или иногда реальные части упакованы вместе в первой половине ответа и мнимые части во второй половине.

В формулах, если X [k] = FFT (x [n]), вы задаете ему вектор i [n] = x [n] и получаете вывод o [m], тогда

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

(хотя иногда вы получаете X [k] = o [k] + j · o [k + K / 2], где K - длина вашего окна, 1024 в вашем примере). Кстати, j - мнимая единица, sqrt (-1).

Величина полосы вычисляется как корень произведения этой полосы с ее комплексным сопряжением:

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

И энергия определяется как квадрат величины.

Если мы называем a = o [2k] и b = o [2k + 1], мы получаем

X[k] = a + j·b

следовательно

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

Развернув все это, если вы получили o [m] в качестве вывода из алгоритма FFT, энергия в полосе k равна:

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

(Примечание: я использовал символ · для обозначения умножения вместо обычного *, чтобы избежать путаницы с оператором сопряжения)

Частота полосы k, предполагающая частоту дискретизации 44,1 кГц и окно из 1024 выборок, составляет

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

Так, например, ваша первая полоса k = 0 представляет 0 Гц, k = 1 составляет 43 Гц, а последняя k = 511 составляет 22 кГц (частота Найквиста).

Я надеюсь, что это отвечает на ваш вопрос о том, как вы получаете энергию сигнала на полосу, используя БПФ.

Приложение : отвечая на ваш вопрос в комментарии и предполагая, что вы используете код из ссылки, которую вы разместили в вопросе (алгоритм Кули-Тьюки в C): допустим, у вас есть входные данные в виде вектора коротких целых:

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

Мой C немного заржавел (в настоящее время я в основном пишу на C ++), но я надеюсь, что не допустил большой ошибки с этим кодом. Конечно, если бы вы были заинтересованы в энергии других полос, нет смысла преобразовывать целое окно для каждой из них, это было бы пустой тратой процессорного времени. В этом случае выполните преобразование один раз и получите все необходимые значения от xout.

CeeJay
источник
О, я только что посмотрел на код, который вы связали, он уже дает вам результаты в «сложной» форме и даже предоставляет вам функцию для вычисления величины комплексного числа. Тогда вам нужно будет только вычислить квадрат этой величины для каждого элемента выходного вектора, не нужно беспокоиться о сортировке результатов.
CeeJay
Например, если у меня есть все 1024 выборки из окна 0-1024, и я получил их как реальные значения, так что нет сложной части. и я хочу рассчитать энергию там на полосе частот 43 Гц. Как бы я интегрировал это тогда? (Мне нужна только реальная часть, положительная часть). Если бы вы могли сделать это в каком-то псевдокоде, я буду в глубине души до вас, и тогда я действительно смогу немного понять концепцию :)
Quincy
Код, который я написал, использует библиотеку C, которую вы связали, которая уже содержит «сложную» структуру. Это делает ненужной
распаковку, которую
0

Я сам этого не делал и не читал об этом, но мой первый снимок выглядит примерно так:

Прежде всего, вам нужно применить оконную функцию, чтобы получить зависящий от времени спектр с БПФ. Удар обычно лежит на более низких частотах, поэтому примените другое БПФ с большим временным окном к интенсивностям некоторых из этих частот (для простоты начните только с 1 при, например, 100 Гц и посмотрите, достаточно ли это надежно). Найдите пик в этом спектре, и эта частота является предположением для ритма.

Тобиас Кинцлер
источник
У меня проблемы не с определением ритма, а с пониманием работы FFT. Я действительно новичок в обработке сигналов, и такие вещи, как: «применить оконную функцию, чтобы получить зависящий от времени спектр с БПФ», не имеют для меня никакого смысла. В любом случае, спасибо :)
Куинси