我正在尝试用8条LED灯创建一个自制频谱分析仪。
我正在努力的部分是执行FFT并了解如何使用结果。
到目前为止,这就是我所拥有的:
import opc
import time
import pyaudio
import wave
import sys
import numpy
import math
CHUNK = 1024
# Gets the pitch from the audio
def pitch(signal):
# NOT SURE IF ANY OF THIS IS CORRECT
signal = numpy.fromstring(signal, 'Int16');
print "signal = ", signal
testing = numpy.fft.fft(signal)
print "testing = ", testing
wf = wave.open(sys.argv[1], 'rb')
RATE = wf.getframerate()
p = pyaudio.PyAudio() # Instantiate PyAudio
# Open Stream
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
# Read data
data = wf.readframes(CHUNK)
# Play Stream
while data != '':
stream.write(data)
data = wf.readframes(CHUNK)
frequency = pitch(data)
print "%f frequency" %frequency
我正忙着在pitch
方法中做些什么。我知道我需要对传入的数据执行FFT,但我真的不确定该怎么做。
还应该使用this功能吗?
答案 0 :(得分:4)
由于np.fft.fft的工作方式,如果使用1024个数据点,您将获得512个频率的值(加上零值Hz, DC偏移)。如果您只需要8个频率,则必须为其提供16个数据点。
你可以通过64倍的下采样来做你想要的事情 - 然后16个下采样点将时间等效到1024个原始点。我从来没有探究过这个,所以我不知道这会带来什么或者可能是什么陷阱。
你将不得不做一些学习 - The Scientist and Engineer's Guide to Digital Signal Processing真的是一种优秀的资源,至少对我而言。
请记住,对于音频CD .wav文件,采样频率为44100 Hz - 1024个采样块仅为声音的23 mS。
scipy.io.wavfile.read可让您轻松获取数据。
samp_rate, data = scipy.io.wavfile.read(filename)
data
是一个2-d numpy数组,其中一个通道位于第0列,数据为[:,0],另一个位于第1列,数据为[:,1]
Matplotlib的specgram和psd函数可以为您提供所需的数据。与您尝试做的相似的图形将是。
from matplotlib import pyplot as plt
import scipy.io.wavfile
samp_rate, data = scipy.io.wavfile.read(filename)
Pxx, freqs, bins, im = plt.specgram(data[:1024,0], NFFT = 16, noverlap = 0, Fs = samp_rate)
plt.show()
plt.close()
由于您没有进行任何绘图,只需使用matplolib.mlab.specgram。
Pxx, freqs, t = matplolib.mlab.specgram(data[:1024,0], NFFT = 16, noverlap = 0, Fs = samp_rate)
其返回值( Pxx , freqs , t )
- *Pxx*: 2-D array, columns are the periodograms of successive segments
- *freqs*: 1-D array of frequencies corresponding to the rows in Pxx
- *t*: 1-D array of times corresponding to midpoints of segments.
Pxx[1:, 0]
将是T0的频率值,T1的Pxx[1:, 1]
,T2的Pxx[1:, 2]
,......这是您要显示的内容。您不使用Pxx[0, :]
,因为它是0 Hz。
功率谱密度 - matplotlib.mlab.psd()
可能另一种降低8个频段的策略是使用大块并对值进行规范化。然后,您可以将值分解为八个段,并获得每个段的总和。我认为这是有效的 - 可能仅适用于功率谱密度。 sklearn.preprocessing.normalize
w = sklearn.preprocessing.normalize(Pxx[1:,:], norm = 'l1', axis = 0)
但话又说回来,我就把这一切都搞定了。
答案 1 :(得分:1)
我不知道@wwii在他的回答中提到的scipy.io.wavfile.read
函数,但似乎他的建议是处理信号加载的方法。但是,我只是想评论傅里叶变换。
我认为您打算对LED设置做的是根据您打算使用的8个频段中每个频段的光谱功率来改变每个LED的亮度。因此,我所理解的你需要的是随着时间的推移以某种方式计算能量。第一个复杂因素是"如何计算光谱功率?"
执行此操作的最佳方法是使用numpy.fft.rfft
,它计算仅具有实数(不是复数)的信号的傅里叶变换。另一方面,函数numpy.fft.fft
是一个通用函数,可以为具有复数的信号计算快速傅里叶变换。概念上的差异是numpy.fft.fft
可用于研究行波及其传播方向。这是因为返回的幅度对应于positive or negative frequencies,表示波的传播方式。 numpy.fft.rfft
产生实值频率的幅度,如numpy.fft.rfftfreq
所示,这就是你需要的。
最后一个问题是选择适当的频段来计算频谱功率。人耳具有巨大的频率响应范围,并且每个频带的宽度将变化很大,低频带非常窄并且高频带非常宽。谷歌搜索,我发现this很好的资源,定义了7个相关的频段
我建议使用这些频段,但将上中频分为2-3 kHz和3-4 kHz。这样您就可以使用8 LED设置。我上传了更新的音高函数供您使用
wf = wave.open(sys.argv[1], 'rb')
CHUNK = 1024
RATE = wf.getframerate()
DT = 1./float(RATE) # time between two successive audio frames
FFT_FREQS = numpy.fft.nfftfreq(CHUNCK,DT)
FFT_FREQS_INDS = -numpy.ones_like(FFT_FREQS)
bands_bounds = [[20,60], # Sub-bass
[60,250], # Bass
[250,500], # Low midrange
[500,2000], # Midrange
[2000,3000], # Upper midrange 0
[3000,4000], # Upper midrange 1
[4000,6000], # Presence
[6000,20000]] # Brilliance
for f_ind,freq in enumerate(FFT_FREQS):
for led_ind,bounds in enumerate(bands_bounds):
if freq<bounds[1] and freq>=bounds[0]:
FFT_FREQS_INDS[ind] = led_ind
# Returns the spectral power in each of the 8 bands assigned to the LEDs
def pitch(signal):
# CONSIDER SWITCHING TO scipy.io.wavfile.read TO GET SIGNAL
signal = numpy.fromstring(signal, 'Int16');
amplitude = numpy.fft.rfft(signal.astype(numpy.float))
power = [np.sum(np.abs(amplitude[FFT_FREQS_INDS==led_ind])**2) for led_ind in range(len(bands_bounds))]
return power
代码的第一部分计算fft频率并构造数组FFT_FREQS_INDS
,指示fft频率对应的8个频段中的哪一个。然后,在pitch
中,计算每个频带中的频谱功率。当然,这可以优化,但我试图使代码不言自明。