0854d66685bbc8a3edf17e5af9e1596e0cbfe83b
[debian/gnuradio] / gr-wxgui / src / python / fftsink_nongl.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2003,2004,2005,2006,2007,2009 Free Software Foundation, Inc.
4
5 # This file is part of GNU Radio
6
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)
10 # any later version.
11
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.
16
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.
21
22
23 from gnuradio import gr, gru, window
24 from gnuradio.wxgui import stdgui2
25 import wx
26 import plot
27 import numpy
28 import math    
29
30 DIV_LEVELS = (1, 2, 5, 10, 20)
31
32 default_fftsink_size = (640,240)
33 default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15)
34
35 class fft_sink_base(object):
36     def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, 
37                  y_divs=8, ref_level=50,
38                  sample_rate=1, fft_size=512,
39                  fft_rate=default_fft_rate,
40                  average=False, avg_alpha=None, title='', peak_hold=False,use_persistence=False,persist_alpha=0.2):
41
42         # initialize common attributes
43         self.baseband_freq = baseband_freq
44         self.y_per_div=y_per_div
45         self.y_divs = y_divs
46         self.ref_level = ref_level
47         self.sample_rate = sample_rate
48         self.fft_size = fft_size
49         self.fft_rate = fft_rate
50         self.average = average
51         if avg_alpha is None:
52             self.avg_alpha = 2.0 / fft_rate
53         else:
54             self.avg_alpha = avg_alpha
55         self.use_persistence = use_persistence
56         self.persist_alpha = persist_alpha
57
58         self.title = title
59         self.peak_hold = peak_hold
60         self.input_is_real = input_is_real
61         self.msgq = gr.msg_queue(2)         # queue that holds a maximum of 2 messages
62
63     def set_y_per_div(self, y_per_div):
64         self.y_per_div = y_per_div
65
66     def set_ref_level(self, ref_level):
67         self.ref_level = ref_level
68
69     def set_average(self, average):
70         self.average = average
71         if average:
72             self.avg.set_taps(self.avg_alpha)
73         else:
74             self.avg.set_taps(1.0)
75         self.win.peak_vals = None
76         
77     def set_peak_hold(self, enable):
78         self.peak_hold = enable
79         self.win.set_peak_hold(enable)
80
81     def set_use_persistence(self, enable):
82         self.use_persistence = enable
83         self.win.set_use_persistence(enable)
84
85     def set_persist_alpha(self, persist_alpha):
86         self.persist_alpha = persist_alpha
87         self.win.set_persist_alpha(persist_alpha)
88
89     def set_avg_alpha(self, avg_alpha):
90         self.avg_alpha = avg_alpha
91
92     def set_baseband_freq(self, baseband_freq):
93         self.baseband_freq = baseband_freq
94
95     def set_sample_rate(self, sample_rate):
96         self.sample_rate = sample_rate
97         self._set_n()
98
99     def _set_n(self):
100         self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
101         
102
103 class fft_sink_f(gr.hier_block2, fft_sink_base):
104     def __init__(self, parent, baseband_freq=0, ref_scale=2.0,
105                  y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512,
106                  fft_rate=default_fft_rate, average=False, avg_alpha=None,
107                  title='', size=default_fftsink_size, peak_hold=False, use_persistence=False,persist_alpha=0.2, **kwargs):
108
109         gr.hier_block2.__init__(self, "fft_sink_f",
110                                 gr.io_signature(1, 1, gr.sizeof_float),
111                                 gr.io_signature(0,0,0))
112
113         fft_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq,
114                                y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level,
115                                sample_rate=sample_rate, fft_size=fft_size,
116                                fft_rate=fft_rate,
117                                average=average, avg_alpha=avg_alpha, title=title,
118                                peak_hold=peak_hold,use_persistence=use_persistence,persist_alpha=persist_alpha)
119                                
120         self.s2p = gr.stream_to_vector(gr.sizeof_float, self.fft_size)
121         self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size,
122                                          max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
123         
124         mywindow = window.blackmanharris(self.fft_size)
125         self.fft = gr.fft_vfc(self.fft_size, True, mywindow)
126         power = 0
127         for tap in mywindow:
128             power += tap*tap
129             
130         self.c2mag = gr.complex_to_mag(self.fft_size)
131         self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size)
132
133         # FIXME  We need to add 3dB to all bins but the DC bin
134         self.log = gr.nlog10_ff(20, self.fft_size,
135                                -10*math.log10(self.fft_size)                # Adjust for number of bins
136                                -10*math.log10(power/self.fft_size)        # Adjust for windowing loss
137                                -20*math.log10(ref_scale/2))                # Adjust for reference scale
138                                
139         self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)
140         self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink)
141
142         self.win = fft_window(self, parent, size=size)
143         self.set_average(self.average)
144         self.set_peak_hold(self.peak_hold)
145         self.set_use_persistence(self.use_persistence)
146         self.set_persist_alpha(self.persist_alpha)
147
148 class fft_sink_c(gr.hier_block2, fft_sink_base):
149     def __init__(self, parent, baseband_freq=0, ref_scale=2.0,
150                  y_per_div=10, y_divs=8, ref_level=50, sample_rate=1, fft_size=512,
151                  fft_rate=default_fft_rate, average=False, avg_alpha=None,
152                  title='', size=default_fftsink_size, peak_hold=False, use_persistence=False,persist_alpha=0.2, **kwargs):
153
154         gr.hier_block2.__init__(self, "fft_sink_c",
155                                 gr.io_signature(1, 1, gr.sizeof_gr_complex),
156                                 gr.io_signature(0,0,0))
157
158         fft_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq,
159                                y_per_div=y_per_div, y_divs=y_divs, ref_level=ref_level,
160                                sample_rate=sample_rate, fft_size=fft_size,
161                                fft_rate=fft_rate,
162                                average=average, avg_alpha=avg_alpha, title=title,
163                                peak_hold=peak_hold, use_persistence=use_persistence,persist_alpha=persist_alpha)
164
165         self.s2p = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size)
166         self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size,
167                                          max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
168         
169         mywindow = window.blackmanharris(self.fft_size)
170         self.fft = gr.fft_vcc(self.fft_size, True, mywindow)
171         power = 0
172         for tap in mywindow:
173             power += tap*tap
174             
175         self.c2mag = gr.complex_to_mag(self.fft_size)
176         self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size)
177
178         # FIXME  We need to add 3dB to all bins but the DC bin
179         self.log = gr.nlog10_ff(20, self.fft_size,
180                                 -10*math.log10(self.fft_size)                # Adjust for number of bins
181                                 -10*math.log10(power/self.fft_size)        # Adjust for windowing loss
182                                 -20*math.log10(ref_scale/2))                # Adjust for reference scale
183                                 
184         self.sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)
185         self.connect(self, self.s2p, self.one_in_n, self.fft, self.c2mag, self.avg, self.log, self.sink)
186
187         self.win = fft_window(self, parent, size=size)
188         self.set_average(self.average)
189         self.set_use_persistence(self.use_persistence)
190         self.set_persist_alpha(self.persist_alpha)
191         self.set_peak_hold(self.peak_hold)
192
193
194 # ------------------------------------------------------------------------
195
196 myDATA_EVENT = wx.NewEventType()
197 EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0)
198
199
200 class DataEvent(wx.PyEvent):
201     def __init__(self, data):
202         wx.PyEvent.__init__(self)
203         self.SetEventType (myDATA_EVENT)
204         self.data = data
205
206     def Clone (self): 
207         self.__class__ (self.GetId())
208
209
210 class input_watcher (gru.msgq_runner):
211     def __init__ (self, msgq, fft_size, event_receiver, **kwds):
212         self.fft_size = fft_size
213         self.event_receiver = event_receiver
214         gru.msgq_runner.__init__(self, msgq, self.handle_msg)
215
216     def handle_msg(self, msg):
217         itemsize = int(msg.arg1())
218         nitems = int(msg.arg2())
219
220         s = msg.to_string() # get the body of the msg as a string
221
222         # There may be more than one FFT frame in the message.
223         # If so, we take only the last one
224         if nitems > 1:
225             start = itemsize * (nitems - 1)
226             s = s[start:start+itemsize]
227
228         complex_data = numpy.fromstring (s, numpy.float32)
229         de = DataEvent (complex_data)
230         wx.PostEvent (self.event_receiver, de)
231         del de
232
233 class control_panel(wx.Panel):
234     
235     class LabelText(wx.StaticText):    
236         def __init__(self, window, label):
237             wx.StaticText.__init__(self, window, -1, label)
238             font = self.GetFont()
239             font.SetWeight(wx.FONTWEIGHT_BOLD)
240             font.SetUnderlined(True)
241             self.SetFont(font)
242     
243     def __init__(self, parent):
244         self.parent = parent
245         wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)    
246         control_box = wx.BoxSizer(wx.VERTICAL)
247         
248         #checkboxes for average and peak hold
249         control_box.AddStretchSpacer()
250         control_box.Add(self.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER)
251         self.average_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Average")
252         self.average_check_box.Bind(wx.EVT_CHECKBOX, parent.on_average)
253         control_box.Add(self.average_check_box, 0, wx.EXPAND)
254         self.use_persistence_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Persistence")
255         self.use_persistence_check_box.Bind(wx.EVT_CHECKBOX, parent.on_use_persistence)
256         control_box.Add(self.use_persistence_check_box, 0, wx.EXPAND)
257         self.peak_hold_check_box = wx.CheckBox(parent=self, style=wx.CHK_2STATE, label="Peak Hold")
258         self.peak_hold_check_box.Bind(wx.EVT_CHECKBOX, parent.on_peak_hold) 
259         control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND)
260        
261         #radio buttons for div size
262         control_box.AddStretchSpacer()
263         control_box.Add(self.LabelText(self, 'Set dB/div'), 0, wx.ALIGN_CENTER)
264         radio_box = wx.BoxSizer(wx.VERTICAL)
265         self.radio_buttons = list()
266         for y_per_div in DIV_LEVELS:
267             radio_button = wx.RadioButton(self, -1, "%d dB/div"%y_per_div)
268             radio_button.Bind(wx.EVT_RADIOBUTTON, self.on_radio_button_change)
269             self.radio_buttons.append(radio_button)
270             radio_box.Add(radio_button, 0, wx.ALIGN_LEFT)
271         control_box.Add(radio_box, 0, wx.EXPAND)
272         
273         #ref lvl buttons
274         control_box.AddStretchSpacer()
275         control_box.Add(self.LabelText(self, 'Adj Ref Lvl'), 0, wx.ALIGN_CENTER)
276         control_box.AddSpacer(2)
277         button_box = wx.BoxSizer(wx.HORIZONTAL)        
278         self.ref_plus_button = wx.Button(self, -1, '+', style=wx.BU_EXACTFIT)
279         self.ref_plus_button.Bind(wx.EVT_BUTTON, parent.on_incr_ref_level)
280         button_box.Add(self.ref_plus_button, 0, wx.ALIGN_CENTER)
281         self.ref_minus_button = wx.Button(self, -1, ' - ', style=wx.BU_EXACTFIT)
282         self.ref_minus_button.Bind(wx.EVT_BUTTON, parent.on_decr_ref_level)
283         button_box.Add(self.ref_minus_button, 0, wx.ALIGN_CENTER)
284         control_box.Add(button_box, 0, wx.ALIGN_CENTER)
285         control_box.AddStretchSpacer()
286         #set sizer
287         self.SetSizerAndFit(control_box)
288         #update
289         self.update()
290         
291     def update(self):
292         """
293         Read the state of the fft plot settings and update the control panel.
294         """
295         #update checkboxes
296         self.average_check_box.SetValue(self.parent.fftsink.average)
297         self.use_persistence_check_box.SetValue(self.parent.fftsink.use_persistence)
298         self.peak_hold_check_box.SetValue(self.parent.fftsink.peak_hold)
299         #update radio buttons    
300         try:
301             index = list(DIV_LEVELS).index(self.parent.fftsink.y_per_div)
302             self.radio_buttons[index].SetValue(True)
303         except: pass
304         
305     def on_radio_button_change(self, evt):
306         selected_radio_button = filter(lambda rb: rb.GetValue(), self.radio_buttons)[0] 
307         index = self.radio_buttons.index(selected_radio_button)
308         self.parent.fftsink.set_y_per_div(DIV_LEVELS[index])
309
310 class fft_window (wx.Panel):
311     def __init__ (self, fftsink, parent, id = -1,
312                   pos = wx.DefaultPosition, size = wx.DefaultSize,
313                   style = wx.DEFAULT_FRAME_STYLE, name = ""):
314         
315         self.fftsink = fftsink
316         #init panel and plot 
317         wx.Panel.__init__(self, parent, -1)                  
318         self.plot = plot.PlotCanvas(self, id, pos, size, style, name)       
319         #setup the box with plot and controls
320         self.control_panel = control_panel(self)
321         main_box = wx.BoxSizer (wx.HORIZONTAL)
322         main_box.Add (self.plot, 1, wx.EXPAND)
323         main_box.Add (self.control_panel, 0, wx.EXPAND)
324         self.SetSizerAndFit(main_box)
325         
326         self.peak_hold = False
327         self.peak_vals = None
328
329         self.use_persistence=False
330         self.persist_alpha=0.2
331
332         
333         self.plot.SetEnableGrid (True)
334         # self.SetEnableZoom (True)
335         # self.SetBackgroundColour ('black')
336         
337         self.build_popup_menu()
338         self.set_baseband_freq(self.fftsink.baseband_freq)
339                 
340         EVT_DATA_EVENT (self, self.set_data)
341         wx.EVT_CLOSE (self, self.on_close_window)
342         self.plot.Bind(wx.EVT_RIGHT_UP, self.on_right_click)
343         self.plot.Bind(wx.EVT_MOTION, self.evt_motion)
344         
345         self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self)
346
347     def set_scale(self, freq):
348         x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq))        
349         if x >= 1e9:
350             self._scale_factor = 1e-9
351             self._units = "GHz"
352             self._format = "%3.6f"
353         elif x >= 1e6:
354             self._scale_factor = 1e-6
355             self._units = "MHz"
356             self._format = "%3.3f"
357         else:
358             self._scale_factor = 1e-3
359             self._units = "kHz"
360             self._format = "%3.3f"
361
362     def set_baseband_freq(self, baseband_freq):
363         if self.peak_hold:
364             self.peak_vals = None
365         self.set_scale(baseband_freq)
366         self.fftsink.set_baseband_freq(baseband_freq)
367         
368     def on_close_window (self, event):
369         print "fft_window:on_close_window"
370         self.keep_running = False
371
372
373     def set_data (self, evt):
374         dB = evt.data
375         L = len (dB)
376
377         if self.peak_hold:
378             if self.peak_vals is None:
379                 self.peak_vals = dB
380             else:
381                 self.peak_vals = numpy.maximum(dB, self.peak_vals)
382
383         if self.fftsink.input_is_real:     # only plot 1/2 the points
384             x_vals = ((numpy.arange (L/2) * (self.fftsink.sample_rate 
385                        * self._scale_factor / L))
386                       + self.fftsink.baseband_freq * self._scale_factor)
387             self._points = numpy.zeros((len(x_vals), 2), numpy.float64)
388             self._points[:,0] = x_vals
389             self._points[:,1] = dB[0:L/2]
390             if self.peak_hold:
391                 self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64)
392                 self._peak_points[:,0] = x_vals
393                 self._peak_points[:,1] = self.peak_vals[0:L/2]
394         else:
395             # the "negative freqs" are in the second half of the array
396             x_vals = ((numpy.arange (-L/2, L/2)
397                        * (self.fftsink.sample_rate * self._scale_factor / L))
398                       + self.fftsink.baseband_freq * self._scale_factor)
399             self._points = numpy.zeros((len(x_vals), 2), numpy.float64)
400             self._points[:,0] = x_vals
401             self._points[:,1] = numpy.concatenate ((dB[L/2:], dB[0:L/2]))
402             if self.peak_hold:
403                 self._peak_points = numpy.zeros((len(x_vals), 2), numpy.float64)
404                 self._peak_points[:,0] = x_vals
405                 self._peak_points[:,1] = numpy.concatenate ((self.peak_vals[L/2:], self.peak_vals[0:L/2]))
406
407         lines = [plot.PolyLine (self._points, colour='BLUE'),]
408         if self.peak_hold:
409             lines.append(plot.PolyLine (self._peak_points, colour='GREEN'))
410
411         graphics = plot.PlotGraphics (lines,
412                                       title=self.fftsink.title,
413                                       xLabel = self._units, yLabel = "dB")
414         x_range = x_vals[0], x_vals[-1]
415         ymax = self.fftsink.ref_level
416         ymin = self.fftsink.ref_level - self.fftsink.y_per_div * self.fftsink.y_divs
417         y_range = ymin, ymax
418         self.plot.Draw (graphics, xAxis=x_range, yAxis=y_range, step=self.fftsink.y_per_div)        
419
420     def set_use_persistence(self, enable):
421         self.use_persistence = enable
422         self.plot.set_use_persistence( enable)
423
424     def set_persist_alpha(self, persist_alpha):
425         self.persist_alpha = persist_alpha
426         self.plot.set_persist_alpha(persist_alpha)
427
428     def set_peak_hold(self, enable):
429         self.peak_hold = enable
430         self.peak_vals = None
431
432     def on_average(self, evt):
433         # print "on_average"
434         self.fftsink.set_average(evt.IsChecked())
435         self.control_panel.update()
436
437     def on_use_persistence(self, evt):
438         # print "on_analog"
439         self.fftsink.set_use_persistence(evt.IsChecked())
440         self.control_panel.update()
441
442     def on_peak_hold(self, evt):
443         # print "on_peak_hold"
444         self.fftsink.set_peak_hold(evt.IsChecked())
445         self.control_panel.update()
446
447     def on_incr_ref_level(self, evt):
448         # print "on_incr_ref_level"
449         self.fftsink.set_ref_level(self.fftsink.ref_level
450                                    + self.fftsink.y_per_div)
451
452     def on_decr_ref_level(self, evt):
453         # print "on_decr_ref_level"
454         self.fftsink.set_ref_level(self.fftsink.ref_level
455                                    - self.fftsink.y_per_div)
456
457     def on_incr_y_per_div(self, evt):
458         # print "on_incr_y_per_div"
459         self.fftsink.set_y_per_div(next_up(self.fftsink.y_per_div, DIV_LEVELS))
460         self.control_panel.update()
461
462     def on_decr_y_per_div(self, evt):
463         # print "on_decr_y_per_div"
464         self.fftsink.set_y_per_div(next_down(self.fftsink.y_per_div, DIV_LEVELS))
465         self.control_panel.update()
466
467     def on_y_per_div(self, evt):
468         # print "on_y_per_div"
469         Id = evt.GetId()
470         if Id == self.id_y_per_div_1:
471             self.fftsink.set_y_per_div(1)
472         elif Id == self.id_y_per_div_2:
473             self.fftsink.set_y_per_div(2)
474         elif Id == self.id_y_per_div_5:
475             self.fftsink.set_y_per_div(5)
476         elif Id == self.id_y_per_div_10:
477             self.fftsink.set_y_per_div(10)
478         elif Id == self.id_y_per_div_20:
479             self.fftsink.set_y_per_div(20)
480         self.control_panel.update()
481
482     def on_right_click(self, event):
483         menu = self.popup_menu
484         for id, pred in self.checkmarks.items():
485             item = menu.FindItemById(id)
486             item.Check(pred())
487         self.plot.PopupMenu(menu, event.GetPosition())
488
489     def evt_motion(self, event):
490         if not hasattr(self, "_points"):
491             return # Got here before first window data update
492             
493         # Clip to plotted values
494         (ux, uy) = self.plot.GetXY(event)      # Scaled position
495         x_vals = numpy.array(self._points[:,0])
496         if ux < x_vals[0] or ux > x_vals[-1]:
497             tip = self.GetToolTip()
498             if tip:
499                 tip.Enable(False)
500             return
501
502         # Get nearest X value (is there a better way)?
503         ind = numpy.argmin(numpy.abs(x_vals-ux))
504         x_val = x_vals[ind]
505         db_val = self._points[ind, 1]
506         text = (self._format+" %s dB=%3.3f") % (x_val, self._units, db_val)
507
508         # Display the tooltip
509         tip = wx.ToolTip(text)
510         tip.Enable(True)
511         tip.SetDelay(0)
512         self.SetToolTip(tip)
513         
514     def build_popup_menu(self):
515         self.id_incr_ref_level = wx.NewId()
516         self.id_decr_ref_level = wx.NewId()
517         self.id_incr_y_per_div = wx.NewId()
518         self.id_decr_y_per_div = wx.NewId()
519         self.id_y_per_div_1 = wx.NewId()
520         self.id_y_per_div_2 = wx.NewId()
521         self.id_y_per_div_5 = wx.NewId()
522         self.id_y_per_div_10 = wx.NewId()
523         self.id_y_per_div_20 = wx.NewId()
524         self.id_average = wx.NewId()
525         self.id_use_persistence = wx.NewId()
526         self.id_peak_hold = wx.NewId()
527         
528         self.plot.Bind(wx.EVT_MENU, self.on_average, id=self.id_average)
529         self.plot.Bind(wx.EVT_MENU, self.on_use_persistence, id=self.id_use_persistence)
530         self.plot.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold)
531         self.plot.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level)
532         self.plot.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level)
533         self.plot.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div)
534         self.plot.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div)
535         self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1)
536         self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2)
537         self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5)
538         self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10)
539         self.plot.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20)
540         
541         # make a menu
542         menu = wx.Menu()
543         self.popup_menu = menu
544         menu.AppendCheckItem(self.id_average, "Average")
545         menu.AppendCheckItem(self.id_use_persistence, "Persistence")
546         menu.AppendCheckItem(self.id_peak_hold, "Peak Hold")
547         menu.Append(self.id_incr_ref_level, "Incr Ref Level")
548         menu.Append(self.id_decr_ref_level, "Decr Ref Level")
549         # menu.Append(self.id_incr_y_per_div, "Incr dB/div")
550         # menu.Append(self.id_decr_y_per_div, "Decr dB/div")
551         menu.AppendSeparator()
552         # we'd use RadioItems for these, but they're not supported on Mac
553         menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div")
554         menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div")
555         menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div")
556         menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div")
557         menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div")
558
559         self.checkmarks = {
560             self.id_average : lambda : self.fftsink.average,
561             self.id_use_persistence : lambda : self.fftsink.use_persistence,
562             self.id_peak_hold : lambda : self.fftsink.peak_hold,
563             self.id_y_per_div_1 : lambda : self.fftsink.y_per_div == 1,
564             self.id_y_per_div_2 : lambda : self.fftsink.y_per_div == 2,
565             self.id_y_per_div_5 : lambda : self.fftsink.y_per_div == 5,
566             self.id_y_per_div_10 : lambda : self.fftsink.y_per_div == 10,
567             self.id_y_per_div_20 : lambda : self.fftsink.y_per_div == 20,
568             }
569
570
571 def next_up(v, seq):
572     """
573     Return the first item in seq that is > v.
574     """
575     for s in seq:
576         if s > v:
577             return s
578     return v
579
580 def next_down(v, seq):
581     """
582     Return the last item in seq that is < v.
583     """
584     rseq = list(seq[:])
585     rseq.reverse()
586
587     for s in rseq:
588         if s < v:
589             return s
590     return v
591
592
593 # ----------------------------------------------------------------
594 # Standalone test app
595 # ----------------------------------------------------------------
596
597 class test_app_block (stdgui2.std_top_block):
598     def __init__(self, frame, panel, vbox, argv):
599         stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)
600
601         fft_size = 256
602
603         # build our flow graph
604         input_rate = 100*20.48e3
605
606         # Generate a complex sinusoid
607         #src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1)
608         src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1)
609
610         # We add these throttle blocks so that this demo doesn't
611         # suck down all the CPU available.  Normally you wouldn't use these.
612         thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate)
613
614         sink1 = fft_sink_c (panel, title="Complex Data", fft_size=fft_size,
615                             sample_rate=input_rate, baseband_freq=100e3,
616                             ref_level=0, y_per_div=20, y_divs=10)
617         vbox.Add (sink1.win, 1, wx.EXPAND)
618
619         self.connect(src1, thr1, sink1)
620
621         #src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 100*2e3, 1)
622         src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 100*5.75e3, 1)
623         thr2 = gr.throttle(gr.sizeof_float, input_rate)
624         sink2 = fft_sink_f (panel, title="Real Data", fft_size=fft_size*2,
625                             sample_rate=input_rate, baseband_freq=100e3,
626                             ref_level=0, y_per_div=20, y_divs=10)
627         vbox.Add (sink2.win, 1, wx.EXPAND)
628
629         self.connect(src2, thr2, sink2)
630
631 def main ():
632     app = stdgui2.stdapp (test_app_block, "FFT Sink Test App")
633     app.MainLoop ()
634
635 if __name__ == '__main__':
636     main ()