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