Phát hiện nhịp và FFT


13

Tôi đang làm việc trên một trò chơi platformer bao gồm âm nhạc với tính năng phát hiện nhịp. Tôi hiện đang phát hiện nhịp đập bằng cách kiểm tra khi biên độ hiện tại vượt quá mẫu lịch sử. Điều này không hoạt động tốt với các thể loại âm nhạc, như rock, có biên độ khá ổn định.

Vì vậy, tôi đã nhìn xa hơn và thấy các thuật toán chia âm thanh thành nhiều dải bằng FFT ... sau đó tôi tìm thấy thuật toán Cooley-Tukey FFt

Vấn đề duy nhất tôi gặp phải là tôi khá mới với âm thanh và tôi không biết làm thế nào để sử dụng điều đó để phân chia tín hiệu thành nhiều tín hiệu.

Vì vậy, câu hỏi của tôi là:

Làm thế nào để bạn sử dụng FFT để chia tín hiệu thành nhiều băng tần?

Ngoài ra, đối với những người quan tâm, đây là thuật toán của tôi trong 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++;
        }
    }

Tôi đoán một điểm khởi đầu tốt là các mục FFTDSP của wikipedia . Mục phát hiện nhịp rất thưa thớt nhưng liên kết đến một bài viết tại gamedev.net
Tobias Kienzler

Câu trả lời:


14

Chà, nếu tín hiệu đầu vào của bạn là thật (như trong, mỗi mẫu là một số thực), phổ sẽ đối xứng và phức tạp. Khai thác tính đối xứng, thông thường các thuật toán FFT đóng gói kết quả bằng cách chỉ trả lại cho bạn một nửa tích cực của phổ. Phần thực của mỗi dải nằm trong các mẫu chẵn và phần ảo trong các mẫu lẻ. Hoặc đôi khi các phần thực được đóng gói cùng nhau trong nửa đầu của phản hồi và các phần ảo trong nửa sau.

Trong các công thức, nếu X [k] = FFT (x [n]), bạn cung cấp cho nó một vectơ i [n] = x [n] và nhận đầu ra o [m], sau đó

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

(mặc dù đôi khi bạn nhận được X [k] = o [k] + j · o [k + K / 2], trong đó K là chiều dài của cửa sổ của bạn, 1024 trong ví dụ của bạn). Nhân tiện, j là đơn vị tưởng tượng, sqrt (-1).

Độ lớn của một dải được tính là gốc của sản phẩm của dải này với liên hợp phức tạp của nó:

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

Và năng lượng được định nghĩa là bình phương của độ lớn.

Nếu chúng ta gọi a = o [2k] và b = o [2k + 1], chúng ta sẽ nhận được

X[k] = a + j·b

vì thế

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

Bỏ kiểm soát toàn bộ, nếu bạn có o [m] là đầu ra từ thuật toán FFT, năng lượng trong dải k là:

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

(Lưu ý: Tôi đã sử dụng ký hiệu · để biểu thị phép nhân thay vì * thông thường để tránh nhầm lẫn với toán tử chia động từ)

Tần số của băng tần k, giả sử tần số lấy mẫu là 44,1Khz và cửa sổ 1024 mẫu, là

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

Vì vậy, ví dụ, băng tần đầu tiên của bạn k = 0 đại diện cho 0 Hz, k = 1 là 43 Hz và băng tần cuối k = 511 là 22KHz (tần số Nyquist).

Tôi hy vọng điều này trả lời câu hỏi của bạn về cách bạn lấy năng lượng của tín hiệu trên mỗi băng tần bằng FFT.

Phụ lục : Trả lời câu hỏi của bạn trong bình luận và giả sử bạn đang sử dụng mã từ liên kết bạn đã đăng trong câu hỏi (Thuật toán Cooley-Tukey trong C): Giả sử bạn có dữ liệu đầu vào của mình dưới dạng vectơ ints ngắn:

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

Chữ C của tôi hơi thô (hiện tại tôi đang viết mã bằng C ++), nhưng tôi hy vọng tôi đã không phạm phải bất kỳ sai lầm lớn nào với mã này. Tất nhiên, nếu bạn quan tâm đến năng lượng của các băng tần khác, sẽ không có ý nghĩa gì khi chuyển đổi toàn bộ cửa sổ cho từng dải, điều đó sẽ gây lãng phí thời gian của CPU. Trong trường hợp đó, thực hiện chuyển đổi một lần và nhận tất cả các giá trị bạn cần từ xout.


Ồ, tôi vừa xem mã bạn đã liên kết, nó đã cung cấp cho bạn kết quả ở dạng "phức tạp" và thậm chí cung cấp cho bạn một hàm để tính độ lớn của một số phức. Sau đó, bạn sẽ chỉ phải tính bình phương độ lớn đó cho từng phần tử của vectơ đầu ra, không cần phải lo lắng về việc sắp xếp kết quả.
CeeJay

Ví dụ: nếu tôi có tất cả 1024 mẫu từ cửa sổ 0-1024 và tôi đã nhận chúng dưới dạng giá trị thực, do đó không có phần phức tạp. và tôi muốn tính năng lượng trong đó trên dải tần số 43Hz. Làm thế nào tôi có thể tích hợp nó sau đó? (Tôi chỉ cần phần lưng thật, phần postive) Nếu bạn có thể làm điều đó trong một số giả Sẽ sâu của em mãi mãi và sau đó tôi thực sự có thể nắm bắt được khái niệm một chút :)
Quincy

Mã tôi đã viết đang sử dụng thư viện C mà bạn đã liên kết, vốn đã chứa cấu trúc "phức tạp". Điều này làm cho việc hủy ghép mà tôi mô tả trong câu hỏi của mình là không cần thiết (và mã phản ánh điều đó)
CeeJay


0

Tôi đã không làm điều này hoặc đọc nhiều về bản thân mình, nhưng phát bắn đầu tiên của tôi là như thế này:

Trước hết, bạn sẽ cần áp dụng chức năng cửa sổ để có được phổ phụ thuộc thời gian với FFT. Nhịp thường nằm ở các tần số thấp hơn, vì vậy hãy áp dụng một FFT khác với cửa sổ thời gian lớn hơn về cường độ của một số tần số này (để đơn giản bắt đầu chỉ với 1 tại ví dụ 100 Hz và xem điều đó có đủ tin cậy không). Tìm cực đại trong phổ này và tần số đó là dự đoán cho nhịp.


Đây thực sự không phải là phát hiện nhịp tôi gặp khó khăn nhưng hiểu cách hoạt động của FFT. Tôi thực sự chưa quen với việc xử lý tín hiệu và những điều như: "áp dụng chức năng cửa sổ để có được phổ phụ thuộc thời gian với FFT" không có ý nghĩa gì với tôi. Dù sao cũng cảm ơn :)
Quincy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.