3 # Copyright 2003,2004,2005,2007,2008 Free Software Foundation, Inc.
5 # This file is part of GNU Radio
7 # GNU Radio is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3, or (at your option)
12 # GNU Radio is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with GNU Radio; see the file COPYING. If not, write to
19 # the Free Software Foundation, Inc., 51 Franklin Street,
20 # Boston, MA 02110-1301, USA.
23 from gnuradio import gr, gru, window
24 from gnuradio.wxgui import stdgui2
26 import gnuradio.wxgui.plot as plot
31 default_fftsink_size = (640,240)
32 default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15)
34 class waterfall_sink_base(object):
35 def __init__(self, input_is_real=False, baseband_freq=0,
36 sample_rate=1, fft_size=512,
37 fft_rate=default_fft_rate,
38 average=False, avg_alpha=None, title=''):
40 # initialize common attributes
41 self.baseband_freq = baseband_freq
42 self.sample_rate = sample_rate
43 self.fft_size = fft_size
44 self.fft_rate = fft_rate
45 self.average = average
47 self.avg_alpha = 2.0 / fft_rate
49 self.avg_alpha = avg_alpha
51 self.input_is_real = input_is_real
52 self.msgq = gr.msg_queue(2) # queue up to 2 messages
54 def set_average(self, average):
55 self.average = average
57 self.avg.set_taps(self.avg_alpha)
59 self.avg.set_taps(1.0)
61 def set_avg_alpha(self, avg_alpha):
62 self.avg_alpha = avg_alpha
64 def set_baseband_freq(self, baseband_freq):
65 self.baseband_freq = baseband_freq
67 def set_sample_rate(self, sample_rate):
68 self.sample_rate = sample_rate
72 self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
74 class waterfall_sink_f(gr.hier_block2, waterfall_sink_base):
75 def __init__(self, parent, baseband_freq=0,
76 y_per_div=10, ref_level=50, sample_rate=1, fft_size=512,
77 fft_rate=default_fft_rate, average=False, avg_alpha=None,
78 title='', size=default_fftsink_size, **kwargs):
80 gr.hier_block2.__init__(self, "waterfall_sink_f",
81 gr.io_signature(1, 1, gr.sizeof_float),
82 gr.io_signature(0,0,0))
84 waterfall_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq,
85 sample_rate=sample_rate, fft_size=fft_size,
87 average=average, avg_alpha=avg_alpha, title=title)
89 self.s2p = gr.serial_to_parallel(gr.sizeof_float, self.fft_size)
90 self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size,
91 max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
93 mywindow = window.blackmanharris(self.fft_size)
94 self.fft = gr.fft_vfc(self.fft_size, True, mywindow)
95 self.c2mag = gr.complex_to_mag(self.fft_size)
96 self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size)
97 self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))
98 self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)
99 self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink)
101 self.win = waterfall_window(self, parent, size=size)
102 self.set_average(self.average)
105 class waterfall_sink_c(gr.hier_block2, waterfall_sink_base):
106 def __init__(self, parent, baseband_freq=0,
107 y_per_div=10, ref_level=50, sample_rate=1, fft_size=512,
108 fft_rate=default_fft_rate, average=False, avg_alpha=None,
109 title='', size=default_fftsink_size, **kwargs):
111 gr.hier_block2.__init__(self, "waterfall_sink_f",
112 gr.io_signature(1, 1, gr.sizeof_gr_complex),
113 gr.io_signature(0,0,0))
115 waterfall_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq,
116 sample_rate=sample_rate, fft_size=fft_size,
118 average=average, avg_alpha=avg_alpha, title=title)
120 self.s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size)
121 self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size,
122 max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
124 mywindow = window.blackmanharris(self.fft_size)
125 self.fft = gr.fft_vcc(self.fft_size, True, mywindow)
126 self.c2mag = gr.complex_to_mag(self.fft_size)
127 self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size)
128 self.log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))
129 self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)
130 self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink)
132 self.win = waterfall_window(self, parent, size=size)
133 self.set_average(self.average)
136 # ------------------------------------------------------------------------
138 myDATA_EVENT = wx.NewEventType()
139 EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0)
142 class DataEvent(wx.PyEvent):
143 def __init__(self, data):
144 wx.PyEvent.__init__(self)
145 self.SetEventType (myDATA_EVENT)
149 self.__class__ (self.GetId())
151 class input_watcher (gru.msgq_runner):
152 def __init__ (self, msgq, fft_size, event_receiver, **kwds):
153 self.fft_size = fft_size
154 self.event_receiver = event_receiver
155 gru.msgq_runner.__init__(self, msgq, self.handle_msg)
157 def handle_msg(self, msg):
158 itemsize = int(msg.arg1())
159 nitems = int(msg.arg2())
161 s = msg.to_string() # get the body of the msg as a string
163 # There may be more than one FFT frame in the message.
164 # If so, we take only the last one
166 start = itemsize * (nitems - 1)
167 s = s[start:start+itemsize]
169 complex_data = numpy.fromstring (s, numpy.float32)
170 de = DataEvent (complex_data)
171 wx.PostEvent (self.event_receiver, de)
174 class waterfall_window (wx.Panel):
175 def __init__ (self, fftsink, parent, id = -1,
176 pos = wx.DefaultPosition, size = wx.DefaultSize,
177 style = wx.DEFAULT_FRAME_STYLE, name = ""):
178 wx.Panel.__init__(self, parent, id, pos, size, style, name)
179 self.set_baseband_freq = fftsink.set_baseband_freq
180 self.fftsink = fftsink
181 self.bm = wx.EmptyBitmap(self.fftsink.fft_size, 300, -1)
183 self.scale_factor = 5.0 # FIXME should autoscale, or set this
186 dc1.SelectObject(self.bm)
189 self.pens = self.make_pens()
191 wx.EVT_PAINT( self, self.OnPaint )
192 wx.EVT_CLOSE (self, self.on_close_window)
193 EVT_DATA_EVENT (self, self.set_data)
195 self.build_popup_menu()
197 wx.EVT_CLOSE (self, self.on_close_window)
198 self.Bind(wx.EVT_RIGHT_UP, self.on_right_click)
200 self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self)
203 def on_close_window (self, event):
204 print "waterfall_window: on_close_window"
205 self.keep_running = False
207 def const_list(self,const,len):
210 def make_colormap(self):
212 r.extend(self.const_list(0,96))
213 r.extend(range(0,255,4))
214 r.extend(self.const_list(255,64))
215 r.extend(range(255,128,-4))
218 g.extend(self.const_list(0,32))
219 g.extend(range(0,255,4))
220 g.extend(self.const_list(255,64))
221 g.extend(range(255,0,-4))
222 g.extend(self.const_list(0,32))
225 b.extend(self.const_list(255,64))
226 b.extend(range(255,0,-4))
227 b.extend(self.const_list(0,96))
231 (r,g,b) = self.make_colormap()
233 for i in range(0,256):
234 colour = wx.Colour(r[i], g[i], b[i])
235 pens.append( wx.Pen(colour, 2, wx.SOLID))
238 def OnPaint(self, event):
239 dc = wx.PaintDC(self)
242 def DoDrawing(self, dc=None):
244 dc = wx.ClientDC(self)
245 dc.DrawBitmap(self.bm, 0, 0, False )
248 def const_list(self,const,len):
250 for i in range(1,len):
255 def set_data (self, evt):
260 dc1.SelectObject(self.bm)
261 dc1.Blit(0,1,self.fftsink.fft_size,300,dc1,0,0,wx.COPY,False,-1,-1)
263 x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq))
275 if self.fftsink.input_is_real: # only plot 1/2 the points
282 scale_factor = self.scale_factor
283 if self.fftsink.input_is_real: # real fft
284 for x_pos in range(0, d_max):
285 value = int(dB[x_pos] * scale_factor)
286 value = min(255, max(0, value))
287 dc1.SetPen(self.pens[value])
288 dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2)
290 for x_pos in range(0, d_max): # positive freqs
291 value = int(dB[x_pos] * scale_factor)
292 value = min(255, max(0, value))
293 dc1.SetPen(self.pens[value])
294 dc1.DrawRectangle(x_pos*p_width + d_max, 0, p_width, 2)
295 for x_pos in range(0 , d_max): # negative freqs
296 value = int(dB[x_pos+d_max] * scale_factor)
297 value = min(255, max(0, value))
298 dc1.SetPen(self.pens[value])
299 dc1.DrawRectangle(x_pos*p_width, 0, p_width, 2)
302 self.DoDrawing (None)
304 def on_average(self, evt):
306 self.fftsink.set_average(evt.IsChecked())
308 def on_right_click(self, event):
309 menu = self.popup_menu
310 for id, pred in self.checkmarks.items():
311 item = menu.FindItemById(id)
313 self.PopupMenu(menu, event.GetPosition())
316 def build_popup_menu(self):
317 self.id_incr_ref_level = wx.NewId()
318 self.id_decr_ref_level = wx.NewId()
319 self.id_incr_y_per_div = wx.NewId()
320 self.id_decr_y_per_div = wx.NewId()
321 self.id_y_per_div_1 = wx.NewId()
322 self.id_y_per_div_2 = wx.NewId()
323 self.id_y_per_div_5 = wx.NewId()
324 self.id_y_per_div_10 = wx.NewId()
325 self.id_y_per_div_20 = wx.NewId()
326 self.id_average = wx.NewId()
328 self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average)
329 #self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level)
330 #self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level)
331 #self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div)
332 #self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div)
333 #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1)
334 #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2)
335 #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5)
336 #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10)
337 #self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20)
342 self.popup_menu = menu
343 menu.AppendCheckItem(self.id_average, "Average")
344 # menu.Append(self.id_incr_ref_level, "Incr Ref Level")
345 # menu.Append(self.id_decr_ref_level, "Decr Ref Level")
346 # menu.Append(self.id_incr_y_per_div, "Incr dB/div")
347 # menu.Append(self.id_decr_y_per_div, "Decr dB/div")
348 # menu.AppendSeparator()
349 # we'd use RadioItems for these, but they're not supported on Mac
350 #menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div")
351 #menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div")
352 #menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div")
353 #menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div")
354 #menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div")
357 self.id_average : lambda : self.fftsink.average
358 #self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1,
359 #self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2,
360 #self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5,
361 #self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10,
362 #self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20,
368 Return the first item in seq that is > v.
375 def next_down(v, seq):
377 Return the last item in seq that is < v.
388 # ----------------------------------------------------------------
389 # Standalone test app
390 # ----------------------------------------------------------------
392 class test_top_block (stdgui2.std_top_block):
393 def __init__(self, frame, panel, vbox, argv):
394 stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)
398 # build our flow graph
399 input_rate = 20.000e3
401 # Generate a complex sinusoid
402 self.src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
403 #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000)
405 # We add these throttle blocks so that this demo doesn't
406 # suck down all the CPU available. Normally you wouldn't use these.
407 self.thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate)
409 sink1 = waterfall_sink_c (panel, title="Complex Data", fft_size=fft_size,
410 sample_rate=input_rate, baseband_freq=100e3)
411 self.connect(self.src1, self.thr1, sink1)
412 vbox.Add (sink1.win, 1, wx.EXPAND)
414 # generate a real sinusoid
415 self.src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
416 self.thr2 = gr.throttle(gr.sizeof_float, input_rate)
417 sink2 = waterfall_sink_f (panel, title="Real Data", fft_size=fft_size,
418 sample_rate=input_rate, baseband_freq=100e3)
419 self.connect(self.src2, self.thr2, sink2)
420 vbox.Add (sink2.win, 1, wx.EXPAND)
424 app = stdgui2.stdapp (test_top_block, "Waterfall Sink Test App")
427 if __name__ == '__main__':