Неточность FFT для С#

Я экспериментировал с алгоритмом FFT. Я использую NAudio вместе с рабочим кодом алгоритма FFT из Интернета. Основываясь на моих наблюдениях за производительностью, результирующий шаг является неточным.

Что происходит, так это то, что у меня есть MIDI (сгенерированный GuitarPro), преобразованный в WAV файл (44.1khz, 16-bit, mono), который содержит прогрессию основного тона, начиная с E2 (самая низкая гитарная нота) до приблизительно E6. Каковы результаты для нижних нот (вокруг E2-B3), как правило, это очень неправильно. Но, достигнув C4, он несколько верен в том, что вы уже можете видеть правильную прогрессию (следующая заметка - С# 4, затем D4 и т.д.). Однако проблема заключается в том, что обнаруженный шаг на половину меньше, чем фактический шаг ( например, C4 должна быть заметкой, но отображается D # 4).

Как вы думаете, может быть неправильно? При необходимости я могу отправить код. Огромное спасибо! Я все еще начинаю понимать поле DSP.

Изменить: вот грубая царапина того, что я делаю

byte[] buffer = new byte[8192];
int bytesRead;
do
{
  bytesRead = stream16.Read(buffer, 0, buffer.Length);
} while (bytesRead != 0);

И затем: (waveBuffer - это просто класс, который должен преобразовать байт [] в float [], поскольку функция принимает только float [])

public int Read(byte[] buffer, int offset, int bytesRead)
{
  int frames = bytesRead / sizeof(float);
  float pitch = DetectPitch(waveBuffer.FloatBuffer, frames);
}

И наконец: (Smbpitchfft - класс, у которого есть FFT-алгоритм... я считаю, что в этом нет ничего плохого, поэтому я не разместил его здесь)

private float DetectPitch(float[] buffer, int inFrames)
{
  Func<int, int, float> window = HammingWindow;
  if (prevBuffer == null)
  {
    prevBuffer = new float[inFrames]; //only contains zeroes
  }  

  // double frames since we are combining present and previous buffers
  int frames = inFrames * 2;
  if (fftBuffer == null)
  {
    fftBuffer = new float[frames * 2]; // times 2 because it is complex input
  }

  for (int n = 0; n < frames; n++)
  {
     if (n < inFrames)
     {
       fftBuffer[n * 2] = prevBuffer[n] * window(n, frames);
       fftBuffer[n * 2 + 1] = 0; // need to clear out as fft modifies buffer
     }
     else
     {
       fftBuffer[n * 2] = buffer[n - inFrames] * window(n, frames);
       fftBuffer[n * 2 + 1] = 0; // need to clear out as fft modifies buffer
     }
   }
   SmbPitchShift.smbFft(fftBuffer, frames, -1);
  }

И для интерпретации результата:

float binSize = sampleRate / frames;
int minBin = (int)(82.407 / binSize); //lowest E string on the guitar
int maxBin = (int)(1244.508 / binSize); //highest E string on the guitar

float maxIntensity = 0f;
int maxBinIndex = 0;

for (int bin = minBin; bin <= maxBin; bin++)
{
    float real = fftBuffer[bin * 2];
    float imaginary = fftBuffer[bin * 2 + 1];
    float intensity = real * real + imaginary * imaginary;
    if (intensity > maxIntensity)
    {
        maxIntensity = intensity;
        maxBinIndex = bin;
    }
}

return binSize * maxBinIndex;

UPDATE (если кому-то все еще интересно):

Итак, в одном из приведенных ниже ответов указано, что пик частоты из БПФ не всегда эквивалентен тангажу. Я это понимаю. Но я хотел попробовать что-то для себя, если бы это было так (в предположении, что бывают моменты, когда пик частоты - это результат). Таким образом, в основном, у меня есть 2 программного обеспечения (SpectraPLUS и FFTProperties от DewResearch, кредиты для них), которые могут отображать частотную область для аудиосигналов.

Итак, вот результаты частотных пиков во временной области:

SpectraPLUS

SpectraPLUS

и свойства FFT: enter image description here

Это было сделано с использованием тестовой ноты A2 (около 110 Гц). При просмотре изображений они имеют частотные пики в диапазоне 102-112 Гц для SpectraPLUS и 108 Гц для свойств FFT. На моем коде я получаю 104 Гц (я использую 8192 блока и выборку 44.1khz... 8192 затем удваивается, чтобы сделать его сложным, поэтому в конце я получаю около 5 Гц для binsize по сравнению с 10 Гц binsize SpectraPLUS).

Итак, теперь я немного смущен, так как на программном обеспечении они, похоже, вернут правильный результат, но в моем коде я всегда получаю 104 Гц (учтите, что я сравнил функцию FFT, которую я использовал с другими, такими как Math.Net и это кажется правильным).

Считаете ли вы, что проблема может быть связана с моей интерпретацией данных? Или же программное обеспечение делает что-то еще перед отображением частотного спектра? Спасибо!

Ответ 1

Похоже, у вас может быть проблема с интерпретацией с выходом FFT. Несколько случайных точек:

  • FFT имеет конечное разрешение - каждый выходной бит имеет разрешение Fs / N, где Fs - частота дискретизации, а N - размер FFT

  • для нот, которые являются низкими на музыкальной шкале, разница в частоте между последовательными нотами относительно невелика, поэтому вам понадобится достаточно большой N для различения нот, которые находятся на расстоянии полутона (см. примечание 1 ниже)

  • первый бит (индекс 0) содержит энергию с центром в 0 Гц, но включает в себя энергию от +/- Fs / 2N

  • bin i содержит энергию с центром в i * Fs / N, но включает в себя энергию от +/- Fs / 2N с каждой стороны этой центральной частоты

  • вы получите спектральную утечку из соседних ящиков - насколько это плохо, это зависит от того, что функция окна, которую вы используете - нет окна (== прямоугольное окно), а спектральная утечка будет очень плохой (очень широкие пики) - для оценки частоты вы хотите выбрать функцию окна, которая дает вам острый пики

    Шаг
  • - это не то же самое, что и частота - шаг - это восприятие, частота - физическая величина - воспринимаемый шаг музыкального инструмента может немного отличаться от основной частоты, в зависимости от типа инструмента (некоторые инструменты даже не производят значительную энергию на их основной частоте, но мы по-прежнему воспринимаем их шаг, как если бы фундаментальные присутствовали)

Мое лучшее предположение из доступной ограниченной информации заключается в том, что, возможно, вы "отключены одним" где-то в вашем преобразовании индекса bin на частоту, или, возможно, ваш FFT слишком мал, чтобы дать вам достаточное разрешение для низких заметок и вам может потребоваться увеличить N.

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


Примечания

(1) Просто, чтобы поместить некоторые цифры на это, E2 составляет 82,4 Гц, F2 составляет 87,3 Гц, поэтому вам нужно разрешение несколько лучше, чем 5 Гц, чтобы различать самые низкие две ноты на гитаре (и намного более тонкие, чем это если вы действительно хотите сделать, скажем, точную настройку). При выборке 44,1 кГц вам, вероятно, понадобится БПФ, по крайней мере, N = 8192, чтобы дать вам достаточное разрешение (44100/8192 = 5,4 Гц), вероятно, было бы лучше N = 16384.

Ответ 2

Я думал, это поможет тебе. Я сделал несколько сюжетов из шести открытых струн гитары. Код находится в Python с использованием pylab, который я рекомендую для экспериментов:

# analyze distorted guitar notes from
# http://www.freesound.org/packsViewSingle.php?id=643
#
# 329.6 E - open 1st string
# 246.9 B - open 2nd string
# 196.0 G - open 3rd string
# 146.8 D - open 4th string
# 110.0 A - open 5th string
#  82.4 E - open 6th string

from pylab import *
import wave

fs = 44100.0 
N = 8192 * 10
t = r_[:N] / fs
f = r_[:N/2+1] * fs / N 
gtr_fun = [329.6, 246.9, 196.0, 146.8, 110.0, 82.4]

gtr_wav = [wave.open('dist_gtr_{0}.wav'.format(n),'r') for n in r_[1:7]]
gtr = [fromstring(g.readframes(N), dtype='int16') for g in gtr_wav]
gtr_t = [g / float64(max(abs(g))) for g in gtr]
gtr_f = [2 * abs(rfft(g)) / N for g in gtr_t]

def make_plots():
    for n in r_[:len(gtr_t)]:
        fig = figure()
        fig.subplots_adjust(wspace=0.5, hspace=0.5)
        subplot2grid((2,2), (0,0))
        plot(t, gtr_t[n]); axis('tight')
        title('String ' + str(n+1) + ' Waveform')
        subplot2grid((2,2), (0,1))
        plot(f, gtr_f[n]); axis('tight')
        title('String ' + str(n+1) + ' DFT')
        subplot2grid((2,2), (1,0), colspan=2)
        M = int(gtr_fun[n] * 16.5 / fs * N)
        plot(f[:M], gtr_f[n][:M]); axis('tight')
        title('String ' + str(n+1) + ' DFT (16 Harmonics)')

if __name__ == '__main__':
    make_plots()
    show()

Строка 1, фундаментальная = 329,6 Гц:

String 1, f0 = 329.6 Hz

Строка 2, фундаментальная = 246,9 Гц:

enter image description here

Строка 3, фундаментальная = 196,0 Гц:

enter image description here

Строка 4, основная = 146,8 Гц:

enter image description here

Строка 5, фундаментальная = 110,0 Гц:

enter image description here

Строка 6, фундаментальная = 82,4 Гц:

enter image description here

Основная частота не всегда является доминирующей гармоникой. Он определяет расстояние между гармониками периодического сигнала.

Ответ 3

У меня был похожий вопрос, и ответом для меня было использование Goertzel вместо FFT. Если вы знаете, какие тона вы ищете (MIDI), то Goertzel способен обнаруживать тоны в пределах одной синусовой волны (один цикл). Он делает это, создавая синусовую волну звука и "помещая его поверх исходных данных", чтобы увидеть, существует ли он. БПФ сэмплирует большие объемы данных, чтобы обеспечить приблизительный частотный спектр.

Ответ 4

Музыкальный шаг отличается от пика частоты. Шаг - это психо-перцептуальное явление, которое может зависеть больше от обертонов и т.д. Частота того, что человек назвал бы высотой тона, может отсутствовать или совсем мала в реальных спектрах сигнала.

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

Итак, у вас есть как минимум 2 проблемы, с которыми можно бороться. Существует тонна научных работ по оценке частоты, а также отдельная тема оценки шага. Начните там.