def __init__(self, *args, **kwds): # begin wxGlade: MyFrame.__init__ kwds["style"] = wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) # Menu Bar self.frame_1_menubar = wx.MenuBar() self.SetMenuBar(self.frame_1_menubar) wxglade_tmp_menu = wx.Menu() self.Exit = wx.MenuItem(wxglade_tmp_menu, ID_EXIT, "Exit", "Exit", wx.ITEM_NORMAL) wxglade_tmp_menu.AppendItem(self.Exit) self.frame_1_menubar.Append(wxglade_tmp_menu, "File") # Menu Bar end self.panel_1 = wx.Panel(self, -1) self.button_1 = wx.Button(self, ID_BUTTON_1, "LSB") self.button_2 = wx.Button(self, ID_BUTTON_2, "USB") self.button_3 = wx.Button(self, ID_BUTTON_3, "AM") self.button_4 = wx.Button(self, ID_BUTTON_4, "CW") self.button_5 = wx.ToggleButton(self, ID_BUTTON_5, "Upper") self.slider_fcutoff_hi = wx.Slider(self, ID_SLIDER_1, 0, -15798, 15799, style=wx.SL_HORIZONTAL | wx.SL_LABELS) self.button_6 = wx.ToggleButton(self, ID_BUTTON_6, "Lower") self.slider_fcutoff_lo = wx.Slider(self, ID_SLIDER_2, 0, -15799, 15798, style=wx.SL_HORIZONTAL | wx.SL_LABELS) self.panel_5 = wx.Panel(self, -1) self.label_1 = wx.StaticText(self, -1, " Band\nCenter") self.text_ctrl_1 = wx.TextCtrl(self, ID_TEXT_1, "") self.panel_6 = wx.Panel(self, -1) self.panel_7 = wx.Panel(self, -1) self.panel_2 = wx.Panel(self, -1) self.button_7 = wx.ToggleButton(self, ID_BUTTON_7, "Freq") self.slider_3 = wx.Slider(self, ID_SLIDER_3, 3000, 0, 6000) self.spin_ctrl_1 = wx.SpinCtrl(self, ID_SPIN_1, "", min=0, max=100) self.button_8 = wx.ToggleButton(self, ID_BUTTON_8, "Vol") self.slider_4 = wx.Slider(self, ID_SLIDER_4, 0, 0, 500) self.slider_5 = wx.Slider(self, ID_SLIDER_5, 0, 0, 20) self.button_9 = wx.ToggleButton(self, ID_BUTTON_9, "Time") self.button_11 = wx.Button(self, ID_BUTTON_11, "Rew") self.button_10 = wx.Button(self, ID_BUTTON_10, "Fwd") self.panel_3 = wx.Panel(self, -1) self.label_2 = wx.StaticText(self, -1, "PGA ") self.panel_4 = wx.Panel(self, -1) self.panel_8 = wx.Panel(self, -1) self.panel_9 = wx.Panel(self, -1) self.label_3 = wx.StaticText(self, -1, "AM Sync\nCarrier") self.slider_6 = wx.Slider(self, ID_SLIDER_6, 50, 0, 200, style=wx.SL_HORIZONTAL | wx.SL_LABELS) self.label_4 = wx.StaticText(self, -1, "Antenna Tune") self.slider_7 = wx.Slider(self, ID_SLIDER_7, 1575, 950, 2200, style=wx.SL_HORIZONTAL | wx.SL_LABELS) self.panel_10 = wx.Panel(self, -1) self.button_12 = wx.ToggleButton(self, ID_BUTTON_12, "Auto Tune") self.button_13 = wx.Button(self, ID_BUTTON_13, "Calibrate") self.button_14 = wx.Button(self, ID_BUTTON_14, "Reset") self.panel_11 = wx.Panel(self, -1) self.panel_12 = wx.Panel(self, -1) self.__set_properties() self.__do_layout() # end wxGlade parser = OptionParser(option_class=eng_option) parser.add_option("", "--address", type="string", default="addr=192.168.10.2", help="Address of UHD device, [default=%default]") parser.add_option("-c", "--ddc-freq", type="eng_float", default=3.9e6, help="set Rx DDC frequency to FREQ", metavar="FREQ") parser.add_option( "-s", "--samp-rate", type="eng_float", default=256e3, help="set sample rate (bandwidth) [default=%default]") parser.add_option("-a", "--audio_file", default="", help="audio output file", metavar="FILE") parser.add_option("-r", "--radio_file", default="", help="radio output file", metavar="FILE") parser.add_option("-i", "--input_file", default="", help="radio input file", metavar="FILE") parser.add_option( "-O", "--audio-output", type="string", default="", help="audio output device name. E.g., hw:0,0, /dev/dsp, or pulse") (options, args) = parser.parse_args() self.usrp_center = options.ddc_freq input_rate = options.samp_rate self.slider_range = input_rate * 0.9375 self.f_lo = self.usrp_center - (self.slider_range / 2) self.f_hi = self.usrp_center + (self.slider_range / 2) self.af_sample_rate = 32000 fir_decim = long(input_rate / self.af_sample_rate) # data point arrays for antenna tuner self.xdata = [] self.ydata = [] self.tb = gr.top_block() # radio variables, initial conditions self.frequency = self.usrp_center # these map the frequency slider (0-6000) to the actual range self.f_slider_offset = self.f_lo self.f_slider_scale = 10000 self.spin_ctrl_1.SetRange(self.f_lo, self.f_hi) self.text_ctrl_1.SetValue(str(int(self.usrp_center))) self.slider_5.SetValue(0) self.AM_mode = False self.slider_3.SetValue( (self.frequency - self.f_slider_offset) / self.f_slider_scale) self.spin_ctrl_1.SetValue(int(self.frequency)) POWERMATE = True try: self.pm = powermate.powermate(self) except: sys.stderr.write("Unable to find PowerMate or Contour Shuttle\n") POWERMATE = False if POWERMATE: powermate.EVT_POWERMATE_ROTATE(self, self.on_rotate) powermate.EVT_POWERMATE_BUTTON(self, self.on_pmButton) self.active_button = 7 # command line options if options.audio_file == "": SAVE_AUDIO_TO_FILE = False else: SAVE_AUDIO_TO_FILE = True if options.radio_file == "": SAVE_RADIO_TO_FILE = False else: SAVE_RADIO_TO_FILE = True if options.input_file == "": self.PLAY_FROM_USRP = True else: self.PLAY_FROM_USRP = False if self.PLAY_FROM_USRP: self.src = uhd.usrp_source(device_addr=options.address, io_type=uhd.io_type.COMPLEX_FLOAT32, num_channels=1) self.src.set_samp_rate(input_rate) input_rate = self.src.get_samp_rate() self.src.set_center_freq(self.usrp_center, 0) self.tune_offset = 0 else: self.src = gr.file_source(gr.sizeof_short, options.input_file) self.tune_offset = 2200 # 2200 works for 3.5-4Mhz band # convert rf data in interleaved short int form to complex s2ss = gr.stream_to_streams(gr.sizeof_short, 2) s2f1 = gr.short_to_float() s2f2 = gr.short_to_float() src_f2c = gr.float_to_complex() self.tb.connect(self.src, s2ss) self.tb.connect((s2ss, 0), s2f1) self.tb.connect((s2ss, 1), s2f2) self.tb.connect(s2f1, (src_f2c, 0)) self.tb.connect(s2f2, (src_f2c, 1)) # save radio data to a file if SAVE_RADIO_TO_FILE: radio_file = gr.file_sink(gr.sizeof_short, options.radio_file) self.tb.connect(self.src, radio_file) # 2nd DDC xlate_taps = gr.firdes.low_pass ( \ 1.0, input_rate, 16e3, 4e3, gr.firdes.WIN_HAMMING ) self.xlate = gr.freq_xlating_fir_filter_ccf ( \ fir_decim, xlate_taps, self.tune_offset, input_rate ) # Complex Audio filter audio_coeffs = gr.firdes.complex_band_pass( 1.0, # gain self.af_sample_rate, # sample rate -3000, # low cutoff 0, # high cutoff 100, # transition gr.firdes.WIN_HAMMING) # window self.slider_fcutoff_hi.SetValue(0) self.slider_fcutoff_lo.SetValue(-3000) self.audio_filter = gr.fir_filter_ccc(1, audio_coeffs) # Main +/- 16Khz spectrum display self.fft = fftsink2.fft_sink_c(self.panel_2, fft_size=512, sample_rate=self.af_sample_rate, average=True, size=(640, 240)) # AM Sync carrier if AM_SYNC_DISPLAY: self.fft2 = fftsink.fft_sink_c(self.tb, self.panel_9, y_per_div=20, fft_size=512, sample_rate=self.af_sample_rate, average=True, size=(640, 240)) c2f = gr.complex_to_float() # AM branch self.sel_am = gr.multiply_const_cc(0) # the following frequencies turn out to be in radians/sample # gr.pll_refout_cc(alpha,beta,min_freq,max_freq) # suggested alpha = X, beta = .25 * X * X pll = gr.pll_refout_cc(.5, .0625, (2. * math.pi * 7.5e3 / self.af_sample_rate), (2. * math.pi * 6.5e3 / self.af_sample_rate)) self.pll_carrier_scale = gr.multiply_const_cc(complex(10, 0)) am_det = gr.multiply_cc() # these are for converting +7.5kHz to -7.5kHz # for some reason gr.conjugate_cc() adds noise ?? c2f2 = gr.complex_to_float() c2f3 = gr.complex_to_float() f2c = gr.float_to_complex() phaser1 = gr.multiply_const_ff(1) phaser2 = gr.multiply_const_ff(-1) # filter for pll generated carrier pll_carrier_coeffs = gr.firdes.complex_band_pass( 2.0, # gain self.af_sample_rate, # sample rate 7400, # low cutoff 7600, # high cutoff 100, # transition gr.firdes.WIN_HAMMING) # window self.pll_carrier_filter = gr.fir_filter_ccc(1, pll_carrier_coeffs) self.sel_sb = gr.multiply_const_ff(1) combine = gr.add_ff() #AGC sqr1 = gr.multiply_ff() intr = gr.iir_filter_ffd([.004, 0], [0, .999]) offset = gr.add_const_ff(1) agc = gr.divide_ff() self.scale = gr.multiply_const_ff(0.00001) dst = audio.sink(long(self.af_sample_rate), options.audio_output) if self.PLAY_FROM_USRP: self.tb.connect(self.src, self.xlate, self.fft) else: self.tb.connect(src_f2c, self.xlate, self.fft) self.tb.connect(self.xlate, self.audio_filter, self.sel_am, (am_det, 0)) self.tb.connect(self.sel_am, pll, self.pll_carrier_scale, self.pll_carrier_filter, c2f3) self.tb.connect((c2f3, 0), phaser1, (f2c, 0)) self.tb.connect((c2f3, 1), phaser2, (f2c, 1)) self.tb.connect(f2c, (am_det, 1)) self.tb.connect(am_det, c2f2, (combine, 0)) self.tb.connect(self.audio_filter, c2f, self.sel_sb, (combine, 1)) if AM_SYNC_DISPLAY: self.tb.connect(self.pll_carrier_filter, self.fft2) self.tb.connect(combine, self.scale) self.tb.connect(self.scale, (sqr1, 0)) self.tb.connect(self.scale, (sqr1, 1)) self.tb.connect(sqr1, intr, offset, (agc, 1)) self.tb.connect(self.scale, (agc, 0)) self.tb.connect(agc, dst) if SAVE_AUDIO_TO_FILE: f_out = gr.file_sink(gr.sizeof_short, options.audio_file) sc1 = gr.multiply_const_ff(64000) f2s1 = gr.float_to_short() self.tb.connect(agc, sc1, f2s1, f_out) self.tb.start() # for mouse position reporting on fft display self.fft.win.Bind(wx.EVT_LEFT_UP, self.Mouse) # and left click to re-tune self.fft.win.Bind(wx.EVT_LEFT_DOWN, self.Click) # start a timer to check for web commands if WEB_CONTROL: self.timer = UpdateTimer(self, 1000) # every 1000 mSec, 1 Sec wx.EVT_BUTTON(self, ID_BUTTON_1, self.set_lsb) wx.EVT_BUTTON(self, ID_BUTTON_2, self.set_usb) wx.EVT_BUTTON(self, ID_BUTTON_3, self.set_am) wx.EVT_BUTTON(self, ID_BUTTON_4, self.set_cw) wx.EVT_BUTTON(self, ID_BUTTON_10, self.fwd) wx.EVT_BUTTON(self, ID_BUTTON_11, self.rew) wx.EVT_BUTTON(self, ID_BUTTON_13, self.AT_calibrate) wx.EVT_BUTTON(self, ID_BUTTON_14, self.AT_reset) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_5, self.on_button) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_6, self.on_button) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_7, self.on_button) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_8, self.on_button) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_9, self.on_button) wx.EVT_SLIDER(self, ID_SLIDER_1, self.set_filter) wx.EVT_SLIDER(self, ID_SLIDER_2, self.set_filter) wx.EVT_SLIDER(self, ID_SLIDER_3, self.slide_tune) wx.EVT_SLIDER(self, ID_SLIDER_4, self.set_volume) wx.EVT_SLIDER(self, ID_SLIDER_5, self.set_pga) wx.EVT_SLIDER(self, ID_SLIDER_6, self.am_carrier) wx.EVT_SLIDER(self, ID_SLIDER_7, self.antenna_tune) wx.EVT_SPINCTRL(self, ID_SPIN_1, self.spin_tune) wx.EVT_MENU(self, ID_EXIT, self.TimeToQuit)
def __init__(self, demod_rate, audio_decimation): """ Hierarchical block for demodulating a broadcast FM signal. The input is the downconverted complex baseband signal (gr_complex). The output is two streams of the demodulated audio (float) 0=Left, 1=Right. @param demod_rate: input sample rate of complex baseband input. @type demod_rate: float @param audio_decimation: how much to decimate demod_rate to get to audio. @type audio_decimation: integer """ gr.hier_block2.__init__( self, "wfm_rcv_fmdet", gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature gr.io_signature(2, 2, gr.sizeof_float)) # Output signature lowfreq = -125e3 / demod_rate highfreq = 125e3 / demod_rate audio_rate = demod_rate / audio_decimation # We assign to self so that outsiders can grab the demodulator # if they need to. E.g., to plot its output. # # input: complex; output: float self.fm_demod = gr.fmdet_cf(demod_rate, lowfreq, highfreq, 0.05) # input: float; output: float self.deemph_Left = fm_deemph(audio_rate) self.deemph_Right = fm_deemph(audio_rate) # compute FIR filter taps for audio filter width_of_transition_band = audio_rate / 32 audio_coeffs = gr.firdes.low_pass( 1.0, # gain demod_rate, # sampling rate 15000, width_of_transition_band, gr.firdes.WIN_HAMMING) # input: float; output: float self.audio_filter = gr.fir_filter_fff(audio_decimation, audio_coeffs) if 1: # Pick off the stereo carrier/2 with this filter. It # attenuated 10 dB so apply 10 dB gain We pick off the # negative frequency half because we want to base band by # it! ## NOTE THIS WAS HACKED TO OFFSET INSERTION LOSS DUE TO ## DEEMPHASIS stereo_carrier_filter_coeffs = gr.firdes.complex_band_pass( 10.0, demod_rate, -19020, -18980, width_of_transition_band, gr.firdes.WIN_HAMMING) #print "len stereo carrier filter = ",len(stereo_carrier_filter_coeffs) #print "stereo carrier filter ", stereo_carrier_filter_coeffs #print "width of transition band = ",width_of_transition_band, " audio rate = ", audio_rate # Pick off the double side band suppressed carrier # Left-Right audio. It is attenuated 10 dB so apply 10 dB # gain stereo_dsbsc_filter_coeffs = gr.firdes.complex_band_pass( 20.0, demod_rate, 38000 - 15000 / 2, 38000 + 15000 / 2, width_of_transition_band, gr.firdes.WIN_HAMMING) #print "len stereo dsbsc filter = ",len(stereo_dsbsc_filter_coeffs) #print "stereo dsbsc filter ", stereo_dsbsc_filter_coeffs # construct overlap add filter system from coefficients # for stereo carrier self.stereo_carrier_filter = gr.fir_filter_fcc( audio_decimation, stereo_carrier_filter_coeffs) # carrier is twice the picked off carrier so arrange to do # a commplex multiply self.stereo_carrier_generator = gr.multiply_cc() # Pick off the rds signal stereo_rds_filter_coeffs = gr.firdes.complex_band_pass( 30.0, demod_rate, 57000 - 1500, 57000 + 1500, width_of_transition_band, gr.firdes.WIN_HAMMING) #print "len stereo dsbsc filter = ",len(stereo_dsbsc_filter_coeffs) #print "stereo dsbsc filter ", stereo_dsbsc_filter_coeffs # construct overlap add filter system from coefficients for stereo carrier self.rds_signal_filter = gr.fir_filter_fcc( audio_decimation, stereo_rds_filter_coeffs) self.rds_carrier_generator = gr.multiply_cc() self.rds_signal_generator = gr.multiply_cc() self_rds_signal_processor = gr.null_sink(gr.sizeof_gr_complex) loop_bw = 2 * math.pi / 100.0 max_freq = -2.0 * math.pi * 18990 / audio_rate min_freq = -2.0 * math.pi * 19010 / audio_rate self.stereo_carrier_pll_recovery = gr.pll_refout_cc( loop_bw, max_freq, min_freq) #self.stereo_carrier_pll_recovery.squelch_enable(False) ##pll_refout does not have squelch yet, so disabled for #now # set up mixer (multiplier) to get the L-R signal at # baseband self.stereo_basebander = gr.multiply_cc() # pick off the real component of the basebanded L-R # signal. The imaginary SHOULD be zero self.LmR_real = gr.complex_to_real() self.Make_Left = gr.add_ff() self.Make_Right = gr.sub_ff() self.stereo_dsbsc_filter = gr.fir_filter_fcc( audio_decimation, stereo_dsbsc_filter_coeffs) if 1: # send the real signal to complex filter to pick off the # carrier and then to one side of a multiplier self.connect(self, self.fm_demod, self.stereo_carrier_filter, self.stereo_carrier_pll_recovery, (self.stereo_carrier_generator, 0)) # send the already filtered carrier to the otherside of the carrier # the resulting signal from this multiplier is the carrier # with correct phase but at -38000 Hz. self.connect(self.stereo_carrier_pll_recovery, (self.stereo_carrier_generator, 1)) # send the new carrier to one side of the mixer (multiplier) self.connect(self.stereo_carrier_generator, (self.stereo_basebander, 0)) # send the demphasized audio to the DSBSC pick off filter, the complex # DSBSC signal at +38000 Hz is sent to the other side of the mixer/multiplier # the result is BASEBANDED DSBSC with phase zero! self.connect(self.fm_demod, self.stereo_dsbsc_filter, (self.stereo_basebander, 1)) # Pick off the real part since the imaginary is # theoretically zero and then to one side of a summer self.connect(self.stereo_basebander, self.LmR_real, (self.Make_Left, 0)) #take the same real part of the DSBSC baseband signal and #send it to negative side of a subtracter self.connect(self.LmR_real, (self.Make_Right, 1)) # Make rds carrier by taking the squared pilot tone and # multiplying by pilot tone self.connect(self.stereo_basebander, (self.rds_carrier_generator, 0)) self.connect(self.stereo_carrier_pll_recovery, (self.rds_carrier_generator, 1)) # take signal, filter off rds, send into mixer 0 channel self.connect(self.fm_demod, self.rds_signal_filter, (self.rds_signal_generator, 0)) # take rds_carrier_generator output and send into mixer 1 # channel self.connect(self.rds_carrier_generator, (self.rds_signal_generator, 1)) # send basebanded rds signal and send into "processor" # which for now is a null sink self.connect(self.rds_signal_generator, self_rds_signal_processor) if 1: # pick off the audio, L+R that is what we used to have and # send it to the summer self.connect(self.fm_demod, self.audio_filter, (self.Make_Left, 1)) # take the picked off L+R audio and send it to the PLUS # side of the subtractor self.connect(self.audio_filter, (self.Make_Right, 0)) # The result of Make_Left gets (L+R) + (L-R) and results in 2*L # The result of Make_Right gets (L+R) - (L-R) and results in 2*R self.connect(self.Make_Left, self.deemph_Left, (self, 0)) self.connect(self.Make_Right, self.deemph_Right, (self, 1))
def __init__(self, *args, **kwds): # begin wxGlade: MyFrame.__init__ kwds["style"] = wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) # Menu Bar self.frame_1_menubar = wx.MenuBar() self.SetMenuBar(self.frame_1_menubar) wxglade_tmp_menu = wx.Menu() self.Exit = wx.MenuItem(wxglade_tmp_menu, ID_EXIT, "Exit", "Exit", wx.ITEM_NORMAL) wxglade_tmp_menu.AppendItem(self.Exit) self.frame_1_menubar.Append(wxglade_tmp_menu, "File") # Menu Bar end self.panel_1 = wx.Panel(self, -1) self.button_1 = wx.Button(self, ID_BUTTON_1, "LSB") self.button_2 = wx.Button(self, ID_BUTTON_2, "USB") self.button_3 = wx.Button(self, ID_BUTTON_3, "AM") self.button_4 = wx.Button(self, ID_BUTTON_4, "CW") self.button_5 = wx.ToggleButton(self, ID_BUTTON_5, "Upper") self.slider_1 = wx.Slider(self, ID_SLIDER_1, 0, -15799, 15799, style=wx.SL_HORIZONTAL | wx.SL_LABELS) self.button_6 = wx.ToggleButton(self, ID_BUTTON_6, "Lower") self.slider_2 = wx.Slider(self, ID_SLIDER_2, 0, -15799, 15799, style=wx.SL_HORIZONTAL | wx.SL_LABELS) self.panel_5 = wx.Panel(self, -1) self.label_1 = wx.StaticText(self, -1, " Band\nCenter") self.text_ctrl_1 = wx.TextCtrl(self, ID_TEXT_1, "") self.panel_6 = wx.Panel(self, -1) self.panel_7 = wx.Panel(self, -1) self.panel_2 = wx.Panel(self, -1) self.button_7 = wx.ToggleButton(self, ID_BUTTON_7, "Freq") self.slider_3 = wx.Slider(self, ID_SLIDER_3, 3000, 0, 6000) self.spin_ctrl_1 = wx.SpinCtrl(self, ID_SPIN_1, "", min=0, max=100) self.button_8 = wx.ToggleButton(self, ID_BUTTON_8, "Vol") self.slider_4 = wx.Slider(self, ID_SLIDER_4, 0, 0, 500) self.slider_5 = wx.Slider(self, ID_SLIDER_5, 0, 0, 20) self.button_9 = wx.ToggleButton(self, ID_BUTTON_9, "Time") self.button_11 = wx.Button(self, ID_BUTTON_11, "Rew") self.button_10 = wx.Button(self, ID_BUTTON_10, "Fwd") self.panel_3 = wx.Panel(self, -1) self.label_2 = wx.StaticText(self, -1, "PGA ") self.panel_4 = wx.Panel(self, -1) self.panel_8 = wx.Panel(self, -1) self.panel_9 = wx.Panel(self, -1) self.label_3 = wx.StaticText(self, -1, "AM Sync\nCarrier") self.slider_6 = wx.Slider(self, ID_SLIDER_6, 50, 0, 200, style=wx.SL_HORIZONTAL | wx.SL_LABELS) self.label_4 = wx.StaticText(self, -1, "Antenna Tune") self.slider_7 = wx.Slider(self, ID_SLIDER_7, 1575, 950, 2200, style=wx.SL_HORIZONTAL | wx.SL_LABELS) self.panel_10 = wx.Panel(self, -1) self.button_12 = wx.ToggleButton(self, ID_BUTTON_12, "Auto Tune") self.button_13 = wx.Button(self, ID_BUTTON_13, "Calibrate") self.button_14 = wx.Button(self, ID_BUTTON_14, "Reset") self.panel_11 = wx.Panel(self, -1) self.panel_12 = wx.Panel(self, -1) self.__set_properties() self.__do_layout() # end wxGlade parser = OptionParser(option_class=eng_option) parser.add_option( "-c", "--ddc-freq", type="eng_float", default=3.9e6, help="set Rx DDC frequency to FREQ", metavar="FREQ" ) parser.add_option("-a", "--audio_file", default="", help="audio output file", metavar="FILE") parser.add_option("-r", "--radio_file", default="", help="radio output file", metavar="FILE") parser.add_option("-i", "--input_file", default="", help="radio input file", metavar="FILE") parser.add_option("-d", "--decim", type="int", default=250, help="USRP decimation") parser.add_option( "-R", "--rx-subdev-spec", type="subdev", default=None, help="select USRP Rx side A or B (default=first one with a daughterboard)", ) (options, args) = parser.parse_args() self.usrp_center = options.ddc_freq usb_rate = 64e6 / options.decim self.slider_range = usb_rate * 0.9375 self.f_lo = self.usrp_center - (self.slider_range / 2) self.f_hi = self.usrp_center + (self.slider_range / 2) self.af_sample_rate = 32000 fir_decim = long(usb_rate / self.af_sample_rate) # data point arrays for antenna tuner self.xdata = [] self.ydata = [] self.tb = gr.top_block() # radio variables, initial conditions self.frequency = self.usrp_center # these map the frequency slider (0-6000) to the actual range self.f_slider_offset = self.f_lo self.f_slider_scale = 10000 / options.decim self.spin_ctrl_1.SetRange(self.f_lo, self.f_hi) self.text_ctrl_1.SetValue(str(int(self.usrp_center))) self.slider_5.SetValue(0) self.AM_mode = False self.slider_3.SetValue((self.frequency - self.f_slider_offset) / self.f_slider_scale) self.spin_ctrl_1.SetValue(int(self.frequency)) POWERMATE = True try: self.pm = powermate.powermate(self) except: sys.stderr.write("Unable to find PowerMate or Contour Shuttle\n") POWERMATE = False if POWERMATE: powermate.EVT_POWERMATE_ROTATE(self, self.on_rotate) powermate.EVT_POWERMATE_BUTTON(self, self.on_pmButton) self.active_button = 7 # command line options if options.audio_file == "": SAVE_AUDIO_TO_FILE = False else: SAVE_AUDIO_TO_FILE = True if options.radio_file == "": SAVE_RADIO_TO_FILE = False else: SAVE_RADIO_TO_FILE = True if options.input_file == "": self.PLAY_FROM_USRP = True else: self.PLAY_FROM_USRP = False if self.PLAY_FROM_USRP: self.src = usrp.source_s(decim_rate=options.decim) if options.rx_subdev_spec is None: options.rx_subdev_spec = pick_subdevice(self.src) self.src.set_mux(usrp.determine_rx_mux_value(self.src, options.rx_subdev_spec)) self.subdev = usrp.selected_subdev(self.src, options.rx_subdev_spec) self.src.tune(0, self.subdev, self.usrp_center) self.tune_offset = 0 # -self.usrp_center - self.src.rx_freq(0) else: self.src = gr.file_source(gr.sizeof_short, options.input_file) self.tune_offset = 2200 # 2200 works for 3.5-4Mhz band # save radio data to a file if SAVE_RADIO_TO_FILE: file = gr.file_sink(gr.sizeof_short, options.radio_file) self.tb.connect(self.src, file) # 2nd DDC xlate_taps = gr.firdes.low_pass(1.0, usb_rate, 16e3, 4e3, gr.firdes.WIN_HAMMING) self.xlate = gr.freq_xlating_fir_filter_ccf(fir_decim, xlate_taps, self.tune_offset, usb_rate) # convert rf data in interleaved short int form to complex s2ss = gr.stream_to_streams(gr.sizeof_short, 2) s2f1 = gr.short_to_float() s2f2 = gr.short_to_float() src_f2c = gr.float_to_complex() self.tb.connect(self.src, s2ss) self.tb.connect((s2ss, 0), s2f1) self.tb.connect((s2ss, 1), s2f2) self.tb.connect(s2f1, (src_f2c, 0)) self.tb.connect(s2f2, (src_f2c, 1)) # Complex Audio filter audio_coeffs = gr.firdes.complex_band_pass( 1.0, # gain self.af_sample_rate, # sample rate -3000, # low cutoff 0, # high cutoff 100, # transition gr.firdes.WIN_HAMMING, ) # window self.slider_1.SetValue(0) self.slider_2.SetValue(-3000) self.audio_filter = gr.fir_filter_ccc(1, audio_coeffs) # Main +/- 16Khz spectrum display self.fft = fftsink2.fft_sink_c( self.panel_2, fft_size=512, sample_rate=self.af_sample_rate, average=True, size=(640, 240) ) # AM Sync carrier if AM_SYNC_DISPLAY: self.fft2 = fftsink.fft_sink_c( self.tb, self.panel_9, y_per_div=20, fft_size=512, sample_rate=self.af_sample_rate, average=True, size=(640, 240), ) c2f = gr.complex_to_float() # AM branch self.sel_am = gr.multiply_const_cc(0) # the following frequencies turn out to be in radians/sample # gr.pll_refout_cc(alpha,beta,min_freq,max_freq) # suggested alpha = X, beta = .25 * X * X pll = gr.pll_refout_cc( 0.5, 0.0625, (2.0 * math.pi * 7.5e3 / self.af_sample_rate), (2.0 * math.pi * 6.5e3 / self.af_sample_rate) ) self.pll_carrier_scale = gr.multiply_const_cc(complex(10, 0)) am_det = gr.multiply_cc() # these are for converting +7.5kHz to -7.5kHz # for some reason gr.conjugate_cc() adds noise ?? c2f2 = gr.complex_to_float() c2f3 = gr.complex_to_float() f2c = gr.float_to_complex() phaser1 = gr.multiply_const_ff(1) phaser2 = gr.multiply_const_ff(-1) # filter for pll generated carrier pll_carrier_coeffs = gr.firdes.complex_band_pass( 2.0, # gain self.af_sample_rate, # sample rate 7400, # low cutoff 7600, # high cutoff 100, # transition gr.firdes.WIN_HAMMING, ) # window self.pll_carrier_filter = gr.fir_filter_ccc(1, pll_carrier_coeffs) self.sel_sb = gr.multiply_const_ff(1) combine = gr.add_ff() # AGC sqr1 = gr.multiply_ff() intr = gr.iir_filter_ffd([0.004, 0], [0, 0.999]) offset = gr.add_const_ff(1) agc = gr.divide_ff() self.scale = gr.multiply_const_ff(0.00001) dst = audio.sink(long(self.af_sample_rate)) self.tb.connect(src_f2c, self.xlate, self.fft) self.tb.connect(self.xlate, self.audio_filter, self.sel_am, (am_det, 0)) self.tb.connect(self.sel_am, pll, self.pll_carrier_scale, self.pll_carrier_filter, c2f3) self.tb.connect((c2f3, 0), phaser1, (f2c, 0)) self.tb.connect((c2f3, 1), phaser2, (f2c, 1)) self.tb.connect(f2c, (am_det, 1)) self.tb.connect(am_det, c2f2, (combine, 0)) self.tb.connect(self.audio_filter, c2f, self.sel_sb, (combine, 1)) if AM_SYNC_DISPLAY: self.tb.connect(self.pll_carrier_filter, self.fft2) self.tb.connect(combine, self.scale) self.tb.connect(self.scale, (sqr1, 0)) self.tb.connect(self.scale, (sqr1, 1)) self.tb.connect(sqr1, intr, offset, (agc, 1)) self.tb.connect(self.scale, (agc, 0)) self.tb.connect(agc, dst) if SAVE_AUDIO_TO_FILE: f_out = gr.file_sink(gr.sizeof_short, options.audio_file) sc1 = gr.multiply_const_ff(64000) f2s1 = gr.float_to_short() self.tb.connect(agc, sc1, f2s1, f_out) self.tb.start() # for mouse position reporting on fft display em.eventManager.Register(self.Mouse, wx.EVT_MOTION, self.fft.win) # and left click to re-tune em.eventManager.Register(self.Click, wx.EVT_LEFT_DOWN, self.fft.win) # start a timer to check for web commands if WEB_CONTROL: self.timer = UpdateTimer(self, 1000) # every 1000 mSec, 1 Sec wx.EVT_BUTTON(self, ID_BUTTON_1, self.set_lsb) wx.EVT_BUTTON(self, ID_BUTTON_2, self.set_usb) wx.EVT_BUTTON(self, ID_BUTTON_3, self.set_am) wx.EVT_BUTTON(self, ID_BUTTON_4, self.set_cw) wx.EVT_BUTTON(self, ID_BUTTON_10, self.fwd) wx.EVT_BUTTON(self, ID_BUTTON_11, self.rew) wx.EVT_BUTTON(self, ID_BUTTON_13, self.AT_calibrate) wx.EVT_BUTTON(self, ID_BUTTON_14, self.AT_reset) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_5, self.on_button) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_6, self.on_button) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_7, self.on_button) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_8, self.on_button) wx.EVT_TOGGLEBUTTON(self, ID_BUTTON_9, self.on_button) wx.EVT_SLIDER(self, ID_SLIDER_1, self.set_filter) wx.EVT_SLIDER(self, ID_SLIDER_2, self.set_filter) wx.EVT_SLIDER(self, ID_SLIDER_3, self.slide_tune) wx.EVT_SLIDER(self, ID_SLIDER_4, self.set_volume) wx.EVT_SLIDER(self, ID_SLIDER_5, self.set_pga) wx.EVT_SLIDER(self, ID_SLIDER_6, self.am_carrier) wx.EVT_SLIDER(self, ID_SLIDER_7, self.antenna_tune) wx.EVT_SPINCTRL(self, ID_SPIN_1, self.spin_tune) wx.EVT_MENU(self, ID_EXIT, self.TimeToQuit)
def __init__ (self, fg, demod_rate, audio_decimation): """ Hierarchical block for demodulating a broadcast FM signal. The input is the downconverted complex baseband signal (gr_complex). The output is two streams of the demodulated audio (float) 0=Left, 1=Right. @param fg: flow graph. @type fg: flow graph @param demod_rate: input sample rate of complex baseband input. @type demod_rate: float @param audio_decimation: how much to decimate demod_rate to get to audio. @type audio_decimation: integer """ bandwidth = 200e3 audio_rate = demod_rate / audio_decimation # We assign to self so that outsiders can grab the demodulator # if they need to. E.g., to plot its output. # # input: complex; output: float alpha = 0.25*bandwidth * math.pi / demod_rate beta = alpha * alpha / 4.0 max_freq = 2.0*math.pi*100e3/demod_rate self.fm_demod = gr.pll_freqdet_cf (alpha,beta,max_freq,-max_freq) # input: float; output: float self.deemph_Left = fm_deemph (fg, audio_rate) self.deemph_Right = fm_deemph (fg, audio_rate) # compute FIR filter taps for audio filter width_of_transition_band = audio_rate / 32 audio_coeffs = gr.firdes.low_pass (1.0 , # gain demod_rate, # sampling rate 15000 , width_of_transition_band, gr.firdes.WIN_HAMMING) # input: float; output: float self.audio_filter = gr.fir_filter_fff (audio_decimation, audio_coeffs) if 1: # Pick off the stereo carrier/2 with this filter. It attenuated 10 dB so apply 10 dB gain # We pick off the negative frequency half because we want to base band by it! ## NOTE THIS WAS HACKED TO OFFSET INSERTION LOSS DUE TO DEEMPHASIS stereo_carrier_filter_coeffs = gr.firdes.complex_band_pass(10.0, demod_rate, -19020, -18980, width_of_transition_band, gr.firdes.WIN_HAMMING) #print "len stereo carrier filter = ",len(stereo_carrier_filter_coeffs) #print "stereo carrier filter ", stereo_carrier_filter_coeffs #print "width of transition band = ",width_of_transition_band, " audio rate = ", audio_rate # Pick off the double side band suppressed carrier Left-Right audio. It is attenuated 10 dB so apply 10 dB gain stereo_dsbsc_filter_coeffs = gr.firdes.complex_band_pass(20.0, demod_rate, 38000-15000/2, 38000+15000/2, width_of_transition_band, gr.firdes.WIN_HAMMING) #print "len stereo dsbsc filter = ",len(stereo_dsbsc_filter_coeffs) #print "stereo dsbsc filter ", stereo_dsbsc_filter_coeffs # construct overlap add filter system from coefficients for stereo carrier self.stereo_carrier_filter = gr.fir_filter_fcc(audio_decimation, stereo_carrier_filter_coeffs) # carrier is twice the picked off carrier so arrange to do a commplex multiply self.stereo_carrier_generator = gr.multiply_cc(); # Pick off the rds signal stereo_rds_filter_coeffs = gr.firdes.complex_band_pass(30.0, demod_rate, 57000 - 1500, 57000 + 1500, width_of_transition_band, gr.firdes.WIN_HAMMING) #print "len stereo dsbsc filter = ",len(stereo_dsbsc_filter_coeffs) #print "stereo dsbsc filter ", stereo_dsbsc_filter_coeffs # construct overlap add filter system from coefficients for stereo carrier self.stereo_carrier_filter = gr.fir_filter_fcc(audio_decimation, stereo_carrier_filter_coeffs) self.rds_signal_filter = gr.fir_filter_fcc(audio_decimation, stereo_rds_filter_coeffs) self.rds_carrier_generator = gr.multiply_cc(); self.rds_signal_generator = gr.multiply_cc(); self_rds_signal_processor = gr.null_sink(gr.sizeof_gr_complex); alpha = 5 * 0.25 * math.pi / (audio_rate) beta = alpha * alpha / 4.0 max_freq = -2.0*math.pi*18990/audio_rate; min_freq = -2.0*math.pi*19010/audio_rate; self.stereo_carrier_pll_recovery = gr.pll_refout_cc(alpha,beta,max_freq,min_freq); #self.stereo_carrier_pll_recovery.squelch_enable(False) #pll_refout does not have squelch yet, so disabled for now # set up mixer (multiplier) to get the L-R signal at baseband self.stereo_basebander = gr.multiply_cc(); # pick off the real component of the basebanded L-R signal. The imaginary SHOULD be zero self.LmR_real = gr.complex_to_real(); self.Make_Left = gr.add_ff(); self.Make_Right = gr.sub_ff(); self.stereo_dsbsc_filter = gr.fir_filter_fcc(audio_decimation, stereo_dsbsc_filter_coeffs) if 1: # send the real signal to complex filter to pick off the carrier and then to one side of a multiplier fg.connect (self.fm_demod,self.stereo_carrier_filter,self.stereo_carrier_pll_recovery, (self.stereo_carrier_generator,0)) # send the already filtered carrier to the otherside of the carrier fg.connect (self.stereo_carrier_pll_recovery, (self.stereo_carrier_generator,1)) # the resulting signal from this multiplier is the carrier with correct phase but at -38000 Hz. # send the new carrier to one side of the mixer (multiplier) fg.connect (self.stereo_carrier_generator, (self.stereo_basebander,0)) # send the demphasized audio to the DSBSC pick off filter, the complex # DSBSC signal at +38000 Hz is sent to the other side of the mixer/multiplier fg.connect (self.fm_demod,self.stereo_dsbsc_filter, (self.stereo_basebander,1)) # the result is BASEBANDED DSBSC with phase zero! # Pick off the real part since the imaginary is theoretically zero and then to one side of a summer fg.connect (self.stereo_basebander, self.LmR_real, (self.Make_Left,0)) #take the same real part of the DSBSC baseband signal and send it to negative side of a subtracter fg.connect (self.LmR_real,(self.Make_Right,1)) # Make rds carrier by taking the squared pilot tone and multiplying by pilot tone fg.connect (self.stereo_basebander,(self.rds_carrier_generator,0)) fg.connect (self.stereo_carrier_pll_recovery,(self.rds_carrier_generator,1)) # take signal, filter off rds, send into mixer 0 channel fg.connect (self.fm_demod,self.rds_signal_filter,(self.rds_signal_generator,0)) # take rds_carrier_generator output and send into mixer 1 channel fg.connect (self.rds_carrier_generator,(self.rds_signal_generator,1)) # send basebanded rds signal and send into "processor" which for now is a null sink fg.connect (self.rds_signal_generator,self_rds_signal_processor) if 1: # pick off the audio, L+R that is what we used to have and send it to the summer fg.connect(self.fm_demod, self.audio_filter, (self.Make_Left, 1)) # take the picked off L+R audio and send it to the PLUS side of the subtractor fg.connect(self.audio_filter,(self.Make_Right, 0)) # The result of Make_Left gets (L+R) + (L-R) and results in 2*L # The result of Make_Right gets (L+R) - (L-R) and results in 2*R # kludge the signals into a stereo channel kludge = gr.kludge_copy(gr.sizeof_float) fg.connect(self.Make_Left , self.deemph_Left, (kludge, 0)) fg.connect(self.Make_Right, self.deemph_Right, (kludge, 1)) #send it to the audio system gr.hier_block.__init__(self, fg, self.fm_demod, # head of the pipeline kludge) # tail of the pipeline else: fg.connect (self.fm_demod, self.audio_filter) gr.hier_block.__init__(self, fg, self.fm_demod, # head of the pipeline self.audio_filter) # tail of the pipeline
def test_pll_refout (self): expected_result = ((1+7.39965699825e-10j), (0.999980390072+0.00626518437639j), (0.999828696251+0.0185074284673j), (0.999342679977+0.0362518876791j), (0.998255133629+0.0590478181839j), (0.996255218983+0.0864609107375j), (0.993005692959+0.118066303432j), (0.988157629967+0.153442293406j), (0.981362581253+0.192165210843j), (0.972283244133+0.233806177974j), (0.960601866245+0.277928203344j), (0.946027755737+0.324085712433j), (0.928303182125+0.371824204922j), (0.907292485237+0.420500129461j), (0.882742881775+0.469856351614j), (0.854515135288+0.519426465034j), (0.822515428066+0.568742752075j), (0.786696314812+0.617340147495j), (0.747057616711+0.664759278297j), (0.703645646572+0.710551083088j), (0.656552672386+0.754280209541j), (0.605915129185+0.795529305935j), (0.551911592484+0.833902597427j), (0.494760006666+0.869029641151j), (0.43471455574+0.900568306446j), (0.37224894762+0.928132891655j), (0.30767711997+0.951490819454j), (0.241136431694+0.970491230488j), (0.172981828451+0.984925031662j), (0.103586450219+0.99462044239j), (0.0333373323083+0.999444127083j), (-0.0373690575361+0.999301552773j), (-0.108130030334+0.994136750698j), (-0.178540825844+0.983932495117j), (-0.248198583722+0.968709170818j), (-0.316705673933+0.948523879051j), (-0.383672952652+0.923469007015j), (-0.448723316193+0.893670737743j), (-0.51132196188+0.85938924551j), (-0.571328520775+0.820721447468j), (-0.628420114517+0.777874112129j), (-0.682293117046+0.73107868433j), (-0.732665538788+0.680588841438j), (-0.779277384281+0.626679122448j), (-0.821892917156+0.569642007351j), (-0.860301196575+0.509786069393j), (-0.894317150116+0.447433561087j), (-0.923782229424+0.382918298244j), (-0.948564887047+0.316582858562j), (-0.968560874462+0.248776733875j), (-0.983657121658+0.180051699281j), (-0.993847966194+0.110753215849j), (-0.999158322811+0.0410195216537j), (-0.999585151672-0.0288011860102j), (-0.995150566101-0.0983632653952j), (-0.985901713371-0.16732545197j), (-0.971909940243-0.235353127122j), (-0.953270018101-0.302119642496j), (-0.9300994277-0.367307811975j), (-0.902537107468-0.430612027645j), (-0.870742559433-0.49173912406j), (-0.834894418716-0.550410091877j), (-0.795189499855-0.606360971928j), (-0.751972675323-0.659194231033j), (-0.705345034599-0.708864152431j), (-0.65554022789-0.755160272121j), (-0.602804005146-0.79788929224j), (-0.547393083572-0.836875617504j), (-0.489574223757-0.871961653233j), (-0.429622590542-0.903008520603j), (-0.367820799351-0.929896712303j), (-0.30445766449-0.952525854111j), (-0.239826664329-0.970815718174j), (-0.174224823713-0.984705924988j), (-0.107951194048-0.994156181812j), (-0.0415063276887-0.999138236046j), (0.0248276274651-0.999691724777j), (0.0909758731723-0.995853126049j), (0.156649470329-0.987654268742j), (0.221562758088-0.975146114826j), (0.285434871912-0.958398103714j), (0.347990810871-0.937497973442j), (0.408962905407-0.912550985813j), (0.468091338873-0.883680105209j), (0.525126338005-0.851024270058j), (0.57982814312-0.814738810062j), (0.631968915462-0.77499371767j), (0.681333422661-0.731973171234j), (0.727582573891-0.68602013588j), (0.770699381828-0.637198925018j), (0.810512244701-0.585721731186j), (0.846863090992-0.531810998917j), (0.879608631134-0.475698113441j), (0.908620357513-0.417623132467j), (0.933785498142-0.357833325863j), (0.955007195473-0.296582698822j), (0.972205162048-0.234130680561j), (0.985315918922-0.170741200447j), (0.994293272495-0.106681488454j), (0.999108314514-0.0422209985554j)) sampling_freq = 10e3 freq = sampling_freq / 100 alpha = 0.1 beta = alpha * alpha / 4.0 maxf = 1 minf = -1 src = gr.sig_source_c (sampling_freq, gr.GR_COS_WAVE, freq, 1.0) pll = gr.pll_refout_cc(alpha, beta, maxf, minf) head = gr.head (gr.sizeof_gr_complex, int (freq)) dst = gr.vector_sink_c () self.tb.connect (src, pll, head) self.tb.connect (head, dst) self.tb.run () dst_data = dst.data () self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 5)
def test_pll_refout(self): expected_result = ( (1 + 7.39965699825e-10j), (0.999980390072 + 0.00626518437639j), (0.999828696251 + 0.0185074284673j), (0.999342679977 + 0.0362518876791j), (0.998255133629 + 0.0590478181839j), (0.996255218983 + 0.0864609107375j), (0.993005692959 + 0.118066303432j), (0.988157629967 + 0.153442293406j), (0.981362581253 + 0.192165210843j), (0.972283244133 + 0.233806177974j), (0.960601866245 + 0.277928203344j), (0.946027755737 + 0.324085712433j), (0.928303182125 + 0.371824204922j), (0.907292485237 + 0.420500129461j), (0.882742881775 + 0.469856351614j), (0.854515135288 + 0.519426465034j), (0.822515428066 + 0.568742752075j), (0.786696314812 + 0.617340147495j), (0.747057616711 + 0.664759278297j), (0.703645646572 + 0.710551083088j), (0.656552672386 + 0.754280209541j), (0.605915129185 + 0.795529305935j), (0.551911592484 + 0.833902597427j), (0.494760006666 + 0.869029641151j), (0.43471455574 + 0.900568306446j), (0.37224894762 + 0.928132891655j), (0.30767711997 + 0.951490819454j), (0.241136431694 + 0.970491230488j), (0.172981828451 + 0.984925031662j), (0.103586450219 + 0.99462044239j), (0.0333373323083 + 0.999444127083j), (-0.0373690575361 + 0.999301552773j), (-0.108130030334 + 0.994136750698j), (-0.178540825844 + 0.983932495117j), (-0.248198583722 + 0.968709170818j), (-0.316705673933 + 0.948523879051j), (-0.383672952652 + 0.923469007015j), (-0.448723316193 + 0.893670737743j), (-0.51132196188 + 0.85938924551j), (-0.571328520775 + 0.820721447468j), (-0.628420114517 + 0.777874112129j), (-0.682293117046 + 0.73107868433j), (-0.732665538788 + 0.680588841438j), (-0.779277384281 + 0.626679122448j), (-0.821892917156 + 0.569642007351j), (-0.860301196575 + 0.509786069393j), (-0.894317150116 + 0.447433561087j), (-0.923782229424 + 0.382918298244j), (-0.948564887047 + 0.316582858562j), (-0.968560874462 + 0.248776733875j), (-0.983657121658 + 0.180051699281j), (-0.993847966194 + 0.110753215849j), (-0.999158322811 + 0.0410195216537j), (-0.999585151672 - 0.0288011860102j), (-0.995150566101 - 0.0983632653952j), (-0.985901713371 - 0.16732545197j), (-0.971909940243 - 0.235353127122j), (-0.953270018101 - 0.302119642496j), (-0.9300994277 - 0.367307811975j), (-0.902537107468 - 0.430612027645j), (-0.870742559433 - 0.49173912406j), (-0.834894418716 - 0.550410091877j), (-0.795189499855 - 0.606360971928j), (-0.751972675323 - 0.659194231033j), (-0.705345034599 - 0.708864152431j), (-0.65554022789 - 0.755160272121j), (-0.602804005146 - 0.79788929224j), (-0.547393083572 - 0.836875617504j), (-0.489574223757 - 0.871961653233j), (-0.429622590542 - 0.903008520603j), (-0.367820799351 - 0.929896712303j), (-0.30445766449 - 0.952525854111j), (-0.239826664329 - 0.970815718174j), (-0.174224823713 - 0.984705924988j), (-0.107951194048 - 0.994156181812j), (-0.0415063276887 - 0.999138236046j), (0.0248276274651 - 0.999691724777j), (0.0909758731723 - 0.995853126049j), (0.156649470329 - 0.987654268742j), (0.221562758088 - 0.975146114826j), (0.285434871912 - 0.958398103714j), (0.347990810871 - 0.937497973442j), (0.408962905407 - 0.912550985813j), (0.468091338873 - 0.883680105209j), (0.525126338005 - 0.851024270058j), (0.57982814312 - 0.814738810062j), (0.631968915462 - 0.77499371767j), (0.681333422661 - 0.731973171234j), (0.727582573891 - 0.68602013588j), (0.770699381828 - 0.637198925018j), (0.810512244701 - 0.585721731186j), (0.846863090992 - 0.531810998917j), (0.879608631134 - 0.475698113441j), (0.908620357513 - 0.417623132467j), (0.933785498142 - 0.357833325863j), (0.955007195473 - 0.296582698822j), (0.972205162048 - 0.234130680561j), (0.985315918922 - 0.170741200447j), (0.994293272495 - 0.106681488454j), (0.999108314514 - 0.0422209985554j)) sampling_freq = 10e3 freq = sampling_freq / 100 alpha = 0.1 beta = alpha * alpha / 4.0 maxf = 1 minf = -1 src = gr.sig_source_c(sampling_freq, gr.GR_COS_WAVE, freq, 1.0) pll = gr.pll_refout_cc(alpha, beta, maxf, minf) head = gr.head(gr.sizeof_gr_complex, int(freq)) dst = gr.vector_sink_c() self.tb.connect(src, pll, head) self.tb.connect(head, dst) self.tb.run() dst_data = dst.data() self.assertComplexTuplesAlmostEqual(expected_result, dst_data, 5)
def test_pll_refout (self): expected_result = ((1+0j), (1+6.4087357643e-10j), (0.999985277653+0.00542619498447j), (0.999868750572+0.0162021834403j), (0.99948567152+0.0320679470897j), (0.99860727787+0.0527590736747j), (0.996953129768+0.0780025869608j), (0.994203746319+0.107512556016j), (0.990011692047+0.140985429287j), (0.984013140202+0.178095817566j), (0.975838363171+0.218493551016j), (0.965121984482+0.261800557375j), (0.95151245594+0.307610183954j), (0.934681296349+0.355486690998j), (0.914401650429+0.404808044434j), (0.890356600285+0.455263823271j), (0.862329125404+0.506348133087j), (0.830152392387+0.557536482811j), (0.793714106083+0.608290970325j), (0.752960026264+0.658066213131j), (0.707896590233+0.706316053867j), (0.658591926098+0.752500295639j), (0.605175673962+0.796091973782j), (0.547837555408+0.836584687233j), (0.48682525754+0.873499393463j), (0.42244040966+0.906390726566j), (0.355197101831+0.934791445732j), (0.285494059324+0.958380460739j), (0.213591173291+0.976923108101j), (0.139945343137+0.990159213543j), (0.065038472414+0.997882783413j), (-0.0106285437942+0.999943494797j), (-0.0865436866879+0.996248066425j), (-0.162189796567+0.986759603024j), (-0.23705175519+0.971496999264j), (-0.310622543097+0.950533330441j), (-0.38240903616+0.923993110657j), (-0.451937526464+0.89204955101j), (-0.518758952618+0.854920566082j), (-0.582311093807+0.812966048717j), (-0.642372369766+0.76639264822j), (-0.698591887951+0.715520322323j), (-0.750654160976+0.660695314407j), (-0.798280358315+0.602286040783j), (-0.841228663921+0.540679454803j), (-0.87929558754+0.476276367903j), (-0.912315964699+0.409486919641j), (-0.940161883831+0.340728074312j), (-0.962742805481+0.270418733358j), (-0.980004072189+0.198977485299j), (-0.991925954819+0.126818284392j), (-0.99851256609+0.0545223206282j), (-0.999846458435-0.0175215266645j), (-0.996021270752-0.0891158208251j), (-0.987133920193-0.159895718098j), (-0.973306238651-0.2295101583j), (-0.954683184624-0.297624111176j), (-0.931430280209-0.363919824362j), (-0.903732538223-0.428097635508j), (-0.871792256832-0.489875763655j), (-0.835827112198-0.548992812634j), (-0.796068251133-0.605206847191j), (-0.752758979797-0.658296227455j), (-0.706152498722-0.70805978775j), (-0.656641483307-0.754202902317j), (-0.604367733002-0.79670548439j), (-0.549597978592-0.835429251194j), (-0.492602348328-0.870254516602j), (-0.433654457331-0.901079237461j), (-0.373029649258-0.927819430828j), (-0.31100410223-0.950408577919j), (-0.247853919864-0.968797445297j), (-0.183855071664-0.982953369617j), (-0.119282215834-0.992860376835j), (-0.0544078871608-0.998518764973j), (0.0104992967099-0.999944865704j), (0.0749994292855-0.997183561325j), (0.138844624162-0.990314185619j), (0.201967850327-0.979392170906j), (0.264124274254-0.964488625526j), (0.325075358152-0.945688128471j), (0.3845885396-0.92308807373j), (0.442438393831-0.89679890871j), (0.498407125473-0.866943061352j), (0.552284479141-0.833655714989j), (0.603869199753-0.797083437443j), (0.652970373631-0.757383465767j), (0.69940674305-0.714723825455j), (0.743007957935-0.66928255558j), (0.78350687027-0.62138313055j), (0.820889055729-0.571087777615j), (0.855021059513-0.51859331131j), (0.885780930519-0.46410369873j), (0.913058102131-0.407829582691j), (0.936754107475-0.349988251925j), (0.956783294678-0.290801793337j), (0.973072886467-0.230497643352j), (0.985563337803-0.169307261705j), (0.9942086339-0.1074674353j), (0.9989772439-0.0452152714133j)) sampling_freq = 10e3 freq = sampling_freq / 100 loop_bw = math.pi/100.0 maxf = 1 minf = -1 src = gr.sig_source_c (sampling_freq, gr.GR_COS_WAVE, freq, 1.0) pll = gr.pll_refout_cc(loop_bw, maxf, minf) head = gr.head (gr.sizeof_gr_complex, int (freq)) dst = gr.vector_sink_c () self.tb.connect (src, pll, head) self.tb.connect (head, dst) self.tb.run () dst_data = dst.data () self.assertComplexTuplesAlmostEqual (expected_result, dst_data, 4)
def __init__ (self, demod_rate, audio_decimation): """ Hierarchical block for demodulating a broadcast FM signal. The input is the downconverted complex baseband signal (gr_complex). The output is two streams of the demodulated audio (float) 0=Left, 1=Right. Args: demod_rate: input sample rate of complex baseband input. (float) audio_decimation: how much to decimate demod_rate to get to audio. (integer) """ gr.hier_block2.__init__(self, "wfm_rcv_fmdet", gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature gr.io_signature(2, 2, gr.sizeof_float)) # Output signature lowfreq = -125e3/demod_rate highfreq = 125e3/demod_rate audio_rate = demod_rate / audio_decimation # We assign to self so that outsiders can grab the demodulator # if they need to. E.g., to plot its output. # # input: complex; output: float self.fm_demod = gr.fmdet_cf (demod_rate, lowfreq, highfreq, 0.05) # input: float; output: float self.deemph_Left = fm_deemph (audio_rate) self.deemph_Right = fm_deemph (audio_rate) # compute FIR filter taps for audio filter width_of_transition_band = audio_rate / 32 audio_coeffs = gr.firdes.low_pass (1.0 , # gain demod_rate, # sampling rate 15000 , width_of_transition_band, gr.firdes.WIN_HAMMING) # input: float; output: float self.audio_filter = gr.fir_filter_fff (audio_decimation, audio_coeffs) if 1: # Pick off the stereo carrier/2 with this filter. It # attenuated 10 dB so apply 10 dB gain We pick off the # negative frequency half because we want to base band by # it! ## NOTE THIS WAS HACKED TO OFFSET INSERTION LOSS DUE TO ## DEEMPHASIS stereo_carrier_filter_coeffs = gr.firdes.complex_band_pass(10.0, demod_rate, -19020, -18980, width_of_transition_band, gr.firdes.WIN_HAMMING) #print "len stereo carrier filter = ",len(stereo_carrier_filter_coeffs) #print "stereo carrier filter ", stereo_carrier_filter_coeffs #print "width of transition band = ",width_of_transition_band, " audio rate = ", audio_rate # Pick off the double side band suppressed carrier # Left-Right audio. It is attenuated 10 dB so apply 10 dB # gain stereo_dsbsc_filter_coeffs = gr.firdes.complex_band_pass(20.0, demod_rate, 38000-15000/2, 38000+15000/2, width_of_transition_band, gr.firdes.WIN_HAMMING) #print "len stereo dsbsc filter = ",len(stereo_dsbsc_filter_coeffs) #print "stereo dsbsc filter ", stereo_dsbsc_filter_coeffs # construct overlap add filter system from coefficients # for stereo carrier self.stereo_carrier_filter = gr.fir_filter_fcc(audio_decimation, stereo_carrier_filter_coeffs) # carrier is twice the picked off carrier so arrange to do # a commplex multiply self.stereo_carrier_generator = gr.multiply_cc(); # Pick off the rds signal stereo_rds_filter_coeffs = gr.firdes.complex_band_pass(30.0, demod_rate, 57000 - 1500, 57000 + 1500, width_of_transition_band, gr.firdes.WIN_HAMMING) #print "len stereo dsbsc filter = ",len(stereo_dsbsc_filter_coeffs) #print "stereo dsbsc filter ", stereo_dsbsc_filter_coeffs # construct overlap add filter system from coefficients for stereo carrier self.rds_signal_filter = gr.fir_filter_fcc(audio_decimation, stereo_rds_filter_coeffs) self.rds_carrier_generator = gr.multiply_cc(); self.rds_signal_generator = gr.multiply_cc(); self_rds_signal_processor = gr.null_sink(gr.sizeof_gr_complex); loop_bw = 2*math.pi/100.0 max_freq = -2.0*math.pi*18990/audio_rate; min_freq = -2.0*math.pi*19010/audio_rate; self.stereo_carrier_pll_recovery = gr.pll_refout_cc(loop_bw, max_freq, min_freq); #self.stereo_carrier_pll_recovery.squelch_enable(False) ##pll_refout does not have squelch yet, so disabled for #now # set up mixer (multiplier) to get the L-R signal at # baseband self.stereo_basebander = gr.multiply_cc(); # pick off the real component of the basebanded L-R # signal. The imaginary SHOULD be zero self.LmR_real = gr.complex_to_real(); self.Make_Left = gr.add_ff(); self.Make_Right = gr.sub_ff(); self.stereo_dsbsc_filter = gr.fir_filter_fcc(audio_decimation, stereo_dsbsc_filter_coeffs) if 1: # send the real signal to complex filter to pick off the # carrier and then to one side of a multiplier self.connect (self, self.fm_demod, self.stereo_carrier_filter, self.stereo_carrier_pll_recovery, (self.stereo_carrier_generator,0)) # send the already filtered carrier to the otherside of the carrier # the resulting signal from this multiplier is the carrier # with correct phase but at -38000 Hz. self.connect (self.stereo_carrier_pll_recovery, (self.stereo_carrier_generator,1)) # send the new carrier to one side of the mixer (multiplier) self.connect (self.stereo_carrier_generator, (self.stereo_basebander,0)) # send the demphasized audio to the DSBSC pick off filter, the complex # DSBSC signal at +38000 Hz is sent to the other side of the mixer/multiplier # the result is BASEBANDED DSBSC with phase zero! self.connect (self.fm_demod,self.stereo_dsbsc_filter, (self.stereo_basebander,1)) # Pick off the real part since the imaginary is # theoretically zero and then to one side of a summer self.connect (self.stereo_basebander, self.LmR_real, (self.Make_Left,0)) #take the same real part of the DSBSC baseband signal and #send it to negative side of a subtracter self.connect (self.LmR_real,(self.Make_Right,1)) # Make rds carrier by taking the squared pilot tone and # multiplying by pilot tone self.connect (self.stereo_basebander,(self.rds_carrier_generator,0)) self.connect (self.stereo_carrier_pll_recovery,(self.rds_carrier_generator,1)) # take signal, filter off rds, send into mixer 0 channel self.connect (self.fm_demod,self.rds_signal_filter,(self.rds_signal_generator,0)) # take rds_carrier_generator output and send into mixer 1 # channel self.connect (self.rds_carrier_generator,(self.rds_signal_generator,1)) # send basebanded rds signal and send into "processor" # which for now is a null sink self.connect (self.rds_signal_generator,self_rds_signal_processor) if 1: # pick off the audio, L+R that is what we used to have and # send it to the summer self.connect(self.fm_demod, self.audio_filter, (self.Make_Left, 1)) # take the picked off L+R audio and send it to the PLUS # side of the subtractor self.connect(self.audio_filter,(self.Make_Right, 0)) # The result of Make_Left gets (L+R) + (L-R) and results in 2*L # The result of Make_Right gets (L+R) - (L-R) and results in 2*R self.connect(self.Make_Left , self.deemph_Left, (self, 0)) self.connect(self.Make_Right, self.deemph_Right, (self, 1))