Updated to use Dave Wards code.
[debian/gnuradio] / gr-radio-astronomy / src / python / ra_waterfallsink.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2003,2004,2005 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 2, 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 stdgui
25 import wx
26 import gnuradio.wxgui.plot as plot
27 import numpy
28 import os
29 import threading
30 import math    
31
32 default_fftsink_size = (640,240)
33 default_fft_rate = gr.prefs().get_long('wxgui', 'fft_rate', 15)
34
35 def axis_design( x1, x2, nx ):
36     # Given start, end, and number of labels, return value of first label,
37     # increment between labels, number of unlabeled division between labels,
38     # and scale factor.
39
40     dx = abs( x2 - x1 )/float(nx+1)  # allow for space at each end
41     ldx = math.log10(dx)
42     l2 = math.log10(2.)
43     l5 = math.log10(5.)
44     le = math.floor(ldx)
45     lf = ldx - le
46     if lf < l2/2:
47         c = 1
48         dt = 10
49     elif lf < (l2+l5)/2:
50         c = 2
51         dt = 4
52     elif lf < (l5+1)/2:
53         c = 5
54         dt = 5
55     else:
56         c = 1
57         dt = 10
58         le += 1
59     inc = c*pow( 10., le )
60     first = math.ceil( x1/inc )*inc
61     scale = 1.
62     while ( abs(x1*scale) >= 1e5 ) or ( abs(x2*scale) >= 1e5 ):
63         scale *= 1e-3
64     return ( first, inc, dt, scale )
65     
66
67 class waterfall_sink_base(object):
68     def __init__(self, input_is_real=False, baseband_freq=0,
69                  sample_rate=1, fft_size=512,
70                  fft_rate=default_fft_rate,
71                  average=False, avg_alpha=None, title='', ofunc=None, xydfunc=None):
72
73         # initialize common attributes
74         self.baseband_freq = baseband_freq
75         self.sample_rate = sample_rate
76         self.fft_size = fft_size
77         self.fft_rate = fft_rate
78         self.average = average
79         self.ofunc = ofunc
80         self.xydfunc = xydfunc
81         if avg_alpha is None:
82             self.avg_alpha = 2.0 / fft_rate
83         else:
84             self.avg_alpha = avg_alpha
85         self.title = title
86         self.input_is_real = input_is_real
87         self.msgq = gr.msg_queue(2)         # queue up to 2 messages
88
89     def reconnect( self, fg ):
90         fg.connect( *self.block_list )
91
92     def set_average(self, average):
93         self.average = average
94         if average:
95             self.avg.set_taps(self.avg_alpha)
96         else:
97             self.avg.set_taps(1.0)
98
99     def set_avg_alpha(self, avg_alpha):
100         self.avg_alpha = avg_alpha
101
102     def set_baseband_freq(self, baseband_freq):
103         self.baseband_freq = baseband_freq
104
105     def set_sample_rate(self, sample_rate):
106         self.sample_rate = sample_rate
107         self._set_n()
108
109     def _set_n(self):
110         self.one_in_n.set_n(max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
111         
112 class waterfall_sink_f(gr.hier_block, waterfall_sink_base):
113     def __init__(self, fg, parent, baseband_freq=0,
114                  ref_level=0, sample_rate=1, fft_size=512,
115                  fft_rate=default_fft_rate, average=False, avg_alpha=None,
116                  title='', size=default_fftsink_size, report=None, span=40, ofunc=None, xydfunc=None):
117
118         waterfall_sink_base.__init__(self, input_is_real=True,
119                                      baseband_freq=baseband_freq,
120                                      sample_rate=sample_rate,
121                                      fft_size=fft_size, fft_rate=fft_rate,
122                                      average=average, avg_alpha=avg_alpha,
123                                      title=title)
124                                
125         s2p = gr.serial_to_parallel(gr.sizeof_float, self.fft_size)
126         self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fft_size,
127                                          max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
128         mywindow = window.blackmanharris(self.fft_size)
129         fft = gr.fft_vfc(self.fft_size, True, mywindow)
130         c2mag = gr.complex_to_mag(self.fft_size)
131         self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size)
132         log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))
133         sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)
134
135         self.block_list = (s2p, self.one_in_n, fft, c2mag, self.avg, log, sink)
136         self.reconnect( fg )
137         gr.hier_block.__init__(self, fg, s2p, sink)
138
139         self.win = waterfall_window(self, parent, size=size, report=report,
140                                     ref_level=ref_level, span=span, ofunc=ofunc, xydfunc=xydfunc)
141         self.set_average(self.average)
142
143
144 class waterfall_sink_c(gr.hier_block, waterfall_sink_base):
145     def __init__(self, fg, parent, baseband_freq=0,
146                  ref_level=0, sample_rate=1, fft_size=512,
147                  fft_rate=default_fft_rate, average=False, avg_alpha=None, 
148                  title='', size=default_fftsink_size, report=None, span=40, ofunc=None, xydfunc=None):
149
150         waterfall_sink_base.__init__(self, input_is_real=False,
151                                      baseband_freq=baseband_freq,
152                                      sample_rate=sample_rate,
153                                      fft_size=fft_size,
154                                      fft_rate=fft_rate,
155                                      average=average, avg_alpha=avg_alpha,
156                                      title=title)
157
158         s2p = gr.serial_to_parallel(gr.sizeof_gr_complex, self.fft_size)
159         self.one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex * self.fft_size,
160                                          max(1, int(self.sample_rate/self.fft_size/self.fft_rate)))
161
162         mywindow = window.blackmanharris(self.fft_size)
163         fft = gr.fft_vcc(self.fft_size, True, mywindow)
164         c2mag = gr.complex_to_mag(self.fft_size)
165         self.avg = gr.single_pole_iir_filter_ff(1.0, self.fft_size)
166         log = gr.nlog10_ff(20, self.fft_size, -20*math.log10(self.fft_size))
167         sink = gr.message_sink(gr.sizeof_float * self.fft_size, self.msgq, True)
168
169         self.block_list = (s2p, self.one_in_n, fft, c2mag, self.avg, log, sink)
170         self.reconnect( fg )
171         gr.hier_block.__init__(self, fg, s2p, sink)
172
173         self.win = waterfall_window(self, parent, size=size, report=report,
174                                     ref_level=ref_level, span=span, ofunc=ofunc, xydfunc=xydfunc)
175         self.set_average(self.average)
176
177
178 # ------------------------------------------------------------------------
179
180 myDATA_EVENT = wx.NewEventType()
181 EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0)
182
183
184 class DataEvent(wx.PyEvent):
185     def __init__(self, data):
186         wx.PyEvent.__init__(self)
187         self.SetEventType (myDATA_EVENT)
188         self.data = data
189
190     def Clone (self): 
191         self.__class__ (self.GetId())
192
193
194 class input_watcher (threading.Thread):
195     def __init__ (self, msgq, fft_size, event_receiver, **kwds):
196         threading.Thread.__init__ (self, **kwds)
197         self.setDaemon (1)
198         self.msgq = msgq
199         self.fft_size = fft_size
200         self.event_receiver = event_receiver
201         self.keep_running = True
202         self.start ()
203
204     def run (self):
205         while (self.keep_running):
206             msg = self.msgq.delete_head()  # blocking read of message queue
207             itemsize = int(msg.arg1())
208             nitems = int(msg.arg2())
209
210             s = msg.to_string()            # get the body of the msg as a string
211
212             # There may be more than one FFT frame in the message.
213             # If so, we take only the last one
214             if nitems > 1:
215                 start = itemsize * (nitems - 1)
216                 s = s[start:start+itemsize]
217
218             complex_data = numpy.fromstring (s, numpy.float32)
219             de = DataEvent (complex_data)
220             wx.PostEvent (self.event_receiver, de)
221             del de
222     
223
224 class waterfall_window (wx.ScrolledWindow):
225     def __init__ (self, fftsink, parent, id = -1,
226                   pos = wx.DefaultPosition, size = wx.DefaultSize,
227                   style = wx.DEFAULT_FRAME_STYLE, name = "", report=None,
228                   ref_level = 0, span = 50, ofunc=None, xydfunc=None):
229         wx.ScrolledWindow.__init__(self, parent, id, pos, size,
230                                    style|wx.HSCROLL, name)
231         self.parent = parent
232         self.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM))
233         self.ref_level = ref_level
234         self.scale_factor = 256./span
235
236         self.ppsh = 128  # pixels per scroll, horizontal
237         self.SetScrollbars( self.ppsh, 0, fftsink.fft_size/self.ppsh, 0 )
238
239         self.fftsink = fftsink
240         self.size = size
241         self.report = report
242         self.ofunc = ofunc
243         self.xydfunc = xydfunc
244
245         dc1 = wx.MemoryDC()
246         dc1.SetFont( wx.SMALL_FONT )
247         self.h_scale = dc1.GetCharHeight() + 3
248         #self.bm_size = ( self.fftsink.fft_size, self.size[1] - self.h_scale )
249         self.im_size = ( self.fftsink.fft_size, self.size[1] - self.h_scale )
250         #self.bm = wx.EmptyBitmap( self.bm_size[0], self.bm_size[1], -1)
251         self.im = wx.EmptyImage( self.im_size[0], self.im_size[1], True )
252         self.im_cur = 0
253
254         self.baseband_freq = None
255
256         self.make_pens()
257
258         wx.EVT_PAINT( self, self.OnPaint )
259         wx.EVT_CLOSE (self, self.on_close_window)
260         #wx.EVT_LEFT_UP(self, self.on_left_up)
261         #wx.EVT_LEFT_DOWN(self, self.on_left_down)
262         EVT_DATA_EVENT (self, self.set_data)
263         
264         self.build_popup_menu()
265         
266         wx.EVT_CLOSE (self, self.on_close_window)
267         self.Bind(wx.EVT_RIGHT_UP, self.on_right_click)
268         self.Bind(wx.EVT_MOTION, self.on_motion)
269
270         self.down_pos = None
271
272         self.input_watcher = input_watcher(fftsink.msgq, fftsink.fft_size, self)
273
274     def on_close_window (self, event):
275         self.keep_running = False
276
277     def on_left_down( self, evt ):
278         self.down_pos = evt.GetPosition()
279         self.down_time = evt.GetTimestamp()
280
281     def on_left_up( self, evt ):
282         if self.down_pos:
283             dt = ( evt.GetTimestamp() - self.down_time )/1000.
284             pph = self.fftsink.fft_size/float(self.fftsink.sample_rate)
285             dx =  evt.GetPosition()[0] - self.down_pos[0]
286             if dx != 0:
287                 rt = pph/dx
288             else:
289                 rt = 0
290             t = 'Down time: %f  Delta f: %f  Period: %f' % ( dt, dx/pph, rt )
291             print t
292             if self.report:
293                 self.report(t)
294
295     def on_motion(self, event):
296         if self.xydfunc:
297             pos = event.GetPosition()
298             self.xydfunc(pos)
299
300
301     def const_list(self,const,len):
302         return [const] * len
303
304     def make_colormap(self):
305         r = []
306         r.extend(self.const_list(0,96))
307         r.extend(range(0,255,4))
308         r.extend(self.const_list(255,64))
309         r.extend(range(255,128,-4))
310         
311         g = []
312         g.extend(self.const_list(0,32))
313         g.extend(range(0,255,4))
314         g.extend(self.const_list(255,64))
315         g.extend(range(255,0,-4))
316         g.extend(self.const_list(0,32))
317         
318         b = range(128,255,4)
319         b.extend(self.const_list(255,64))
320         b.extend(range(255,0,-4))
321         b.extend(self.const_list(0,96))
322         return (r,g,b)
323
324     def make_pens(self):
325         (r,g,b) = self.make_colormap()
326         self.rgb = numpy.transpose( numpy.array( (r,g,b) ).astype(numpy.int8) )
327         
328     def OnPaint(self, event):
329         dc = wx.BufferedPaintDC(self)
330         self.DoDrawing( dc )
331
332     def DoDrawing(self,dc):
333         w, h = self.GetClientSizeTuple()
334         w = min( w, self.fftsink.fft_size )
335         if w <= 0:
336             return
337
338         if dc is None:
339             dc = wx.BufferedDC( wx.ClientDC(self), (w,h) )
340
341         dc.SetBackground( wx.Brush( self.GetBackgroundColour(), wx.SOLID ) )
342         dc.Clear()
343
344         x, y = self.GetViewStart()
345         x *= self.ppsh
346
347         ih = min( h - self.h_scale, self.im_size[1] - self.im_cur )
348         r = wx.Rect( x, self.im_cur, w, ih )
349         bm = wx.BitmapFromImage( self.im.GetSubImage(r) )
350         dc.DrawBitmap( bm, 0, self.h_scale )
351         rem = min( self.im_size[1] - ih, h - ih - self.h_scale )
352         if( rem > 0 ):
353             r = wx.Rect( x, 0, w, rem )
354             bm = wx.BitmapFromImage( self.im.GetSubImage(r) )
355             dc.DrawBitmap( bm, 0, ih + self.h_scale )
356         
357         # Draw axis
358         if self.baseband_freq != self.fftsink.baseband_freq:
359             self.baseband_freq = self.fftsink.baseband_freq
360             t = self.fftsink.sample_rate*w/float(self.fftsink.fft_size)
361             self.ax_spec = axis_design( self.baseband_freq - t/2,
362                                         self.baseband_freq + t/2, 7 )
363         dc.SetFont( wx.SMALL_FONT )
364         fo = self.baseband_freq
365         po = self.fftsink.fft_size/2
366         pph = self.fftsink.fft_size/float(self.fftsink.sample_rate)
367         f = math.floor((fo-po/pph)/self.ax_spec[1])*self.ax_spec[1]
368         while True:
369             t = po + ( f - fo )*pph
370             s = str( f*self.ax_spec[3] )
371             e = dc.GetTextExtent( s )
372             if t - e[1]/2 >= x + w:
373                 break
374             dc.DrawText( s, t - x - e[0]/2, 0 )
375             dc.DrawLine( t - x, e[1] - 1, t - x, self.h_scale )
376             dt = self.ax_spec[1]/self.ax_spec[2]*pph
377             for i in range(self.ax_spec[2]-1):
378                 t += dt
379                 if t >= x + w:
380                     break
381                 dc.DrawLine( t - x, e[1] + 1, t - x, self.h_scale )
382             f += self.ax_spec[1]
383
384     def const_list(self,const,len):
385         a = [const]
386         for i in range(1,len):
387             a.append(const)
388         return a
389
390     def make_colormap(self):
391         r = []
392         r.extend(self.const_list(0,96))
393         r.extend(range(0,255,4))
394         r.extend(self.const_list(255,64))
395         r.extend(range(255,128,-4))
396         
397         g = []
398         g.extend(self.const_list(0,32))
399         g.extend(range(0,255,4))
400         g.extend(self.const_list(255,64))
401         g.extend(range(255,0,-4))
402         g.extend(self.const_list(0,32))
403         
404         b = range(128,255,4)
405         b.extend(self.const_list(255,64))
406         b.extend(range(255,0,-4))
407         b.extend(self.const_list(0,96))
408         return (r,g,b)
409
410     def set_data (self, evt):
411         dB = evt.data
412         L = len (dB)
413
414         if self.ofunc != None:
415             self.ofunc(evt.data, L)
416         #dc1 = wx.MemoryDC()
417         #dc1.SelectObject(self.bm)
418
419         # Scroll existing bitmap
420         if 1:
421             #dc1.Blit(0,1,self.bm_size[0],self.bm_size[1]-1,dc1,0,0,
422             #         wx.COPY,False,-1,-1)
423             pass
424         else:
425             for i in range( self.bm_size[1]-1, 0, -1 ):
426                 dc1.Blit( 0, i, self.bm_size[0], 1, dc1, 0, i-1 )
427
428         x = max(abs(self.fftsink.sample_rate), abs(self.fftsink.baseband_freq))
429         if x >= 1e9:
430             sf = 1e-9
431             units = "GHz"
432         elif x >= 1e6:
433             sf = 1e-6
434             units = "MHz"
435         else:
436             sf = 1e-3
437             units = "kHz"
438
439
440         if self.fftsink.input_is_real:     # only plot 1/2 the points
441             d_max = L/2
442             p_width = 2
443         else:
444             d_max = L/2
445             p_width = 1
446
447         scale_factor = self.scale_factor
448         dB -= self.ref_level
449         dB *= scale_factor
450         dB = dB.astype(numpy.int_).clip( min=0, max=255 )
451         if self.fftsink.input_is_real:     # real fft
452             dB = numpy.array( ( dB[0:d_max][::-1], dB[0:d_max] ) )
453         else:                               # complex fft
454             dB = numpy.concatenate( ( dB[d_max:L], dB[0:d_max] ) )
455
456         dB = self.rgb[dB]
457         img = wx.ImageFromData( L, 1, dB.ravel().tostring() )
458         #bm = wx.BitmapFromImage( img )
459         #dc1.DrawBitmap( bm, 0, 0 )
460         ibuf = self.im.GetDataBuffer()
461         self.im_cur -= 1
462         if self.im_cur < 0:
463             self.im_cur = self.im_size[1] - 1
464         start = 3*self.im_cur*self.im_size[0]
465         ibuf[start:start+3*self.im_size[0]] = img.GetData()
466
467         #del dc1
468         self.DoDrawing(None)
469
470     def on_average(self, evt):
471         # print "on_average"
472         self.fftsink.set_average(evt.IsChecked())
473
474     def on_right_click(self, event):
475         menu = self.popup_menu
476         self.PopupMenu(menu, event.GetPosition())
477
478
479     def build_popup_menu(self):
480         id_ref_gain = wx.NewId()
481         self.Bind( wx.EVT_MENU, self.on_ref_gain, id=id_ref_gain )
482
483         # make a menu
484         menu = wx.Menu()
485         self.popup_menu = menu
486         menu.Append( id_ref_gain, "Ref Level and Gain" )
487         self.rg_dialog = None
488
489         self.checkmarks = {
490             #self.id_average : lambda : self.fftsink.average
491             }
492
493     def on_ref_gain( self, evt ):
494         if self.rg_dialog == None:
495             self.rg_dialog = rg_dialog( self.parent, self.set_ref_gain,
496                                         ref=self.ref_level,
497                                         span=256./self.scale_factor )
498         self.rg_dialog.Show( True )
499
500     def set_ref_gain( self, ref, span ):
501         self.ref_level = ref
502         self.scale_factor = 256/span
503
504 class rg_dialog( wx.Dialog ):
505     def __init__( self, parent, set_function, ref=0, span=256./5. ):
506         wx.Dialog.__init__( self, parent, -1, "Waterfall Settings" )
507         self.set_function = set_function
508         #status_bar = wx.StatusBar( self, -1 )
509
510         d_sizer = wx.BoxSizer( wx.VERTICAL )  # dialog sizer
511         f_sizer = wx.BoxSizer( wx.VERTICAL )  # form sizer
512         vs = 10
513
514         #f_sizer.Add( fn_sizer, 0, flag=wx.TOP, border=10 )
515
516         h_sizer = wx.BoxSizer( wx.HORIZONTAL )
517         self.ref = tab_item( self, "Ref Level:", 4, "dB" )
518         self.ref.ctrl.SetValue( "%d" % ref )
519         h_sizer.Add((0,0),1)
520         h_sizer.Add( self.ref, 0 )
521         h_sizer.Add((0,0),1)
522         self.span = tab_item( self, "Range:", 4, "dB" )
523         self.span.ctrl.SetValue( "%d" % span )
524         h_sizer.Add( self.span, 0 )
525         h_sizer.Add((0,0),1)
526         f_sizer.Add( h_sizer, 0, flag=wx.TOP|wx.EXPAND, border=vs )
527
528         d_sizer.Add((0,0),1)
529         d_sizer.Add( f_sizer, 0, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.EXPAND )
530         d_sizer.Add((0,0),1)
531         d_sizer.Add((0,0),1)
532
533         button_sizer = wx.BoxSizer( wx.HORIZONTAL )
534         apply_button = wx.Button( self, -1, "Apply" )
535         apply_button.Bind( wx.EVT_BUTTON, self.apply_evt )
536         cancel_button = wx.Button( self, -1, "Cancel" )
537         cancel_button.Bind( wx.EVT_BUTTON, self.cancel_evt )
538         ok_button = wx.Button( self, -1, "OK" )
539         ok_button.Bind( wx.EVT_BUTTON, self.ok_evt )
540         button_sizer.Add((0,0),1)
541         button_sizer.Add( apply_button, 0,
542                           flag=wx.ALIGN_CENTER_HORIZONTAL )
543         button_sizer.Add((0,0),1)
544         button_sizer.Add( cancel_button, 0,
545                           flag=wx.ALIGN_CENTER_HORIZONTAL )
546         button_sizer.Add((0,0),1)
547         button_sizer.Add( ok_button, 0,
548                           flag=wx.ALIGN_CENTER_HORIZONTAL )
549         button_sizer.Add((0,0),1)
550         d_sizer.Add( button_sizer, 0,
551                      flag=wx.EXPAND|wx.ALIGN_CENTER|wx.BOTTOM, border=30 )
552         self.SetSizer( d_sizer )
553
554     def apply_evt( self, evt ):
555         self.do_apply()
556
557     def cancel_evt( self, evt ):
558         self.Show( False )
559
560     def ok_evt( self, evt ):
561         self.do_apply()
562         self.Show( False )
563
564     def do_apply( self ):
565         r = float( self.ref.ctrl.GetValue() )
566         g = float( self.span.ctrl.GetValue() )
567         self.set_function( r, g )
568
569 def next_up(v, seq):
570     """
571     Return the first item in seq that is > v.
572     """
573     for s in seq:
574         if s > v:
575             return s
576     return v
577
578 def next_down(v, seq):
579     """
580     Return the last item in seq that is < v.
581     """
582     rseq = list(seq[:])
583     rseq.reverse()
584
585     for s in rseq:
586         if s < v:
587             return s
588     return v
589
590 # One of many copies that should be consolidated . . .
591 def tab_item( parent, label, chars, units, style=wx.TE_RIGHT, value="" ):
592     s = wx.BoxSizer( wx.HORIZONTAL )
593     s.Add( wx.StaticText( parent, -1, label ), 0,
594            flag=wx.ALIGN_CENTER_VERTICAL )
595     s.ctrl = wx.TextCtrl( parent, -1, style=style, value=value )
596     s.ctrl.SetMinSize( ( (1.00+chars)*s.ctrl.GetCharWidth(),
597                                  1.25*s.ctrl.GetCharHeight() ) )
598     s.Add( s.ctrl, -1, flag=wx.LEFT, border=3 )
599     s.Add( wx.StaticText( parent, -1, units ), 0,
600            flag=wx.ALIGN_CENTER_VERTICAL|wx.LEFT, border=1 )
601     return s
602
603
604 # ----------------------------------------------------------------
605 #                     Deprecated interfaces
606 # ----------------------------------------------------------------
607
608 # returns (block, win).
609 #   block requires a single input stream of float
610 #   win is a subclass of wxWindow
611
612 def make_waterfall_sink_f(fg, parent, title, fft_size, input_rate):
613     
614     block = waterfall_sink_f(fg, parent, title=title, fft_size=fft_size,
615                              sample_rate=input_rate)
616     return (block, block.win)
617
618 # returns (block, win).
619 #   block requires a single input stream of gr_complex
620 #   win is a subclass of wxWindow
621
622 def make_waterfall_sink_c(fg, parent, title, fft_size, input_rate):
623     block = waterfall_sink_c(fg, parent, title=title, fft_size=fft_size,
624                              sample_rate=input_rate)
625     return (block, block.win)
626
627
628 # ----------------------------------------------------------------
629 # Standalone test app
630 # ----------------------------------------------------------------
631
632 class test_app_flow_graph (stdgui.gui_flow_graph):
633     def __init__(self, frame, panel, vbox, argv):
634         stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv)
635
636         fft_size = 512
637
638         # build our flow graph
639         input_rate = 20.000e3
640
641         # Generate a complex sinusoid
642         src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
643         #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000)
644
645         # We add these throttle blocks so that this demo doesn't
646         # suck down all the CPU available.  Normally you wouldn't use these.
647         thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate)
648
649         sink1 = waterfall_sink_c (self, panel, title="Complex Data",
650                                   fft_size=fft_size,
651                                   sample_rate=input_rate, baseband_freq=0,
652                                   size=(600,144) )
653         vbox.Add (sink1.win, 1, wx.EXPAND)
654         self.connect (src1, thr1, sink1)
655
656         # generate a real sinusoid
657         src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 5.75e3, 1000)
658         #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3, 1000)
659         thr2 = gr.throttle(gr.sizeof_float, input_rate)
660         sink2 = waterfall_sink_f (self, panel, title="Real Data", fft_size=fft_size,
661                                   sample_rate=input_rate, baseband_freq=0)
662         vbox.Add (sink2.win, 1, wx.EXPAND)
663         self.connect (src2, thr2, sink2)
664
665 def main ():
666     app = stdgui.stdapp (test_app_flow_graph,
667                          "Waterfall Sink Test App")
668     app.MainLoop ()
669
670 if __name__ == '__main__':
671     main ()