Home | Python |     Share This Page
SignalGen

A Python / GStreamer based signal generator

P. Lutus Message Page

Copyright © 2010, P. Lutus

Discussion | Licensing, Source | Revision History | Program Listing

(double-click any word to see its definition)


NOTE: This program has been superseded. I have written a much better program in Java, portable between platforms, superior in every way, available here. This page remains primarily because it shows some interesting and useful Python programming techniques.
Discussion

This is a replacement for an older program that, because of gradual changes in Linux and in various program development environments, is about to stop working entirely.

But this new program is in many ways better than the old — it uses a robust toolkit called GStreamer to manage the sound issues. This should guard the new program against the rapid obsolescence problems that shortened the earlier program's life.

This program uses your system's sound card as an output device, so the program's bandwidth is roughly the same as your sound card. The program creates various kinds of waveforms — sine, triangle, square and sawtooth — and in a wide range of frequencies. It can be used to troubleshoot computer sound system issues or test the abilities of your multimedia system. It can even be used to tune musical instruments — its frequency accuracy is much better than a traditional standard like a tuning fork.

This program belongs to a relatively new class of programs that represent virtual equivalents of classic test instruments — signal generators, oscilloscopes and other kinds of equipment are gradually disappearing, replaced by computer programs and various kinds of hardware interfaces to connect to the real world.

In this case, the only real range limit is posed by the system sound card, which can be relied on to create signals up to about 1/2 the selected sampling rate (on the basis of the Nyquist–Shannon sampling theorem). While using this program, my system's sound card can support a sampling rate of 48,000 samples per second, so I am able to generate usable signals between 0 Hz - 24 KHz.

Actually, there are several faster sampling rates available from modern computer sound cards, but because Python is interpreted, it can't deliver data fast enough to support them. I would have included these higher rates in the program, but unless your system is much faster than mine, it won't be able to deliver data fast enough to support rates above about 48 KHz. The sign that the program is falling behind will be a breakup of the generated signal, quite unmistakable.

Mouse Wheel Function

You can type numbers for the frequency and level program entries, but if you prefer, you can also place your mouse cursor over the desired entry window and spin your mouse wheel to change the numerical value. If the default rate of change is not fast enough, press Ctrl, Alt or Shift while spinning the mouse wheel -- each of these modifier keys multiplies the rate by ten times, and their effect is cumulative. (Each of Ctrl, Alt and Shift multiplies by ten, so all of them at once multiplies the rate of change by 1,000.)

Modulation

Building on this program's predecessors, I have added modulation and noise sources to the feature set. The modulation capability is especially useful, and was added in support of another project that needed various kinds of modulated waveforms for development work.

In AM modulation mode, a level of 100% simply means 100% modulation:

But in FM mode, there is no widely accepted meaning for "100% modulation". I have adopted the convention that 100% FM modulation means excursions between 0 Hz and twice the carrier frequency -- for a carrier frequency of 1000 Hz, 100% modulation means swings between zero Hz and 2,000 Hz. Here is an example of 50% FM modulation:

For those curious about the mathematics, here are equations for the AM and FM modulation schemes:

$ \displaystyle am(t) = \cos(2 \pi f_c t) \left(\frac{1 + a_m\ \cos(2 \pi f_m t)}{2}\right)$

$ \displaystyle fm(t) = \cos \left(2 \pi f_c \left(t + a_m \int \cos(2 \pi f_m t)\ dt\right) \right) $

Where:
  • t = time, seconds
  • fc = carrier frequency, Hz
  • fm = modulation frequency, Hz
  • am = modulation amplitude, 0 <= am <= 1

As shown, the equations assume sinusoidal carrier and modulation waveforms, but all the waveform types (sine, triangle, square and sawtooth) are available for both carrier and modulation.

Modulation Index

Those who are accustomed to thinking of FM modulation in terms of "modulation index", that is, a maximum frequency deviation from a carrier frequency, may use this equation:

$ \displaystyle mi = 100 \frac{f_{d}}{f_c}$

