ビート検出とFFT


13

私はビート検出付きの音楽を含むプラットフォーマーゲームに取り組んでいます。現在、現在の振幅が履歴サンプルを超えているかどうかを確認することで、ビートを検出しています。これは、かなり安定した振幅を持つロックなどの音楽のジャンルではうまく機能しません。

そこで、さらに調べて、FFTを使用してサウンドを複数の帯域に分割するアルゴリズムを見つけました...そして、Cooley-Tukey FFtアルゴリズムを見つけました

私が抱えている唯一の問題は、私がオーディオにまったく慣れていないことであり、それを使用して信号を複数の信号に分割する方法がわかりません。

だから私の質問は:

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

出発点としては、ウィキペディアの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

すべてを展開すると、FFTアルゴリズムからの出力としてo [m]が得られた場合、帯域kのエネルギーは次のようになります。

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

(注:共役演算子との混乱を避けるために、通常の*の代わりに記号・を使用して乗算を示します)

44.1Khzのサンプリング周波数と1024サンプルのウィンドウを想定したバンドkの周波数は次のとおりです。

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

したがって、たとえば、最初の帯域k = 0は0 Hzを表し、k = 1は43 Hz、最後の帯域k = 511は22KHz(ナイキスト周波数)です。

これが、FFTを使用して帯域ごとに信号のエネルギーをどのように取得するかについてのあなたの質問に答えることを願っています。

補遺:コメントで質問に答え、質問に投稿したリンクのコードを使用していると仮定します(CのCooley-Tukeyアルゴリズム):入力データが短いintのベクトルとしてあるとします。

// 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 ++でコーディングしています)が、このコードで大きな間違いを犯さなかったことを願っています。もちろん、他のバンドのエネルギーに興味がある場合、それぞれのウィンドウ全体を変換することは意味がありません。それはCPU時間の無駄です。その場合、変換を1回実行し、xoutから必要なすべての値を取得します。


ああ、私はあなたがリンクしたコードを見たところです、それはすでにあなたに「複雑な」形式で結果を与え、さらに複素数の大きさを計算する関数を提供します。次に、出力ベクトルの各要素に対してその大きさの2乗を計算するだけでよく、結果の並べ替えを心配する必要はありません。
CeeJay

例として、ウィンドウ0-1024から1024個すべてのサンプルがあり、それらを実際の値として取得した場合、複雑な部分はありません。43Hzの周波数帯域でエネルギーを計算したいです。それではどのように統合しますか?(本当の部分、ポジティブな部分だけが必要です)何らかの擬似コードでそれを行うことができれば、私は永遠にあなたの奥深くにいるでしょう、そして実際に少し概念を把握するかもしれません:)
クインシー

私が書いたコードは、リンクしたCライブラリを使用しています。このライブラリにはすでに「複雑な」構造が含まれています。これにより、質問で説明したアンラッピングが不要になります(コードにはそれが反映されます)
-CeeJay


0

私はこれをやったことがないし、それについて多くを読んだことはありませんが、私の最初のショットは次のようなものです:

まず、FFTで時間依存スペクトルを取得するために、ウィンドウ関数を適用する必要があります。ビートは通常、より低い周波数にあるため、これらの周波数のいくつかの強度に大きな時間ウィンドウを持つ別の FFTを適用します(簡単にするために、たとえば100 Hzで1から始めて、十分に信頼できるかどうかを確認します)。このスペクトルのピークを見つけ、その周波数はビートの推測です。


実際に私が問題を抱えているのはビート検出ではなく、FFTの仕組みを理解することです。シグナル処理は私にとって本当に新しいもので、「FFTを使用して時間依存スペクトルを取得するためにウィンドウ関数を適用する」などは意味がありません。とにかくありがとう:)
クインシー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.