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