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