Imported Upstream version 3.0.4
[debian/gnuradio] / gr-wxgui / src / python / scopesink.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2003,2004,2006 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, eng_notation
24 from gnuradio.wxgui import stdgui
25 import wx
26 import gnuradio.wxgui.plot as plot
27 import Numeric
28 import threading
29 import struct
30
31 default_scopesink_size = (640, 240)
32 default_v_scale = 1000
33 default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1)
34
35 class scope_sink_f(gr.hier_block):
36     def __init__(self, fg, parent, title='', sample_rate=1,
37                  size=default_scopesink_size, frame_decim=default_frame_decim,
38                  v_scale=default_v_scale, t_scale=None):
39         msgq = gr.msg_queue(2)         # message queue that holds at most 2 messages
40         self.guts = gr.oscope_sink_f(sample_rate, msgq)
41         gr.hier_block.__init__(self, fg, self.guts, self.guts)
42         self.win = scope_window(win_info (msgq, sample_rate, frame_decim,
43                                           v_scale, t_scale, self.guts, title), parent)
44
45     def set_sample_rate(self, sample_rate):
46         self.guts.set_sample_rate(sample_rate)
47         self.win.info.set_sample_rate(sample_rate)
48
49 class scope_sink_c(gr.hier_block):
50     def __init__(self, fg, parent, title='', sample_rate=1,
51                  size=default_scopesink_size, frame_decim=default_frame_decim,
52                  v_scale=default_v_scale, t_scale=None):
53         msgq = gr.msg_queue(2)         # message queue that holds at most 2 messages
54         c2f = gr.complex_to_float()
55         self.guts = gr.oscope_sink_f(sample_rate, msgq)
56         fg.connect((c2f, 0), (self.guts, 0))
57         fg.connect((c2f, 1), (self.guts, 1))
58         gr.hier_block.__init__(self, fg, c2f, self.guts)
59         self.win = scope_window(win_info(msgq, sample_rate, frame_decim,
60                                          v_scale, t_scale, self.guts, title), parent)
61         
62     def set_sample_rate(self, sample_rate):
63         self.guts.set_sample_rate(sample_rate)
64         self.win.info.set_sample_rate(sample_rate)
65
66 # ========================================================================
67 # This is the deprecated interface, retained for compatibility...
68 #
69 # returns (block, win).
70 #   block requires a N input stream of float
71 #   win is a subclass of wxWindow
72
73 def make_scope_sink_f (fg, parent, label, input_rate):
74     block = scope_sink_f(fg, parent, title=label, sample_rate=input_rate)
75     return (block, block.win)
76
77 # ========================================================================
78
79
80 time_base_list = [                      # time / division
81     1.0e-7,   # 100ns / div
82     2.5e-7,
83     5.0e-7,
84     1.0e-6,   #   1us / div
85     2.5e-6,
86     5.0e-6,
87     1.0e-5,   #  10us / div
88     2.5e-5,
89     5.0e-5,
90     1.0e-4,   # 100us / div
91     2.5e-4,
92     5.0e-4,
93     1.0e-3,   #   1ms / div
94     2.5e-3,
95     5.0e-3,
96     1.0e-2,   #  10ms / div
97     2.5e-2,
98     5.0e-2
99     ]
100
101 v_scale_list = [ # counts / div, LARGER gains are SMALLER /div, appear EARLIER
102     2.0e-3,   # 2m / div, don't call it V/div it's actually counts/div
103     5.0e-3,
104     1.0e-2,
105     2.0e-2,
106     5.0e-2,
107     1.0e-1,
108     2.0e-1,
109     5.0e-1,
110     1.0e+0,
111     2.0e+0,
112     5.0e+0,
113     1.0e+1,
114     2.0e+1,
115     5.0e+1,
116     1.0e+2,
117     2.0e+2,
118     5.0e+2,
119     1.0e+3,
120     2.0e+3,
121     5.0e+3,
122     1.0e+4 # 10000 /div, USRP full scale is -/+ 32767
123     ]
124
125     
126 wxDATA_EVENT = wx.NewEventType()
127
128 def EVT_DATA_EVENT(win, func):
129     win.Connect(-1, -1, wxDATA_EVENT, func)
130
131 class DataEvent(wx.PyEvent):
132     def __init__(self, data):
133         wx.PyEvent.__init__(self)
134         self.SetEventType (wxDATA_EVENT)
135         self.data = data
136
137     def Clone (self): 
138         self.__class__ (self.GetId())
139
140
141 class win_info (object):
142     __slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale', 
143                  'scopesink', 'title',
144                  'time_scale_cursor', 'v_scale_cursor', 'marker', 'xy',
145                  'autorange', 'running']
146
147     def __init__ (self, msgq, sample_rate, frame_decim, v_scale, t_scale,
148                   scopesink, title = "Oscilloscope"):
149         self.msgq = msgq
150         self.sample_rate = sample_rate
151         self.frame_decim = frame_decim
152         self.scopesink = scopesink
153         self.title = title;
154
155         self.time_scale_cursor = gru.seq_with_cursor(time_base_list, initial_value = t_scale)
156         self.v_scale_cursor = gru.seq_with_cursor(v_scale_list, initial_value = v_scale)
157
158         self.marker = 'line'
159         self.xy = False
160         if v_scale == None:        # 0 and None are both False, but 0 != None
161             self.autorange = True
162         else:
163             self.autorange = False # 0 is a valid v_scale            
164         self.running = True
165
166     def get_time_per_div (self):
167         return self.time_scale_cursor.current ()
168
169     def get_volts_per_div (self):
170         return self.v_scale_cursor.current ()
171
172     def set_sample_rate(self, sample_rate):
173         self.sample_rate = sample_rate
174         
175     def get_sample_rate (self):
176         return self.sample_rate
177
178     def get_decimation_rate (self):
179         return 1.0
180
181     def set_marker (self, s):
182         self.marker = s
183
184     def get_marker (self):
185         return self.marker
186
187
188 class input_watcher (threading.Thread):
189     def __init__ (self, msgq, event_receiver, frame_decim, **kwds):
190         threading.Thread.__init__ (self, **kwds)
191         self.setDaemon (1)
192         self.msgq = msgq
193         self.event_receiver = event_receiver
194         self.frame_decim = frame_decim
195         self.iscan = 0
196         self.keep_running = True
197         self.start ()
198
199     def run (self):
200         # print "input_watcher: pid = ", os.getpid ()
201         while (self.keep_running):
202             msg = self.msgq.delete_head()   # blocking read of message queue
203             if self.iscan == 0:            # only display at frame_decim
204                 self.iscan = self.frame_decim
205                                 
206                 nchan = int(msg.arg1())    # number of channels of data in msg
207                 nsamples = int(msg.arg2()) # number of samples in each channel
208
209                 s = msg.to_string()      # get the body of the msg as a string
210
211                 bytes_per_chan = nsamples * gr.sizeof_float
212
213                 records = []
214                 for ch in range (nchan):
215
216                     start = ch * bytes_per_chan
217                     chan_data = s[start:start+bytes_per_chan]
218                     rec = Numeric.fromstring (chan_data, Numeric.Float32)
219                     records.append (rec)
220
221                 # print "nrecords = %d, reclen = %d" % (len (records),nsamples)
222
223                 de = DataEvent (records)
224                 wx.PostEvent (self.event_receiver, de)
225                 records = []
226                 del de
227
228             # end if iscan == 0
229             self.iscan -= 1
230     
231
232 class scope_window (wx.Panel):
233
234     def __init__ (self, info, parent, id = -1,
235                   pos = wx.DefaultPosition, size = wx.DefaultSize, name = ""):
236         wx.Panel.__init__ (self, parent, -1)
237         self.info = info
238
239         vbox = wx.BoxSizer (wx.VERTICAL)
240
241         self.graph = graph_window (info, self, -1)
242
243         vbox.Add (self.graph, 1, wx.EXPAND)
244         vbox.Add (self.make_control_box(), 0, wx.EXPAND)
245         vbox.Add (self.make_control2_box(), 0, wx.EXPAND)
246
247         self.sizer = vbox
248         self.SetSizer (self.sizer)
249         self.SetAutoLayout (True)
250         self.sizer.Fit (self)
251         self.set_autorange(self.info.autorange)
252         
253
254     # second row of control buttons etc. appears BELOW control_box
255     def make_control2_box (self):
256         ctrlbox = wx.BoxSizer (wx.HORIZONTAL)
257
258         self.inc_v_button = wx.Button (self, 1101, " < ", style=wx.BU_EXACTFIT)
259         self.inc_v_button.SetToolTipString ("Increase vertical range")
260         wx.EVT_BUTTON (self, 1101, self.incr_v_scale) # ID matches button ID above
261
262         self.dec_v_button  = wx.Button (self, 1100, " > ", style=wx.BU_EXACTFIT)
263         self.dec_v_button.SetToolTipString ("Decrease vertical range")
264         wx.EVT_BUTTON (self, 1100, self.decr_v_scale)
265
266         self.v_scale_label = wx.StaticText (self, 1002, "None") # vertical /div
267         self.update_v_scale_label ()
268
269         self.autorange_checkbox = wx.CheckBox (self, 1102, "Autorange")
270         self.autorange_checkbox.SetToolTipString ("Select autorange on/off")
271         wx.EVT_CHECKBOX(self, 1102, self.autorange_checkbox_event)
272
273         ctrlbox.Add ((5,0) ,0) # left margin space
274         ctrlbox.Add (self.inc_v_button, 0, wx.EXPAND)
275         ctrlbox.Add (self.dec_v_button, 0, wx.EXPAND)
276         ctrlbox.Add (self.v_scale_label, 0, wx.ALIGN_CENTER)
277         ctrlbox.Add ((20,0) ,0) # spacer
278         ctrlbox.Add (self.autorange_checkbox, 0, wx.ALIGN_CENTER)
279
280         return ctrlbox
281
282     def make_control_box (self):
283         ctrlbox = wx.BoxSizer (wx.HORIZONTAL)
284
285         tb_left = wx.Button (self, 1001, " < ", style=wx.BU_EXACTFIT)
286         tb_left.SetToolTipString ("Increase time base")
287         wx.EVT_BUTTON (self, 1001, self.incr_timebase)
288
289
290         tb_right  = wx.Button (self, 1000, " > ", style=wx.BU_EXACTFIT)
291         tb_right.SetToolTipString ("Decrease time base")
292         wx.EVT_BUTTON (self, 1000, self.decr_timebase)
293
294         self.time_base_label = wx.StaticText (self, 1002, "")
295         self.update_timebase_label ()
296
297         ctrlbox.Add ((5,0) ,0)
298         # ctrlbox.Add (wx.StaticText (self, -1, "Horiz Scale: "), 0, wx.ALIGN_CENTER)
299         ctrlbox.Add (tb_left, 0, wx.EXPAND)
300         ctrlbox.Add (tb_right, 0, wx.EXPAND)
301         ctrlbox.Add (self.time_base_label, 0, wx.ALIGN_CENTER)
302
303         ctrlbox.Add ((10,0) ,1)            # stretchy space
304
305         ctrlbox.Add (wx.StaticText (self, -1, "Trig: "), 0, wx.ALIGN_CENTER)
306         self.trig_chan_choice = wx.Choice (self, 1004,
307                                            choices = ['Ch1', 'Ch2', 'Ch3', 'Ch4'])
308         self.trig_chan_choice.SetToolTipString ("Select channel for trigger")
309         wx.EVT_CHOICE (self, 1004, self.trig_chan_choice_event)
310         ctrlbox.Add (self.trig_chan_choice, 0, wx.ALIGN_CENTER)
311
312         self.trig_mode_choice = wx.Choice (self, 1005,
313                                            choices = ['Auto', 'Pos', 'Neg'])
314         self.trig_mode_choice.SetToolTipString ("Select trigger slope or Auto (untriggered roll)")
315         wx.EVT_CHOICE (self, 1005, self.trig_mode_choice_event)
316         ctrlbox.Add (self.trig_mode_choice, 0, wx.ALIGN_CENTER)
317
318         trig_level50 = wx.Button (self, 1006, "50%")
319         trig_level50.SetToolTipString ("Set trigger level to 50%")
320         wx.EVT_BUTTON (self, 1006, self.set_trig_level50)
321         ctrlbox.Add (trig_level50, 0, wx.EXPAND)
322
323         run_stop = wx.Button (self, 1007, "Run/Stop")
324         run_stop.SetToolTipString ("Toggle Run/Stop mode")
325         wx.EVT_BUTTON (self, 1007, self.run_stop)
326         ctrlbox.Add (run_stop, 0, wx.EXPAND)
327
328         ctrlbox.Add ((10, 0) ,1)            # stretchy space
329
330         ctrlbox.Add (wx.StaticText (self, -1, "Fmt: "), 0, wx.ALIGN_CENTER)
331         self.marker_choice = wx.Choice (self, 1002, choices = self._marker_choices)
332         self.marker_choice.SetToolTipString ("Select plotting with lines, pluses or dots")
333         wx.EVT_CHOICE (self, 1002, self.marker_choice_event)
334         ctrlbox.Add (self.marker_choice, 0, wx.ALIGN_CENTER)
335
336         self.xy_choice = wx.Choice (self, 1003, choices = ['X:t', 'X:Y'])
337         self.xy_choice.SetToolTipString ("Select X vs time or X vs Y display")
338         wx.EVT_CHOICE (self, 1003, self.xy_choice_event)
339         ctrlbox.Add (self.xy_choice, 0, wx.ALIGN_CENTER)
340
341         return ctrlbox
342     
343     _marker_choices = ['line', 'plus', 'dot']
344
345     def update_timebase_label (self):
346         time_per_div = self.info.get_time_per_div ()
347         s = ' ' + eng_notation.num_to_str (time_per_div) + 's/div'
348         self.time_base_label.SetLabel (s)
349         
350     def decr_timebase (self, evt):
351         self.info.time_scale_cursor.prev ()
352         self.update_timebase_label ()
353
354     def incr_timebase (self, evt):
355         self.info.time_scale_cursor.next ()
356         self.update_timebase_label ()
357
358     def update_v_scale_label (self):
359         volts_per_div = self.info.get_volts_per_div ()
360         s = ' ' + eng_notation.num_to_str (volts_per_div) + '/div' # Not V/div
361         self.v_scale_label.SetLabel (s)
362         
363     def decr_v_scale (self, evt):
364         self.info.v_scale_cursor.prev ()
365         self.update_v_scale_label ()
366
367     def incr_v_scale (self, evt):
368         self.info.v_scale_cursor.next ()
369         self.update_v_scale_label ()
370         
371     def marker_choice_event (self, evt):
372         s = evt.GetString ()
373         self.set_marker (s)
374
375     def set_autorange(self, on):
376         if on:
377             self.v_scale_label.SetLabel(" (auto)")
378             self.info.autorange = True
379             self.autorange_checkbox.SetValue(True)
380             self.inc_v_button.Enable(False)
381             self.dec_v_button.Enable(False)
382         else:
383             if self.graph.y_range:
384                 (l,u) = self.graph.y_range # found by autorange
385                 self.info.v_scale_cursor.set_index_by_value((u-l)/8.0)
386             self.update_v_scale_label()
387             self.info.autorange = False
388             self.autorange_checkbox.SetValue(False)
389             self.inc_v_button.Enable(True)
390             self.dec_v_button.Enable(True)
391             
392     def autorange_checkbox_event(self, evt):
393         if evt.Checked():
394             self.set_autorange(True)
395         else:
396             self.set_autorange(False)
397             
398     def set_marker (self, s):
399         self.info.set_marker (s)        # set info for drawing routines
400         i = self.marker_choice.FindString (s)
401         assert i >= 0, "Hmmm, set_marker problem"
402         self.marker_choice.SetSelection (i)
403
404     def set_format_line (self):
405         self.set_marker ('line')
406
407     def set_format_dot (self):
408         self.set_marker ('dot')
409
410     def set_format_plus (self):
411         self.set_marker ('plus')
412         
413     def xy_choice_event (self, evt):
414         s = evt.GetString ()
415         self.info.xy = s == 'X:Y'
416
417     def trig_chan_choice_event (self, evt):
418         s = evt.GetString ()
419         ch = int (s[-1]) - 1
420         self.info.scopesink.set_trigger_channel (ch)
421
422     def trig_mode_choice_event (self, evt):
423         sink = self.info.scopesink
424         s = evt.GetString ()
425         if s == 'Pos':
426             sink.set_trigger_mode (gr.gr_TRIG_POS_SLOPE)
427         elif s == 'Neg':
428             sink.set_trigger_mode (gr.gr_TRIG_NEG_SLOPE)
429         elif s == 'Auto':
430             sink.set_trigger_mode (gr.gr_TRIG_AUTO)
431         else:
432             assert 0, "Bad trig_mode_choice string"
433     
434     def set_trig_level50 (self, evt):
435         self.info.scopesink.set_trigger_level_auto ()
436
437     def run_stop (self, evt):
438         self.info.running = not self.info.running
439         
440
441 class graph_window (plot.PlotCanvas):
442
443     channel_colors = ['BLUE', 'RED',
444                       'CYAN', 'MAGENTA', 'GREEN', 'YELLOW']
445     
446     def __init__ (self, info, parent, id = -1,
447                   pos = wx.DefaultPosition, size = (640, 240),
448                   style = wx.DEFAULT_FRAME_STYLE, name = ""):
449         plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name)
450
451         self.SetXUseScopeTicks (True)
452         self.SetEnableGrid (True)
453         self.SetEnableZoom (True)
454         self.SetEnableLegend(True)
455         # self.SetBackgroundColour ('black')
456         
457         self.info = info;
458         self.y_range = None
459         self.x_range = None
460         self.avg_y_min = None
461         self.avg_y_max = None
462         self.avg_x_min = None
463         self.avg_x_max = None
464
465         EVT_DATA_EVENT (self, self.format_data)
466
467         self.input_watcher = input_watcher (info.msgq, self, info.frame_decim)
468
469     def channel_color (self, ch):
470         return self.channel_colors[ch % len(self.channel_colors)]
471        
472     def format_data (self, evt):
473         if not self.info.running:
474             return
475         
476         if self.info.xy:
477             self.format_xy_data (evt)
478             return
479
480         info = self.info
481         records = evt.data
482         nchannels = len (records)
483         npoints = len (records[0])
484
485         objects = []
486
487         Ts = 1.0 / (info.get_sample_rate () / info.get_decimation_rate ())
488         x_vals = Ts * Numeric.arrayrange (-npoints/2, npoints/2)
489
490         # preliminary clipping based on time axis here, instead of in graphics code
491         time_per_window = self.info.get_time_per_div () * 10
492         n = int (time_per_window / Ts + 0.5)
493         n = n & ~0x1                    # make even
494         n = max (2, min (n, npoints))
495
496         self.SetXUseScopeTicks (True)   # use 10 divisions, no labels
497
498         for ch in range(nchannels):
499             r = records[ch]
500
501             # plot middle n points of record
502
503             lb = npoints/2 - n/2
504             ub = npoints/2 + n/2
505             # points = zip (x_vals[lb:ub], r[lb:ub])
506             points = Numeric.zeros ((ub-lb, 2), Numeric.Float64)
507             points[:,0] = x_vals[lb:ub]
508             points[:,1] = r[lb:ub]
509
510             m = info.get_marker ()
511             if m == 'line':
512                 objects.append (plot.PolyLine (points,
513                                                colour=self.channel_color (ch),
514                                                legend=('Ch%d' % (ch+1,))))
515             else:
516                 objects.append (plot.PolyMarker (points,
517                                                  marker=m,
518                                                  colour=self.channel_color (ch),
519                                                  legend=('Ch%d' % (ch+1,))))
520
521         graphics = plot.PlotGraphics (objects,
522                                       title=self.info.title,
523                                       xLabel = '', yLabel = '')
524
525         time_per_div = info.get_time_per_div ()
526         x_range = (-5.0 * time_per_div, 5.0 * time_per_div) # ranges are tuples!
527         volts_per_div = info.get_volts_per_div ()
528         if not self.info.autorange:
529             self.y_range = (-4.0 * volts_per_div, 4.0 * volts_per_div)
530         self.Draw (graphics, xAxis=x_range, yAxis=self.y_range)
531         self.update_y_range () # autorange to self.y_range
532
533
534     def format_xy_data (self, evt):
535         info = self.info
536         records = evt.data
537         nchannels = len (records)
538         npoints = len (records[0])
539
540         if nchannels < 2:
541             return
542
543         objects = []
544         # points = zip (records[0], records[1])
545         points = Numeric.zeros ((len(records[0]), 2), Numeric.Float32)
546         points[:,0] = records[0]
547         points[:,1] = records[1]
548         
549         self.SetXUseScopeTicks (False)
550
551         m = info.get_marker ()
552         if m == 'line':
553             objects.append (plot.PolyLine (points,
554                                            colour=self.channel_color (0)))
555         else:
556             objects.append (plot.PolyMarker (points,
557                                              marker=m,
558                                              colour=self.channel_color (0)))
559
560         graphics = plot.PlotGraphics (objects,
561                                       title=self.info.title,
562                                       xLabel = 'I', yLabel = 'Q')
563
564         self.Draw (graphics, xAxis=self.x_range, yAxis=self.y_range)
565         self.update_y_range ()
566         self.update_x_range ()
567
568
569     def update_y_range (self):
570         alpha = 1.0/25
571         graphics = self.last_draw[0]
572         p1, p2 = graphics.boundingBox ()     # min, max points of graphics
573
574         if self.avg_y_min: # prevent vertical scale from jumping abruptly --?
575             self.avg_y_min = p1[1] * alpha + self.avg_y_min * (1 - alpha)
576             self.avg_y_max = p2[1] * alpha + self.avg_y_max * (1 - alpha)
577         else: # initial guess
578             self.avg_y_min = p1[1] # -500.0 workaround, sometimes p1 is ~ 10^35
579             self.avg_y_max = p2[1] # 500.0
580
581         self.y_range = self._axisInterval ('auto', self.avg_y_min, self.avg_y_max)
582         # print "p1 %s  p2 %s  y_min %s  y_max %s  y_range %s" \
583         #        % (p1, p2, self.avg_y_min, self.avg_y_max, self.y_range)
584
585
586     def update_x_range (self):
587         alpha = 1.0/25
588         graphics = self.last_draw[0]
589         p1, p2 = graphics.boundingBox ()     # min, max points of graphics
590
591         if self.avg_x_min:
592             self.avg_x_min = p1[0] * alpha + self.avg_x_min * (1 - alpha)
593             self.avg_x_max = p2[0] * alpha + self.avg_x_max * (1 - alpha)
594         else:
595             self.avg_x_min = p1[0]
596             self.avg_x_max = p2[0]
597
598         self.x_range = self._axisInterval ('auto', self.avg_x_min, self.avg_x_max)
599
600
601 # ----------------------------------------------------------------
602 # Stand-alone test application
603 # ----------------------------------------------------------------
604
605 class test_app_flow_graph (stdgui.gui_flow_graph):
606     def __init__(self, frame, panel, vbox, argv):
607         stdgui.gui_flow_graph.__init__ (self, frame, panel, vbox, argv)
608
609         if len(argv) > 1:
610             frame_decim = int(argv[1]) 
611         else:
612             frame_decim = 1
613
614         if len(argv) > 2:
615             v_scale = float(argv[2])  # start up at this v_scale value
616         else:
617             v_scale = None  # start up in autorange mode, default
618
619         if len(argv) > 3:
620             t_scale = float(argv[3])  # start up at this t_scale value
621         else:
622             t_scale = None  # old behavior
623
624         print "frame decim %s  v_scale %s  t_scale %s" % (frame_decim,v_scale,t_scale)
625             
626         input_rate = 1e6
627
628         # Generate a complex sinusoid
629         src0 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 25.1e3, 1e3)
630
631         # We add this throttle block so that this demo doesn't suck down
632         # all the CPU available.  You normally wouldn't use it...
633         throttle = gr.throttle(gr.sizeof_gr_complex, input_rate)
634
635         scope = scope_sink_c (self, panel,"Secret Data",sample_rate=input_rate,
636                               frame_decim=frame_decim,
637                               v_scale=v_scale, t_scale=t_scale)
638         vbox.Add (scope.win, 1, wx.EXPAND)
639
640         # wire the blocks together
641         self.connect (src0, throttle, scope)
642
643 def main ():
644     app = stdgui.stdapp (test_app_flow_graph, "O'Scope Test App")
645     app.MainLoop ()
646
647 if __name__ == '__main__':
648     main ()
649
650 # ----------------------------------------------------------------