Вычисление БПФ для получения частотного спектра из файла WAV — но не вижу ожидаемого ответа .. использование Python / Numpy .. есть идеи?

#python #numpy #signal-processing #fft

Вопрос:

Я читаю файлы .wav различных записей и пытаюсь построить частотный спектр с помощью БПФ. Результаты не имеют для меня никакого смысла. Я не уверен, что

  • У меня ошибка в коде или
  • Я неправильно понимаю использование функций библиотеки numpy FFT или
  • Я неправильно понимаю математику / обработку сигналов, и я вижу артефакт, вызванный частотой дискретизации.

На этом графике я читаю wav-файл синусоидальной волны 1 кГц, записанной со скоростью 44100 отсчетов в секунду. Я строю только 10 циклов (0,01 секунды) или 442 точки данных.

Временной ряд и график частотного спектра тона 1 кГц из файла wav

Я ожидал увидеть один высокоэнергетический пик на частоте 1 кГц. Но на самом деле я вижу эту закономерность, которая для меня имеет мало смысла.

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

Любая помощь или идеи о том, что здесь происходит, были бы очень полезны, пожалуйста.

»’

 # -*- coding: utf-8 -*- """ Created on Mon Oct 11 18:58:39 2021  """  import sounddevice as sd import soundfile as sf import numpy as np import matplotlib.pyplot as plt  soundDir = '..\01 - Engine Recordings\'  #soundFileName = soundDir   '500 Hz Sine.wav' soundFileName = soundDir   '1000 Hz Sine.wav'  theSignal, fs = sf.read(soundFileName)  length, numChannels = theSignal.shape  print('The shape of the signal data is = ', theSignal.shape)  # I am only interested in mono data  if (numChannels == 2):  theSignal = np.delete(theSignal,1,1)     # now reduce the length of the file - we only need a few seconds theSignal = theSignal[0:442] # 442 = whole number of cycles  print('Sampling rate = ', fs) print('Number of bins in sample', theSignal.size) print('Therefore, lengh of sample in seconds = ', theSignal.size * 1/fs) xAxis = [(x / fs) for x in range(0, theSignal.size)] # Creat a real-time x axis  lastX = xAxis[len(xAxis) - 1] maxY = np.amax(theSignal) minY = np.amin(theSignal) print('Original Signal : Max amplitude = ', maxY, 'Min ampltude = ', minY)  fig,ax = plt.subplots(2)  # Plot the oscillascope ax[0].set_xlim([0,lastX]) ax[0].set_ylim([minY, maxY]) ax[0].set_xlabel('Time (s)') ax[0].set_ylabel('Amplitude') ax[0].set_title('Oscillascope')  ax[0].scatter(xAxis, theSignal, s=1)   yf = np.fft.fft(theSignal) # Calculate FFT yfAbs = np.abs(yf) # just for plotting xf = np.fft.fftfreq(yf.size, 1/fs) # calculates the frequencies in the center of each bin in the output of fft().   # For PLOTTING PURPOSES ONLY .. we don't need the negative frequency components .. so I can filter these out # Note that we WILL need them if we want to reconstruct the original signal from the Frequency Domain  print('Number of frequency bins = ', xf.size) zeroBinIndex = int(xf.size / 2) print('Therefore, the zero value is at bin', zeroBinIndex, 'With a value of',xf[zeroBinIndex])  # zeroBinIndex = 1200  yfAbs = yfAbs[0:zeroBinIndex] xf = xf[0:zeroBinIndex]  maxX = np.amax(xf) minX = np.amin(xf) print('Highest frequency = ', maxX, 'Lowest frequency = ', minX)  maxyfAbs = np.amax(yfAbs) minyfAbs = np.amin(yfAbs) print('Lowest energy in plot = ', minyfAbs, 'Max energy = ', maxyfAbs)   # Plot the frequency spectrum ax[1].set_xlim([np.amin(xf), np.amax(xf)]) ax[1].set_ylim([np.amin(yfAbs), np.amax(yfAbs)]) ax[1].set_xlabel('Frequency (Hz)') ax[1].set_ylabel('Energy') ax[1].set_title('Spectrum') ax[1].scatter(xf, yfAbs, s=3)  # as a bar chart # xf = np.array(xf).flatten() # yfAbs = np.array(yfAbs).flatten() # ax[1].bar(xf, yfAbs,width = 1)  plt.show()   # sd.play(theSignal, fs) # sd.wait()    

»’