Where:
  • mi = modulation index "level" value for SignalGen
  • fd = maximum desired frequency deviation
  • fc = carrier frequency

Licensing, Source

Revision History

  • Version 1.1 01/12/2011. Initial Public Release.

Program Listing

  1: #!/usr/bin/env python
  2: # -*- coding: utf-8 -*-
  3: 
  4: # ***************************************************************************
  5: # *   Copyright (C) 2011, Paul Lutus                                        *
  6: # *                                                                         *
  7: # *   This program 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 of the License, or     *
 10: # *   (at your option) any later version.                                   *
 11: # *                                                                         *
 12: # *   This program 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 this program; if not, write to the                         *
 19: # *   Free Software Foundation, Inc.,                                       *
 20: # *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 21: # ***************************************************************************
 22: 
 23: # version date 01-12-2011
 24: 
 25: VERSION = '1.1'
 26: 
 27: import re, sys, os
 28: 
 29: import gobject
 30: gobject.threads_init()
 31: import gst
 32: import gtk
 33: gtk.gdk.threads_init()
 34: import time
 35: import struct
 36: import math
 37: import random
 38: import signal
 39: import webbrowser
 40: 
 41: class Icon:
 42:   icon = [
 43:     "32 32 17 1",
 44:     "   c None",
 45:     ".  c #2A2E30",
 46:     "+  c #333739",
 47:     "@  c #464A4C",
 48:     "#  c #855023",
 49:     "$  c #575A59",
 50:     "%  c #676A69",
 51:     "&  c #CC5B00",
 52:     "*  c #777A78",
 53:     "=  c #DB731A",
 54:     "-  c #8A8C8A",
 55:     ";  c #969895",
 56:     ">  c #F68C22",
 57:     ",  c #A5A7A4",
 58:     "'  c #F49D4A",
 59:     ")  c #B3B5B2",
 60:     "!  c #DEE0DD",
 61:     "                        &&&&&&& ",
 62:     "                  &&&===='''''& ",
 63:     "                  &'''''====&'& ",
 64:     "             +++++&'&&&&&   &'& ",
 65:     "          +@$%****&'&+      &'& ",
 66:     "        +@**%$@++@&'&*@+    &'& ",
 67:     "      +@**@+++++++&'&@**@+  &'& ",
 68:     "     +$*$+++++++++&'&++$*$+ &'& ",
 69:     "     @*@++++++++++&'&+++@#&&&'& ",
 70:     "    +*@++++++++#&&&'&+++#=''''& ",
 71:     "   +*$++++++++#=''''&+++&'>>>'& ",
 72:     "   @*+++++++++&'>>>'&+++#='''=  ",
 73:     "  +%$++++++++@#='''=#@@++#&&&#  ",
 74:     "  +*@+++++++@@@#&&&#@@@@@++@*+  ",
 75:     "  +*+++++++@@@@++@$%$$@@@@++*+  ",
 76:     "  +*++++++@@+@;,,*@@*$$$@@@+*+  ",
 77:     "  +*@++++@@@%!!!!,;@$*$$$@@@*+  ",
 78:     "  +%$++++@@+)!!!),-*+-%$$$@$%+  ",
 79:     "  +@*+++@@@+-!!!,;-%@;%%$$+*@+  ",
 80:     "   +*@++@@@@+$*-*%@+*-%%$@@*+   ",
 81:     "   ++*@+@@@$$%@++@%;;*%%$@-$+   ",
 82:     "    +@%+@@@$$%*;;;;-*%%%@**+    ",
 83:     "    .+$%@@@$$$*******%$$*-+.    ",
 84:     "     .+@%%@@$$*@*@%%%$%-%+.     ",
 85:     "      .++@%$$$$$$%%%%--@+.      ",
 86:     "        +++@@$%*****%+++        ",
 87:     "         +++++++++++++@.        ",
 88:     "          @--%@++@$*-%+         ",
 89:     "           +%,))),;%+.          ",
 90:     "             ++++++.            ",
 91:     "                                ",
 92:     "                                "
 93:   ]
 94: 
 95: # this should be a temporary hack
 96: 
 97: class WidgetFinder:
 98:   def localize_widgets(self,parent,xmlfile):
 99:     # an unbelievable hack made necessary by
