Home | | Python | | Share This Page |
A Python / GStreamer based signal generator
— P. Lutus — Message Page —
Copyright © 2010, P. Lutus
(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
SignalGen is released under the GNU General Public License.
Here is the plain-text source file without line numbers.
Here is the Glade GTKBuilder XML configuration file (required).
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 |