비트 감지 및 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++;
        }
    }

좋은 출발점이 Wikipedia의 FFTDSP 항목 이라고 생각 합니다. 비트 탐지 항목은 드물지만 gamedev.net의 기사로
Tobias Kienzler

답변:


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은 0Hz를 나타내고, k = 1은 43Hz이며, 마지막 밴드 인 k = 511은 22KHz (나이키 스트 주파수)입니다.

FFT를 사용하여 대역 당 신호의 에너지를 얻는 방법에 대한 귀하의 질문에 대한 답변이 되었기를 바랍니다.

부록 : 의견에 질문에 대답하고 질문에 게시 한 링크 (C의 Cooley-Tukey 알고리즘)의 코드를 사용한다고 가정합니다. 입력 데이터가 짧은 정수의 벡터로 있다고 가정 해 봅시다.

// 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 시간 낭비입니다. 이 경우 변환을 한 번 수행하고 xout에서 필요한 모든 값을 가져옵니다.


오, 나는 당신이 링크 한 코드를 보았습니다. 이미 "복잡한"형태로 결과를 제공하고 심지어 복잡한 숫자의 크기를 계산하는 함수를 제공합니다. 그런 다음 출력 벡터의 각 요소에 대해 해당 크기의 제곱을 계산해야하므로 결과 정렬에 대해 걱정할 필요가 없습니다.
CeeJay

예를 들어 0-1024 창에서 1024 개의 샘플을 모두 가지고 실제 값으로 얻은 경우 복잡한 부분이 없습니다. 주파수 대역 43Hz에서 에너지를 계산하고 싶습니다. 그러면 어떻게 통합합니까? (나는 진짜 부분 만 다시, postive 부분이 필요하다) 만약 당신이 의사 코드에서 그것을 할 수 있다면 나는 당신의 깊이에 영원히있을 것이고 실제로 개념을 조금 이해하게 될 것이다. :
Quincy

내가 작성한 코드는 이미 "복잡한"구조를 포함하고있는 C 라이브러리를 사용하고 있습니다. 이것은 내 질문에 설명 된 언 래핑을 불필요하게 만듭니다 (그리고 코드는 그것을 반영합니다)
CeeJay


0

나는 이것을하지 않았거나 그것에 대해 많이 읽지 만 첫 번째 장면은 다음과 같습니다.

우선, FFT를 사용하여 시간 종속 스펙트럼을 얻으려면 윈도우 기능을 적용해야합니다. 비트는 일반적으로 낮은 주파수에 있으므로 일부 주파수의 강도에 더 큰 시간 범위를 갖는 다른 FFT를 적용 하십시오 (예를 들어 100Hz에서 1로만 시작하여 충분히 신뢰할 수 있는지 확인하십시오). 이 스펙트럼에서 피크를 찾으십시오. 그리고 주파수는 비트에 대한 추측입니다.


실제로 문제가되는 비트 감지가 아니라 FFT의 작동 방식을 이해하는 것입니다. 나는 신호 처리를 처음 접했고 다음과 같은 것들을 좋아한다. 어쨌든 감사합니다 :)
Quincy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.