2
This plot displays the audio spectrum from the microphone.
4
Based on updating_plot.py
6
# Major library imports
8
from numpy import zeros, linspace, short, fromstring, transpose, array
11
# Enthought library imports
12
from enable.api import Window, Component, ComponentEditor
13
from traits.api import HasTraits, Instance, List
14
from traitsui.api import Item, Group, View, Handler
15
from pyface.timer.api import Timer
18
from chaco.api import (Plot, ArrayPlotData, HPlotContainer, VPlotContainer,
19
AbstractMapper, LinePlot, LinearMapper, DataRange1D)
23
SPECTROGRAM_LENGTH = 50
25
class WaterfallRenderer(LinePlot):
27
# numpy arrays of the same length
28
values = List(args=[])
30
# Maps each array in values into a contrained, short screen space
31
y2_mapper = Instance(AbstractMapper)
33
_cached_data_pts = List()
34
_cached_screen_pts = List()
36
def _gather_points(self):
37
if not self._cache_valid:
38
if not self.index or len(self.values) == 0:
41
index = self.index.get_data()
45
if numindex == 0 or all(len(v)==0 for v in values) or all(numindex != len(v) for v in values):
46
self._cached_data_pts = []
47
self._cache_valid = True
49
self._cached_data_pts = [transpose(array((index, v))) for v in values]
50
self._cache_value = True
53
def get_screen_points(self):
55
return [self.map_screen(pts, i) for i, pts in enumerate(self._cached_data_pts)]
57
def map_screen(self, data_array, data_offset=None):
58
""" data_offset, if provided, is a float that will be mapped
59
into screen space using self.value_mapper and then added to
60
mapping data_array with y2_mapper. If data_offset is not
61
provided, then y2_mapper is used.
63
if len(data_array) == 0:
65
x_ary, y_ary = transpose(data_array)
66
sx = self.index_mapper.map_screen(x_ary)
67
if data_offset is not None:
68
dy = self.value_mapper.map_screen(data_offset)
69
sy = self.y2_mapper.map_screen(y_ary) + dy
71
sy = self.value_mapper.map_screen(y_ary)
73
if self.orientation == "h":
74
return transpose(array((sx, sy)))
76
return transpose(array((sy, sx)))
78
#============================================================================
79
# Create the Chaco plot.
80
#============================================================================
82
def _create_plot_component(obj):
83
# Setup the spectrum plot
84
frequencies = linspace(0.0, float(SAMPLING_RATE)/2, num=NUM_SAMPLES/2)
85
obj.spectrum_data = ArrayPlotData(frequency=frequencies)
86
empty_amplitude = zeros(NUM_SAMPLES/2)
87
obj.spectrum_data.set_data('amplitude', empty_amplitude)
89
obj.spectrum_plot = Plot(obj.spectrum_data)
90
spec_renderer = obj.spectrum_plot.plot(("frequency", "amplitude"), name="Spectrum",
92
obj.spectrum_plot.padding = 50
93
obj.spectrum_plot.title = "Spectrum"
94
spec_range = obj.spectrum_plot.plots.values()[0][0].value_mapper.range
97
obj.spectrum_plot.index_axis.title = 'Frequency (hz)'
98
obj.spectrum_plot.value_axis.title = 'Amplitude'
101
times = linspace(0.0, float(NUM_SAMPLES)/SAMPLING_RATE, num=NUM_SAMPLES)
102
obj.time_data = ArrayPlotData(time=times)
103
empty_amplitude = zeros(NUM_SAMPLES)
104
obj.time_data.set_data('amplitude', empty_amplitude)
106
obj.time_plot = Plot(obj.time_data)
107
obj.time_plot.plot(("time", "amplitude"), name="Time", color="blue")
108
obj.time_plot.padding = 50
109
obj.time_plot.title = "Time"
110
obj.time_plot.index_axis.title = 'Time (seconds)'
111
obj.time_plot.value_axis.title = 'Amplitude'
112
time_range = obj.time_plot.plots.values()[0][0].value_mapper.range
113
time_range.low = -0.2
114
time_range.high = 0.2
117
values = [zeros(NUM_SAMPLES/2) for i in xrange(SPECTROGRAM_LENGTH)]
118
p = WaterfallRenderer(index = spec_renderer.index, values = values,
119
index_mapper = LinearMapper(range = obj.spectrum_plot.index_mapper.range),
120
value_mapper = LinearMapper(range = DataRange1D(low=0, high=SPECTROGRAM_LENGTH)),
121
y2_mapper = LinearMapper(low_pos=0, high_pos=8,
122
range=DataRange1D(low=0, high=15)),
125
obj.spectrogram_plot = p
128
dummy.index_axis.mapper.range = p.index_mapper.range
129
dummy.index_axis.title = "Frequency (hz)"
132
container = HPlotContainer()
133
container.add(obj.spectrum_plot)
134
container.add(obj.time_plot)
136
c2 = VPlotContainer()
143
def get_audio_data():
144
pa = pyaudio.PyAudio()
145
stream = pa.open(format=pyaudio.paInt16, channels=1, rate=SAMPLING_RATE,
146
input=True, frames_per_buffer=NUM_SAMPLES)
147
audio_data = fromstring(stream.read(NUM_SAMPLES), dtype=short)
149
normalized_data = audio_data / 32768.0
150
return (abs(fft(normalized_data))[:NUM_SAMPLES/2], normalized_data)
153
# HasTraits class that supplies the callable for the timer event.
154
class TimerController(HasTraits):
156
def onTimer(self, *args):
157
spectrum, time = get_audio_data()
158
self.spectrum_data.set_data('amplitude', spectrum)
159
self.time_data.set_data('amplitude', time)
160
spec_data = self.spectrogram_plot.values[1:] + [spectrum]
161
self.spectrogram_plot.values = spec_data
162
self.spectrum_plot.request_redraw()
165
#============================================================================
166
# Attributes to use for the plot view.
168
title = "Audio Spectrum Waterfall"
170
#============================================================================
171
# Demo class that is used by the demo.py application.
172
#============================================================================
174
class DemoHandler(Handler):
176
def closed(self, info, is_ok):
177
""" Handles a dialog-based user interface being closed by the user.
178
Overridden here to stop the timer once the window is destroyed.
181
info.object.timer.Stop()
184
class Demo(HasTraits):
186
plot = Instance(Component)
188
controller = Instance(TimerController, ())
190
timer = Instance(Timer)
194
Item('plot', editor=ComponentEditor(size=size),
196
orientation = "vertical"),
197
resizable=True, title=title,
198
width=size[0], height=size[1]+25,
202
def __init__(self, **traits):
203
super(Demo, self).__init__(**traits)
204
self.plot = _create_plot_component(self.controller)
206
def edit_traits(self, *args, **kws):
207
# Start up the timer! We should do this only when the demo actually
208
# starts and not when the demo object is created.
209
self.timer = Timer(20, self.controller.onTimer)
210
return super(Demo, self).edit_traits(*args, **kws)
212
def configure_traits(self, *args, **kws):
213
# Start up the timer! We should do this only when the demo actually
214
# starts and not when the demo object is created.
215
self.timer = Timer(20, self.controller.onTimer)
216
return super(Demo, self).configure_traits(*args, **kws)
221
if __name__ == "__main__":
222
popup.configure_traits()