100:     # someone unwilling to fix a year-old bug
101:     with open(xmlfile) as f:
102:       for name in re.findall('(?s) id="(.*?)"',f.read()):
103:         if re.search('^k_',name):
104:           obj = parent.builder.get_object(name)
105:           setattr(parent,name,obj)
106: 
107: class ConfigManager:
108:   def __init__(self,path,dic):
109:     self.path = path
110:     self.dic = dic
111: 
112:   def read_config(self):
113:     if os.path.exists(self.path):
114:       with open(self.path) as f:
115:         for record in f.readlines():
116:           se = re.search('(.*?)\s*=\s*(.*)',record.strip())
117:           if(se):
118:             key,value = se.groups()
119:             if (key in self.dic):
120:               widget = self.dic[key]
121:               typ = type(widget)
122:               if(typ == list):
123:                 widget[0] = value
124:               elif(typ == gtk.Entry):
125:                 widget.set_text(value)
126:               elif(typ == gtk.HScale):
127:                 widget.set_value(float(value))
128:               elif(typ == gtk.Window):
129:                 w,h = value.split(',')
130:                 widget.resize(int(w),int(h))
131:               elif(typ == gtk.CheckButton or typ == gtk.RadioButton or typ == gtk.ToggleButton):
132:                 widget.set_active(value == 'True')
133:               elif(typ == gtk.ComboBox):
134:                 if(value in widget.datalist):
135:                   i = widget.datalist.index(value)
136:                   widget.set_active(i)
137:               else:
138:                 print("ERROR: reading, cannot identify key %s with type %s" % (key,type(widget)))
139: 
140:   def write_config(self):
141:     with open(self.path,'w') as f:
142:       for key,widget in sorted(self.dic.items()):
143:         typ = type(widget)
144:         if(typ == list):
145:           value = widget[0]
146:         elif(typ == gtk.Entry):
147:           value = widget.get_text()
148:         elif(typ == gtk.HScale):
149:           value = str(widget.get_value())
150:         elif(typ == gtk.Window):
151:           _,_,w,h = widget.get_allocation()
152:           value = "%d,%d" % (w,h)
153:         elif(typ == gtk.CheckButton or typ == gtk.RadioButton or typ == gtk.ToggleButton):
154:           value = ('False','True')[widget.get_active()]
155:         elif(typ == gtk.ComboBox):
156:           value = widget.get_active_text()
157:         else:
158:           print("ERROR: writing, cannot identify key %s with type %s" % (key,type(widget)))
159:           value = "Error"
160:         f.write("%s = %s\n" % (key,value))
161: 
162:   def preset_combobox(self,box,v):
163:     if(v in box.datalist):
164:       i = box.datalist.index(v)
165:       box.set_active(i)
166:     else:
167:       box.set_active(0)
168: 
169:   def load_combobox(self,obj,data):
170:     if(len(obj.get_cells()) == 0):
171:       # Create a text cell renderer
172:       cell = gtk.CellRendererText ()
173:       obj.pack_start(cell)
174:       obj.add_attribute (cell, "text", 0)
175:     obj.get_model().clear()
176:     for s in data:
177:       obj.append_text(s.strip())
178:     setattr(obj,'datalist',data)
179: 
180: class TextEntryController:
181:   def __init__(self,parent,widget):
182:     self.par = parent
183:     self.widget = widget
184:     widget.connect('scroll-event',self.scroll_event)
185:     widget.set_tooltip_text('Enter number or:\n\
186:     Mouse wheel: increase,decrease\n\
187:     Shift/Ctrl/Alt: faster change')
188: 
189:   def scroll_event(self,w,evt):
190:     q = (-1,1)[evt.direction == gtk.gdk.SCROLL_UP]
191:     # magnify change if shift,ctrl,alt pressed
192:     for m in (1,2,4):
193:       if(self.par.mod_key_val & m): q *= 10
194:     s = self.widget.get_text()
195:     v = float(s)
196:     v += q
197:     v = max(0,v)
198:     s = self.par.format_num(v)
199:     self.widget.set_text(s)
200: 
201: class SignalGen:
202:   M_AM,M_FM = list(range(2))
203:   W_SINE,W_TRIANGLE,W_SQUARE,W_SAWTOOTH = list(range(4))
204:   waveform_strings = ('Sine','Triangle','Square','Sawtooth')
205:   R_48000,R_44100,R_22050,R_16000,R_11025,R_8000,R_4000 = list(range(7))
206:   sample_rates = ('48000','44100','22050','16000', '11025', '8000', '4000')
207:   def __init__(self):
208:     self.restart = False
209:     # exit correctly on system signals
210:     signal.signal(signal.SIGTERM, self.close)
211:     signal.signal(signal.SIGINT, self.close)
212:     # precompile struct operator
213:     self.struct_int = struct.Struct('i')
214:     self.max_level = (2.0**31)-1
215:     self.gen_functions = (
216:       self.sine_function,
217:       self.triangle_function,
218:       self.square_function,
219:       self.sawtooth_function
220:     )
221:     self.main_color = gtk.gdk.color_parse('#c04040')
222:     self.sig_color = gtk.gdk.color_parse('#40c040')
223:     self.mod_color = gtk.gdk.color_parse('#4040c0')
224:     self.noise_color = gtk.gdk.color_parse('#c040c0')
225:     self.pipeline = False
226:     self.count = 0
227:     self.imod = 0
228:     self.rate = 1
229:     self.mod_key_val = 0
230:     self.sig_freq = 440
231:     self.mod_freq = 3
232:     self.sig_level = 100
233:     self.mod_level = 100
234:     self.noise_level = 100
235:     self.enable = True
236:     self.sig_waveform = SignalGen.W_SINE
237:     self.sig_enable = True
238:     self.sig_function = False
239:     self.mod_waveform = SignalGen.W_SINE
240:     self.mod_function = False
241:     self.mod_mode = SignalGen.M_AM
242:     self.mod_enable = False
243:     self.noise_enable = False
244:     self.sample_rate = SignalGen.R_22050
245:     self.left_audio  = True
246:     self.right_audio = True
247:     self.program_name = self.__class__.__name__
248:     self.config_file = os.path.expanduser("~/." + self.program_name)
249:     self.builder = gtk.Builder()
250:     self.xmlfile = 'signalgen_gui.glade'
251:     self.builder.add_from_file(self.xmlfile)
252:     WidgetFinder().localize_widgets(self,self.xmlfile)
253:     self.k_quit_button.connect('clicked',self.close)
254:     self.k_help_button.connect('clicked',self.launch_help)
255:     self.k_mainwindow.connect('destroy',self.close)
256:     self.k_mainwindow.set_icon(gtk.gdk.pixbuf_new_from_xpm_data(Icon.icon))
257:     self.title = self.program_name + ' ' + VERSION
258:     self.k_mainwindow.set_title(self.title)
259:     self.tooltips = {
260:       self.k_sample_rate_combobox : 'Change data sampling rate',
261:       self.k_left_checkbutton : 'Enable left channel audio',
262:       self.k_right_checkbutton : 'Enable right channel audio',
263:       self.k_sig_waveform_combobox : 'Select signal waveform',
264:       self.k_mod_waveform_combobox : 'Select modulation waveform',
265:       self.k_mod_enable_checkbutton  : 'Enable modulation',
266:       self.k_sig_enable_checkbutton  : 'Enable signal',
267:       self.k_noise_enable_checkbutton  : 'Enable white noise',
268:       self.k_mod_am_radiobutton : 'Enable amplitude modulation',
269:       self.k_mod_fm_radiobutton : 'Enable frequency modulation',
270:       self.k_quit_button : 'Quit %s' % self.title,
271:       self.k_enable_checkbutton : 'Enable output',
272:       self.k_help_button : 'Visit the %s Web page' % self.title,
273:     }
274:     for k,v in self.tooltips.items():
275:       k.set_tooltip_text(v)
276:     self.config_data = {
277:       'SampleRate' : self.k_sample_rate_combobox,
278:       'LeftChannelEnabled' : self.k_left_checkbutton,
279:       'RightChannelEnabled' : self.k_right_checkbutton,
280:       'SignalWaveform' : self.k_sig_waveform_combobox,
281:       'SignalFrequency' : self.k_sig_freq_entry,
282:       'SignalLevel' : self.k_sig_level_entry,
283:       'SignalEnabled' : self.k_sig_enable_checkbutton,
284:       'ModulationWaveform' : self.k_mod_waveform_combobox,
285:       'ModulationFrequency' : self.k_mod_freq_entry,
286:       'ModulationLevel' : self.k_mod_level_entry,
287:       'ModulationEnabled' : self.k_mod_enable_checkbutton,
288:       'AmplitudeModulation' : self.k_mod_am_radiobutton,
289:       'FrequencyModulation' : self.k_mod_fm_radiobutton,
290:       'NoiseEnabled' : self.k_noise_enable_checkbutton,
291:       'NoiseLevel' : self.k_noise_level_entry,
292:       'OutputEnabled' : self.k_enable_checkbutton,
293:     }
294:     self.cm = ConfigManager(self.config_file,self.config_data)
295:     self.cm.load_combobox(self.k_sig_waveform_combobox,self.waveform_strings)
296:     self.k_sig_waveform_combobox.set_active(self.sig_waveform)
297:     self.cm.load_combobox(self.k_mod_waveform_combobox,self.waveform_strings)
298:     self.k_mod_waveform_combobox.set_active(self.mod_waveform)
299:     self.cm.load_combobox(self.k_sample_rate_combobox,self.sample_rates)
300:     self.k_sample_rate_combobox.set_active(self.sample_rate)
301:     self.k_sig_freq_entry.set_text(self.format_num(self.sig_freq))
302:     self.k_sig_level_entry.set_text(self.format_num(self.sig_level))
303:     self.k_mod_freq_entry.set_text(self.format_num(self.mod_freq))
304:     self.k_mod_level_entry.set_text(self.format_num(self.mod_level))
305:     self.k_noise_level_entry.set_text(self.format_num(self.noise_level))
306:     self.k_main_viewport_border.modify_bg(gtk.STATE_NORMAL,self.main_color)
307:     self.k_sig_viewport_border.modify_bg(gtk.STATE_NORMAL,self.sig_color)
308:     self.k_mod_viewport_border.modify_bg(gtk.STATE_NORMAL,self.mod_color)
309:     self.k_noise_viewport_border.modify_bg(gtk.STATE_NORMAL,self.noise_color)
310:     self.sig_freq_cont = TextEntryController(self,self.k_sig_freq_entry)
311:     self.sig_level_cont = TextEntryController(self,self.k_sig_level_entry)
312:     self.mod_freq_cont = TextEntryController(self,self.k_mod_freq_entry)
313:     self.mod_level_cont = TextEntryController(self,self.k_mod_level_entry)
314:     self.noise_level_cont = TextEntryController(self,self.k_noise_level_entry)
315:     self.k_mainwindow.connect('key-press-event',self.key_event)
316:     self.k_mainwindow.connect('key-release-event',self.key_event)
317:     self.k_enable_checkbutton.connect('toggled',self.update_values)
318:     self.k_sig_freq_entry.connect('changed',self.update_entry_values)
319:     self.k_sig_level_entry.connect('changed',self.update_entry_values)
320:     self.k_sig_enable_checkbutton.connect('toggled',self.update_checkbutton_values)
321:     self.k_mod_freq_entry.connect('changed',self.update_entry_values)
322:     self.k_mod_level_entry.connect('changed',self.update_entry_values)
323:     self.k_noise_level_entry.connect('changed',self.update_entry_values)
324:     self.k_sample_rate_combobox.connect('changed',self.update_values)
325:     self.k_sig_waveform_combobox.connect('changed',self.update_values)
326:     self.k_mod_waveform_combobox.connect('changed',self.update_values)
327:     self.k_left_checkbutton.connect('toggled',self.update_checkbutton_values)
328:     self.k_right_checkbutton.connect('toggled',self.update_checkbutton_values)
329:     self.k_mod_enable_checkbutton.connect('toggled',self.update_checkbutton_values)
330:     self.k_noise_enable_checkbutton.connect('toggled',self.update_checkbutton_values)
331:     self.k_mod_am_radiobutton.connect('toggled',self.update_checkbutton_values)
332:     self.cm.read_config()
333:     self.update_entry_values()
334:     self.update_checkbutton_values()
335:     self.update_values()
336: 
337:   def format_num(self,v):
338:     return "%.2f" % v
339: 
340:   def get_widget_text(self,w):
341:     typ = type(w)
342:     if(typ == gtk.ComboBox):
343:       return w.get_active_text()
344:     elif(typ == gtk.Entry):
345:       return w.get_text()
346: 
347:   def get_widget_num(self,w):
348:     try:
349:       return float(self.get_widget_text(w))
350:     except:
351:       return 0.0
352: 
353:   def restart_test(self,w,pv):
354:     nv = w.get_active()
355:     self.restart |= (nv != pv)
356:     return nv
357:     
358:   def update_entry_values(self,*args):
359:     self.sig_freq = self.get_widget_num(self.k_sig_freq_entry)
360:     self.sig_level = self.get_widget_num(self.k_sig_level_entry) / 100.0
361:     self.mod_freq = self.get_widget_num(self.k_mod_freq_entry)
362:     self.mod_level = self.get_widget_num(self.k_mod_level_entry) / 100.0
363:     self.noise_level = self.get_widget_num(self.k_noise_level_entry) / 100.0
364:     
365:   def update_checkbutton_values(self,*args):
366:     self.left_audio = self.k_left_checkbutton.get_active()
367:     self.right_audio = self.k_right_checkbutton.get_active()
368:     self.mod_enable = self.k_mod_enable_checkbutton.get_active()
369:     self.sig_enable = self.k_sig_enable_checkbutton.get_active()
370:     self.mod_mode = (SignalGen.M_FM,SignalGen.M_AM)[self.k_mod_am_radiobutton.get_active()]
371:     self.noise_enable = self.k_noise_enable_checkbutton.get_active()
372:     
373:   def update_values(self,*args):
374:     self.restart = (not self.sig_function)
375:     self.sample_rate = self.restart_test(self.k_sample_rate_combobox, self.sample_rate)
376:     self.enable = self.restart_test(self.k_enable_checkbutton,self.enable)
377:     self.mod_waveform = self.k_mod_waveform_combobox.get_active()
378:     self.mod_function = self.gen_functions[self.mod_waveform]
379:     self.sig_waveform = self.k_sig_waveform_combobox.get_active()
380:     self.sig_function = self.gen_functions[self.sig_waveform]
381:     self.k_sample_rate_combobox.set_sensitive(not self.enable)
382:     if(self.restart):
383:       self.init_audio()
384:       
385:   def make_and_chain(self,name):
386:     target = gst.element_factory_make(name)
387:     self.chain.append(target)
388:     return target
389: 
390:   def unlink_gst(self):
391:     if(self.pipeline):
392:       self.pipeline.set_state(gst.STATE_NULL)
393:       self.pipeline.remove_many(*self.chain)
394:       gst.element_unlink_many(*self.chain)
395:       for item in self.chain:
396:         item = False
397:       self.pipeline = False
398:       time.sleep(0.01)
399: 
400:   def init_audio(self):
401:     self.unlink_gst()
402:     if(self.enable):
403:       self.chain = []
404:       self.pipeline = gst.Pipeline("mypipeline")
405:       self.source = self.make_and_chain("appsrc")
406:       rs = SignalGen.sample_rates[self.sample_rate]
407:       self.rate = float(rs)
408:       self.interval = 1.0 / self.rate
409:       caps = gst.Caps(
410:       'audio/x-raw-int,'
411:       'endianness=(int)1234,'
412:       'channels=(int)2,'
413:       'width=(int)32,'
414:       'depth=(int)32,'
415:       'signed=(boolean)true,'
416:       'rate=(int)%s' % rs)
417:       self.source.set_property('caps', caps)
418:       self.sink = self.make_and_chain("autoaudiosink")
419:       self.pipeline.add(*self.chain)
420:       gst.element_link_many(*self.chain)
421:       self.source.connect('need-data', self.need_data)
422:       self.pipeline.set_state(gst.STATE_PLAYING)
423:       
424:   def key_event(self,w,evt):
425:     cn = gtk.gdk.keyval_name(evt.keyval)
426:     if(re.search('Shift',cn) != None):
427:       mod = 1
428:     elif(re.search('Control',cn) != None):
429:       mod = 2
430:     elif(re.search('Alt|Meta',cn) != None):
431:       mod = 4
432:     else:
433:       return
434:     if(evt.type == gtk.gdk.KEY_PRESS):
435:       self.mod_key_val |= mod
436:     else:
437:       self.mod_key_val &= ~mod
438: 
439:   def sine_function(self,t,f):
440:     return math.sin(2.0*math.pi*f*t)
441: 
442:   def triangle_function(self,t,f):
443:     q = 4*math.fmod(t*f,1)
444:     q = (q,2-q)[q > 1]
445:     return (q,-2-q)[q < -1]
446: 
447:   def square_function(self,t,f):
448:     if(f == 0): return 0
449:     q = 0.5 - math.fmod(t*f,1)
450:     return (-1,1)[q > 0]
451: 
452:   def sawtooth_function(self,t,f):
453:     return 2.0*math.fmod((t*f)+0.5,1.0)-1.0
454: 
455:   def need_data(self,src,length):
456:     bytes = ""
457:     # sending two channels, so divide requested length by 2
458:     ld2 = length / 2
459:     for tt in range(ld2):
460:       t = (self.count + tt) * self.interval
461:       if(not self.mod_enable):
462:         datum = self.sig_function(t,self.sig_freq)
463:       else:
464:         mod = self.mod_function(t,self.mod_freq)
465:         # AM mode
466:         if(self.mod_mode == SignalGen.M_AM):
467:           datum = 0.5 * self.sig_function(t,self.sig_freq) * (1.0 + (mod * self.mod_level))
468:         # FM mode
469:         else:
470:           self.imod += (mod * self.mod_level * self.interval)
471:           datum = self.sig_function(t+self.imod,self.sig_freq)
472:       v = 0
473:       if(self.sig_enable):
474:         v += (datum * self.sig_level)
475:       if(self.noise_enable):
476:         noise = ((2.0 * random.random()) - 1.0)
477:         v += noise * self.noise_level
478:       v *= self.max_level
479:       v = max(-self.max_level,v)
480:       v = min(self.max_level,v)
481:       left  = (0,v)[self.left_audio]
482:       right = (0,v)[self.right_audio]
483:       bytes += self.struct_int.pack(left)
484:       bytes += self.struct_int.pack(right)
485:     self.count += ld2
486:     src.emit('push-buffer', gst.Buffer(bytes))
487:     
488:   def launch_help(self,*args):
489:     webbrowser.open("http://arachnoid.com/python/signalgen_program.html")
490: 
491:   def close(self,*args):
492:     self.unlink_gst()
493:     self.cm.write_config()
494:     gtk.main_quit()
495: 
496: app=SignalGen()
497: gtk.main()
498: 
 

Home | Python |     Share This Page