I’m stretching the limits of what these software platforms were designed to to, but I’m impressed such a haphazard hacked-together code as this produces fast, functional results. The code below is the simplest case code I could create which would graph the audio spectrum of the microphone input (or a WAV file or other sound as it’s being played). There’s some smoothing involved (moving window down-sampling along the frequency axis and sequence averaging along the time axis) but the darn thing seems to keep up with realtime audio input at a good 30+ FPS on my modest maching. It should work on Windows and Linux. I chose not to go with matplotlib because I didn’t think it was fast enough for my needs in this one case (although I love it in every other way). Here’s what the code below looks like running:
NOTE that this program was designed with the intent of recording the FFTs, therefore if the program “falls behind” the realtime input, it will buffer the sound on its own and try to catch up (accomplished by two layers of threading). In this way, *EVERY MOMENT* of audio is interpreted. If you’re just trying to create a spectrograph for simple purposes, have it only sample the audio when it needs to, rather than having it sample audio continuously.
import pyaudio
import scipy
import struct
import scipy.fftpack
from Tkinter import *
import threading
import time, datetime
import wckgraph
import math
#ADJUST THIS TO CHANGE SPEED/SIZE OF FFT
bufferSize=2**11
#bufferSize=2**8
# ADJUST THIS TO CHANGE SPEED/SIZE OF FFT
sampleRate=48100
#sampleRate=64000
p = pyaudio.PyAudio()
chunks=[]
ffts=[]
def stream():
global chunks, inStream, bufferSize
while True:
chunks.append(inStream.read(bufferSize))
def record():
global w, inStream, p, bufferSize
inStream = p.open(format=pyaudio.paInt16,channels=1,\
rate=sampleRate,input=True,frames_per_buffer=bufferSize)
threading.Thread(target=stream).start()
def downSample(fftx,ffty,degree=10):
x,y=[],[]
for i in range(len(ffty)/degree-1):
x.append(fftx[i*degree+degree/2])
y.append(sum(ffty[i*degree:(i+1)*degree])/degree)
return [x,y]
def smoothWindow(fftx,ffty,degree=10):
lx,ly=fftx[degree:-degree],[]
for i in range(degree,len(ffty)-degree):
ly.append(sum(ffty[i-degree:i+degree]))
return [lx,ly]
def smoothMemory(ffty,degree=3):
global ffts
ffts = ffts+[ffty]
if len(ffts)< =degree: return ffty
ffts=ffts[1:]
return scipy.average(scipy.array(ffts),0)
def detrend(fftx,ffty,degree=10):
lx,ly=fftx[degree:-degree],[]
for i in range(degree,len(ffty)-degree):
ly.append(ffty[i]-sum(ffty[i-degree:i+degree])/(degree*2))
#ly.append(fft[i]-(ffty[i-degree]+ffty[i+degree])/2)
return [lx,ly]
def graph():
global chunks, bufferSize, fftx,ffty, w
if len(chunks)>0:
data = chunks.pop(0)
data=scipy.array(struct.unpack("%dB"%(bufferSize*2),data))
#print "RECORDED",len(data)/float(sampleRate),"SEC"
ffty=scipy.fftpack.fft(data)
fftx=scipy.fftpack.rfftfreq(bufferSize*2, 1.0/sampleRate)
fftx=fftx[0:len(fftx)/4]
ffty=abs(ffty[0:len(ffty)/2])/1000
ffty1=ffty[:len(ffty)/2]
ffty2=ffty[len(ffty)/2::]+2
ffty2=ffty2[::-1]
ffty=ffty1+ffty2
ffty=scipy.log(ffty)-2
#fftx,ffty=downSample(fftx,ffty,5)
#fftx,ffty=detrend(fftx,ffty,30)
#fftx,ffty=smoothWindow(fftx,ffty,10)
ffty=smoothMemory(ffty,3)
#fftx,ffty=detrend(fftx,ffty,10)
w.clear()
#w.add(wckgraph.Axes(extent=(0, -1, fftx[-1], 3)))
w.add(wckgraph.Axes(extent=(0, -1, 6000, 3)))
w.add(wckgraph.LineGraph([fftx,ffty]))
w.update()
if len(chunks)>20:
print "falling behind...",len(chunks)
def go(x=None):
global w,fftx,ffty
print "STARTING!"
threading.Thread(target=record).start()
while True:
graph()
root = Tk()
root.title("SPECTRUM ANALYZER")
root.geometry('500x200')
w = wckgraph.GraphWidget(root)
w.pack(fill=BOTH, expand=1)
go()
mainloop()


8 comments
Tsuki
April 15, 2010 at 2:54 PM (UTC -5) Link to this comment
Heya would u midn helping me make a modification to this code so i find in at what frequence the graph is at the highest?
Scott
April 21, 2010 at 6:40 PM (UTC -5) Link to this comment
0.000 is the highest.
bc
May 15, 2010 at 7:24 AM (UTC -5) Link to this comment
A few suggestions:
-You can do all this with numpy, rather than scipy. Numpy is less of a dependancy.
-Since your input data is real, an rfft (rather than fft) would be more appropriate (more efficient).
-You can avoid all your global state if you use iterators. E.g. implement your smoothMemory function as a generator: it can then store it’s state as a local variable. for example (taken from the python deque documentation:
def moving_average(iterable, n=3):
# moving_average([40, 30, 50, 46, 39, 44]) –> 40.0 42.0 45.0 43.0
# http://en.wikipedia.org/wiki/Moving_average
it = iter(iterable)
d = deque(itertools.islice(it, n-1))
d.appendleft(0)
s = sum(d)
for elem in it:
s += elem – d.popleft()
d.append(elem)
yield s / float(n)
Peter Wang
May 18, 2010 at 11:29 PM (UTC -5) Link to this comment
Chaco (http://code.enthought.com/projects/chaco) also has an example similar to this, except it also includes a spectrograph image:
http://code.enthought.com/projects/chaco/pu-audio-spectrum.html
The code is at:
https://svn.enthought.com/enthought/browser/Chaco/trunk/examples/advanced/spectrum.py
The plots are interactive, i.e. can be panned and zoomed in realtime as they are updating.
PK
October 19, 2011 at 7:04 AM (UTC -5) Link to this comment
i have a problem integrating wckgraph into python 2.6?? any suggestions?
Jorge Orpinel
December 17, 2011 at 4:01 AM (UTC -5) Link to this comment
Hey, this code was very helpful for my final project in the Music Information Retrieval at NYU. Thanks!
Anonymous
August 24, 2012 at 4:50 AM (UTC -5) Link to this comment
can i get this code in c++
Emdad
April 7, 2013 at 2:05 AM (UTC -5) Link to this comment
Why cant I find WCKGRAPH for python2.7 or later versions?