diff options
Diffstat (limited to 'gr-analog/python/analog')
30 files changed, 3360 insertions, 0 deletions
diff --git a/gr-analog/python/analog/CMakeLists.txt b/gr-analog/python/analog/CMakeLists.txt new file mode 100644 index 0000000000..182fc24860 --- /dev/null +++ b/gr-analog/python/analog/CMakeLists.txt @@ -0,0 +1,61 @@ +# Copyright 212 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +######################################################################## +# Setup python install +######################################################################## +include(GrPython) + +GR_PYTHON_INSTALL( + FILES + __init__.py + am_demod.py + fm_demod.py + fm_emph.py + nbfm_rx.py + nbfm_tx.py + standard_squelch.py + wfm_rcv.py + wfm_rcv_fmdet.py + wfm_rcv_pll.py + wfm_tx.py + DESTINATION ${GR_PYTHON_DIR}/gnuradio/analog + COMPONENT "analog_python" +) + +######################################################################## +# Handle the unit tests +######################################################################## +if(ENABLE_TESTING) + + set(GR_TEST_TARGET_DEPS "") + set(GR_TEST_LIBRARY_DIRS "") + set(GR_TEST_PYTHON_DIRS + ${CMAKE_BINARY_DIR}/gruel/src/python + ${CMAKE_BINARY_DIR}/gnuradio-core/src/python + ) + + include(GrTest) + file(GLOB py_qa_test_files "qa_*.py") + foreach(py_qa_test_file ${py_qa_test_files}) + get_filename_component(py_qa_test_name ${py_qa_test_file} NAME_WE) + GR_ADD_TEST(${py_qa_test_name} ${PYTHON_EXECUTABLE} ${PYTHON_DASH_B} ${py_qa_test_file}) + endforeach(py_qa_test_file) +endif(ENABLE_TESTING) + diff --git a/gr-analog/python/analog/__init__.py b/gr-analog/python/analog/__init__.py new file mode 100644 index 0000000000..836d4ba935 --- /dev/null +++ b/gr-analog/python/analog/__init__.py @@ -0,0 +1,44 @@ +# Copyright 2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +''' +Blocks and utilities for analog modulation and demodulation. +''' + +# The presence of this file turns this directory into a Python package +import os + +try: + from analog_swig import * +except ImportError: + dirname, filename = os.path.split(os.path.abspath(__file__)) + __path__.append(os.path.join(dirname, "..", "..", "swig")) + from analog_swig import * + +from am_demod import * +from fm_demod import * +from fm_emph import * +from nbfm_rx import * +from nbfm_tx import * +from standard_squelch import * +from wfm_rcv import * +from wfm_rcv_fmdet import * +from wfm_rcv_pll import * +from wfm_tx import * diff --git a/gr-analog/python/analog/am_demod.py b/gr-analog/python/analog/am_demod.py new file mode 100644 index 0000000000..3459e825f4 --- /dev/null +++ b/gr-analog/python/analog/am_demod.py @@ -0,0 +1,73 @@ +# +# Copyright 2006,2007,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr +from gnuradio import blocks +from gnuradio import filter + +class am_demod_cf(gr.hier_block2): + """ + Generalized AM demodulation block with audio filtering. + + This block demodulates a band-limited, complex down-converted AM + channel into the the original baseband signal, applying low pass + filtering to the audio output. It produces a float stream in the + range [-1.0, +1.0]. + + Args: + channel_rate: incoming sample rate of the AM baseband (integer) + audio_decim: input to output decimation rate (integer) + audio_pass: audio low pass filter passband frequency (float) + audio_stop: audio low pass filter stop frequency (float) + """ + def __init__(self, channel_rate, audio_decim, audio_pass, audio_stop): + gr.hier_block2.__init__(self, "am_demod_cf", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature(1, 1, gr.sizeof_float)) # Input signature + + MAG = blocks.complex_to_mag() + DCR = blocks.add_const_ff(-1.0) + + audio_taps = filter.optfir.low_pass(0.5, # Filter gain + channel_rate, # Sample rate + audio_pass, # Audio passband + audio_stop, # Audio stopband + 0.1, # Passband ripple + 60) # Stopband attenuation + LPF = filter.fir_filter_fff(audio_decim, audio_taps) + + self.connect(self, MAG, DCR, LPF, self) + +class demod_10k0a3e_cf(am_demod_cf): + """ + AM demodulation block, 10 KHz channel. + + This block demodulates an AM channel conformant to 10K0A3E emission + standards, such as broadcast band AM transmissions. + + Args: + channel_rate: incoming sample rate of the AM baseband (integer) + audio_decim: input to output decimation rate (integer) + """ + def __init__(self, channel_rate, audio_decim): + am_demod_cf.__init__(self, channel_rate, audio_decim, + 5000, # Audio passband + 5500) # Audio stopband diff --git a/gr-analog/python/analog/fm_demod.py b/gr-analog/python/analog/fm_demod.py new file mode 100644 index 0000000000..1976a076ca --- /dev/null +++ b/gr-analog/python/analog/fm_demod.py @@ -0,0 +1,108 @@ +# +# Copyright 2006,2007,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, filter +from fm_emph import fm_deemph +from math import pi + +try: + from gnuradio import analog +except ImportError: + import analog_swig as analog + +class fm_demod_cf(gr.hier_block2): + """ + Generalized FM demodulation block with deemphasis and audio + filtering. + + This block demodulates a band-limited, complex down-converted FM + channel into the the original baseband signal, optionally applying + deemphasis. Low pass filtering is done on the resultant signal. It + produces an output float strem in the range of [-1.0, +1.0]. + + Args: + channel_rate: incoming sample rate of the FM baseband (integer) + deviation: maximum FM deviation (default = 5000) (float) + audio_decim: input to output decimation rate (integer) + audio_pass: audio low pass filter passband frequency (float) + audio_stop: audio low pass filter stop frequency (float) + gain: gain applied to audio output (default = 1.0) (float) + tau: deemphasis time constant (default = 75e-6), specify 'None' to prevent deemphasis + """ + def __init__(self, channel_rate, audio_decim, deviation, + audio_pass, audio_stop, gain=1.0, tau=75e-6): + gr.hier_block2.__init__(self, "fm_demod_cf", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature(1, 1, gr.sizeof_float)) # Output signature + + k = channel_rate/(2*pi*deviation) + QUAD = analog.quadrature_demod_cf(k) + + audio_taps = filter.optfir.low_pass(gain, # Filter gain + channel_rate, # Sample rate + audio_pass, # Audio passband + audio_stop, # Audio stopband + 0.1, # Passband ripple + 60) # Stopband attenuation + LPF = filter.fir_filter_fff(audio_decim, audio_taps) + + if tau is not None: + DEEMPH = fm_deemph(channel_rate, tau) + self.connect(self, QUAD, DEEMPH, LPF, self) + else: + self.connect(self, QUAD, LPF, self) + +class demod_20k0f3e_cf(fm_demod_cf): + """ + NBFM demodulation block, 20 KHz channels + + This block demodulates a complex, downconverted, narrowband FM + channel conforming to 20K0F3E emission standards, outputting + floats in the range [-1.0, +1.0]. + + Args: + sample_rate: incoming sample rate of the FM baseband (integer) + audio_decim: input to output decimation rate (integer) + """ + def __init__(self, channel_rate, audio_decim): + fm_demod_cf.__init__(self, channel_rate, audio_decim, + 5000, # Deviation + 3000, # Audio passband frequency + 4500) # Audio stopband frequency + +class demod_200kf3e_cf(fm_demod_cf): + """ + WFM demodulation block, mono. + + This block demodulates a complex, downconverted, wideband FM + channel conforming to 200KF3E emission standards, outputting + floats in the range [-1.0, +1.0]. + + Args: + sample_rate: incoming sample rate of the FM baseband (integer) + audio_decim: input to output decimation rate (integer) + """ + def __init__(self, channel_rate, audio_decim): + fm_demod_cf.__init__(self, channel_rate, audio_decim, + 75000, # Deviation + 15000, # Audio passband + 16000, # Audio stopband + 20.0) # Audio gain diff --git a/gr-analog/python/analog/fm_emph.py b/gr-analog/python/analog/fm_emph.py new file mode 100644 index 0000000000..2821f6e3cd --- /dev/null +++ b/gr-analog/python/analog/fm_emph.py @@ -0,0 +1,150 @@ +# +# Copyright 2005,2007,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, filter +import math + +# +# 1 +# H(s) = ------- +# 1 + s +# +# tau is the RC time constant. +# critical frequency: w_p = 1/tau +# +# We prewarp and use the bilinear z-transform to get our IIR coefficients. +# See "Digital Signal Processing: A Practical Approach" by Ifeachor and Jervis +# + +class fm_deemph(gr.hier_block2): + """ + FM Deemphasis IIR filter. + """ + + + def __init__(self, fs, tau=75e-6): + """ + + Args: + fs: sampling frequency in Hz (float) + tau: Time constant in seconds (75us in US, 50us in EUR) (float) + """ + gr.hier_block2.__init__(self, "fm_deemph", + gr.io_signature(1, 1, gr.sizeof_float), # Input signature + gr.io_signature(1, 1, gr.sizeof_float)) # Output signature + + w_p = 1/tau + w_pp = math.tan(w_p / (fs * 2)) # prewarped analog freq + + a1 = (w_pp - 1)/(w_pp + 1) + b0 = w_pp/(1 + w_pp) + b1 = b0 + + btaps = [b0, b1] + ataps = [1, a1] + + if 0: + print "btaps =", btaps + print "ataps =", ataps + global plot1 + plot1 = gru.gnuplot_freqz(gru.freqz(btaps, ataps), fs, True) + + deemph = filter.iir_filter_ffd(btaps, ataps) + self.connect(self, deemph, self) + +# +# 1 + s*t1 +# H(s) = ---------- +# 1 + s*t2 +# +# I think this is the right transfer function. +# +# +# This fine ASCII rendition is based on Figure 5-15 +# in "Digital and Analog Communication Systems", Leon W. Couch II +# +# +# R1 +# +-----||------+ +# | | +# o------+ +-----+--------o +# | C1 | | +# +----/\/\/\/--+ \ +# / +# \ R2 +# / +# \ +# | +# o--------------------------+--------o +# +# f1 = 1/(2*pi*t1) = 1/(2*pi*R1*C) +# +# 1 R1 + R2 +# f2 = ------- = ------------ +# 2*pi*t2 2*pi*R1*R2*C +# +# t1 is 75us in US, 50us in EUR +# f2 should be higher than our audio bandwidth. +# +# +# The Bode plot looks like this: +# +# +# /---------------- +# / +# / <-- slope = 20dB/decade +# / +# -------------/ +# f1 f2 +# +# We prewarp and use the bilinear z-transform to get our IIR coefficients. +# See "Digital Signal Processing: A Practical Approach" by Ifeachor and Jervis +# + +class fm_preemph(gr.hier_block2): + """ + FM Preemphasis IIR filter. + """ + def __init__(self, fs, tau=75e-6): + """ + + Args: + fs: sampling frequency in Hz (float) + tau: Time constant in seconds (75us in US, 50us in EUR) (float) + """ + + gr.hier_block2.__init__(self, "fm_deemph", + gr.io_signature(1, 1, gr.sizeof_float), # Input signature + gr.io_signature(1, 1, gr.sizeof_float)) # Output signature + + # FIXME make this compute the right answer + + btaps = [1] + ataps = [1] + + if 0: + print "btaps =", btaps + print "ataps =", ataps + global plot2 + plot2 = gru.gnuplot_freqz(gru.freqz(btaps, ataps), fs, True) + + preemph = filter.iir_filter_ffd(btaps, ataps) + self.connect(self, preemph, self) diff --git a/gr-analog/python/analog/nbfm_rx.py b/gr-analog/python/analog/nbfm_rx.py new file mode 100644 index 0000000000..b2c86db70f --- /dev/null +++ b/gr-analog/python/analog/nbfm_rx.py @@ -0,0 +1,90 @@ +# +# Copyright 2005,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math +from gnuradio import gr +from gnuradio import filter +from fm_emph import fm_deemph + +try: + from gnuradio import analog +except ImportError: + import analog_swig as analog + +class nbfm_rx(gr.hier_block2): + def __init__(self, audio_rate, quad_rate, tau=75e-6, max_dev=5e3): + """ + Narrow Band FM Receiver. + + Takes a single complex baseband input stream and produces a single + float output stream of audio sample in the range [-1, +1]. + + Args: + audio_rate: sample rate of audio stream, >= 16k (integer) + quad_rate: sample rate of output stream (integer) + tau: preemphasis time constant (default 75e-6) (float) + max_dev: maximum deviation in Hz (default 5e3) (float) + + quad_rate must be an integer multiple of audio_rate. + + Exported sub-blocks (attributes): + squelch + quad_demod + deemph + audio_filter + """ + + gr.hier_block2.__init__(self, "nbfm_rx", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature(1, 1, gr.sizeof_float)) # Output signature + + # FIXME audio_rate and quad_rate ought to be exact rationals + audio_rate = int(audio_rate) + quad_rate = int(quad_rate) + + if quad_rate % audio_rate != 0: + raise ValueError, "quad_rate is not an integer multiple of audio_rate" + + squelch_threshold = 20 # dB + #self.squelch = analog.simple_squelch_cc(squelch_threshold, 0.001) + + # FM Demodulator input: complex; output: float + k = quad_rate/(2*math.pi*max_dev) + self.quad_demod = analog.quadrature_demod_cf(k) + + # FM Deemphasis IIR filter + self.deemph = fm_deemph(quad_rate, tau=tau) + + # compute FIR taps for audio filter + audio_decim = quad_rate // audio_rate + audio_taps = filter.firdes.low_pass(1.0, # gain + quad_rate, # sampling rate + 2.7e3, # Audio LPF cutoff + 0.5e3, # Transition band + filter.firdes.WIN_HAMMING) # filter type + + print "len(audio_taps) =", len(audio_taps) + + # Decimating audio filter + # input: float; output: float; taps: float + self.audio_filter = filter.fir_filter_fff(audio_decim, audio_taps) + + self.connect(self, self.quad_demod, self.deemph, self.audio_filter, self) diff --git a/gr-analog/python/analog/nbfm_tx.py b/gr-analog/python/analog/nbfm_tx.py new file mode 100644 index 0000000000..62b56bae52 --- /dev/null +++ b/gr-analog/python/analog/nbfm_tx.py @@ -0,0 +1,93 @@ +# +# Copyright 2005,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math +from gnuradio import gr, filter +from fm_emph import fm_preemph + +try: + from gnuradio import analog +except ImportError: + import analog_swig as analog + +class nbfm_tx(gr.hier_block2): + def __init__(self, audio_rate, quad_rate, tau=75e-6, max_dev=5e3): + """ + Narrow Band FM Transmitter. + + Takes a single float input stream of audio samples in the range [-1,+1] + and produces a single FM modulated complex baseband output. + + Args: + audio_rate: sample rate of audio stream, >= 16k (integer) + quad_rate: sample rate of output stream (integer) + tau: preemphasis time constant (default 75e-6) (float) + max_dev: maximum deviation in Hz (default 5e3) (float) + + quad_rate must be an integer multiple of audio_rate. + """ + + gr.hier_block2.__init__(self, "nbfm_tx", + gr.io_signature(1, 1, gr.sizeof_float), # Input signature + gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature + + # FIXME audio_rate and quad_rate ought to be exact rationals + audio_rate = int(audio_rate) + quad_rate = int(quad_rate) + + if quad_rate % audio_rate != 0: + raise ValueError, "quad_rate is not an integer multiple of audio_rate" + + + do_interp = audio_rate != quad_rate + + if do_interp: + interp_factor = quad_rate / audio_rate + interp_taps = filter.optfir.low_pass(interp_factor, # gain + quad_rate, # Fs + 4500, # passband cutoff + 7000, # stopband cutoff + 0.1, # passband ripple dB + 40) # stopband atten dB + + #print "len(interp_taps) =", len(interp_taps) + self.interpolator = filter.interp_fir_filter_fff (interp_factor, interp_taps) + + self.preemph = fm_preemph(quad_rate, tau=tau) + + k = 2 * math.pi * max_dev / quad_rate + self.modulator = analog.frequency_modulator_fc(k) + + if do_interp: + self.connect(self, self.interpolator, self.preemph, self.modulator, self) + else: + self.connect(self, self.preemph, self.modulator, self) + + +class ctcss_gen_f(gr.hier_block2): + def __init__(self, sample_rate, tone_freq): + gr.hier_block2.__init__(self, "ctcss_gen_f", + gr.io_signature(0, 0, 0), # Input signature + gr.io_signature(1, 1, gr.sizeof_float)) # Output signature + + self.plgen = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, + tone_freq, 0.1, 0.0) + self.connect(self.plgen, self) diff --git a/gr-analog/python/analog/qa_agc.py b/gr-analog/python/analog/qa_agc.py new file mode 100755 index 0000000000..f274b04e7e --- /dev/null +++ b/gr-analog/python/analog/qa_agc.py @@ -0,0 +1,471 @@ +#!/usr/bin/env python +# +# Copyright 2004,2007,2010,2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_agc(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_001_sets(self): + agc = analog.agc_cc(1e-3, 1, 1, 1000) + + agc.set_rate(1) + agc.set_reference(1.1) + agc.set_gain(1.1) + agc.set_max_gain(100) + + self.assertAlmostEqual(agc.rate(), 1) + self.assertAlmostEqual(agc.reference(), 1.1) + self.assertAlmostEqual(agc.gain(), 1.1) + self.assertAlmostEqual(agc.max_gain(), 100) + + def test_001(self): + ''' Test the complex AGC loop (single rate input) ''' + tb = self.tb + + expected_result = ( + (100.000244140625+7.2191943445432116e-07j), + (72.892257690429688+52.959323883056641j), + (25.089065551757812+77.216217041015625j), + (-22.611061096191406+69.589706420898438j), + (-53.357715606689453+38.766635894775391j), + (-59.458671569824219+3.4792964243024471e-07j), + (-43.373462677001953-31.512666702270508j), + (-14.94139289855957-45.984889984130859j), + (13.478158950805664-41.48150634765625j), + (31.838506698608398-23.132022857666016j), + (35.519271850585938-3.1176801940091536e-07j), + (25.942903518676758+18.848621368408203j), + (8.9492912292480469+27.5430908203125j), + (-8.0852642059326172+24.883890151977539j), + (-19.131628036499023+13.899936676025391j), + (-21.383295059204102+3.1281737733479531e-07j), + (-15.650330543518066-11.370632171630859j), + (-5.4110145568847656-16.65339469909668j), + (4.9008159637451172-15.083160400390625j), + (11.628337860107422-8.4484796524047852j), + (13.036135673522949-2.288476110834381e-07j), + (9.5726661682128906+6.954948902130127j), + (3.3216962814331055+10.223132133483887j), + (-3.0204284191131592+9.2959251403808594j), + (-7.1977195739746094+5.2294478416442871j), + (-8.1072216033935547+1.8976157889483147e-07j), + (-5.9838657379150391-4.3475332260131836j), + (-2.0879747867584229-6.4261269569396973j), + (1.9100792407989502-5.8786196708679199j), + (4.5814824104309082-3.3286411762237549j), + (5.1967458724975586-1.3684227440080576e-07j), + (3.8647139072418213+2.8078789710998535j), + (1.3594740629196167+4.1840314865112305j), + (-1.2544282674789429+3.8607344627380371j), + (-3.0366206169128418+2.2062335014343262j), + (-3.4781389236450195+1.1194014604143376e-07j), + (-2.6133756637573242-1.8987287282943726j), + (-0.9293016791343689-2.8600969314575195j), + (0.86727333068847656-2.6691930294036865j), + (2.1243946552276611-1.5434627532958984j), + (2.4633183479309082-8.6486437567145913e-08j), + (1.8744727373123169+1.3618841171264648j), + (0.67528903484344482+2.0783262252807617j), + (-0.63866174221038818+1.965599536895752j), + (-1.5857341289520264+1.152103066444397j), + (-1.8640764951705933+7.6355092915036948e-08j), + (-1.4381576776504517-1.0448826551437378j), + (-0.52529704570770264-1.6166983842849731j), + (0.50366902351379395-1.5501341819763184j), + (1.26766037940979-0.92100900411605835j)) + + sampling_freq = 100 + src1 = analog.sig_source_c(sampling_freq, analog.GR_SIN_WAVE, + sampling_freq * 0.10, 100.0) + dst1 = blocks.vector_sink_c() + head = blocks.head(gr.sizeof_gr_complex, int (5*sampling_freq * 0.10)) + + agc = analog.agc_cc(1e-3, 1, 1, 1000) + + tb.connect(src1, head) + tb.connect(head, agc) + tb.connect(agc, dst1) + + tb.run() + dst_data = dst1.data() + self.assertComplexTuplesAlmostEqual(expected_result, dst_data, 4) + + def test_002_sets(self): + agc = analog.agc_ff(1e-3, 1, 1, 1000) + + agc.set_rate(1) + agc.set_reference(1.1) + agc.set_gain(1.1) + agc.set_max_gain(100) + + self.assertAlmostEqual(agc.rate(), 1) + self.assertAlmostEqual(agc.reference(), 1.1) + self.assertAlmostEqual(agc.gain(), 1.1) + self.assertAlmostEqual(agc.max_gain(), 100) + + def test_002(self): + ''' Test the floating point AGC loop (single rate input) ''' + tb = self.tb + + expected_result = ( + 7.2191943445432116e-07, + 58.837181091308594, + 89.700050354003906, + 81.264183044433594, + 45.506141662597656, + 4.269894304798072e-07, + -42.948936462402344, + -65.50335693359375, + -59.368724822998047, + -33.261005401611328, + -4.683740257860336e-07, + 31.423542022705078, + 47.950984954833984, + 43.485683441162109, + 24.378345489501953, + 5.7254135299444897e-07, + -23.062990188598633, + -35.218441009521484, + -31.964075088500977, + -17.934831619262695, + -5.0591745548445033e-07, + 16.998210906982422, + 25.982204437255859, + 23.606258392333984, + 13.260685920715332, + 4.9936483037527069e-07, + -12.59880542755127, + -19.28221321105957, + -17.54347038269043, + -9.8700437545776367, + -4.188150626305287e-07, + 9.4074573516845703, + 14.422011375427246, + 13.145503044128418, + 7.41046142578125, + 3.8512698097292741e-07, + -7.0924453735351562, + -10.896408081054688, + -9.9552040100097656, + -5.6262712478637695, + -3.1982864356905338e-07, + 5.4131259918212891, + 8.3389215469360352, + 7.6409502029418945, + 4.3320145606994629, + 2.882407841298118e-07, + -4.194943904876709, + -6.4837145805358887, + -5.9621825218200684, + -3.3931560516357422) + + sampling_freq = 100 + src1 = analog.sig_source_f(sampling_freq, analog.GR_SIN_WAVE, + sampling_freq * 0.10, 100.0) + dst1 = blocks.vector_sink_f () + head = blocks.head (gr.sizeof_float, int (5*sampling_freq * 0.10)) + + agc = analog.agc_ff(1e-3, 1, 1, 1000) + + tb.connect (src1, head) + tb.connect (head, agc) + tb.connect (agc, dst1) + + tb.run () + dst_data = dst1.data () + self.assertFloatTuplesAlmostEqual (expected_result, dst_data, 4) + + def test_003_sets(self): + agc = analog.agc2_cc(1e-3, 1e-1, 1, 1, 1000) + + agc.set_attack_rate(1) + agc.set_decay_rate(2) + agc.set_reference(1.1) + agc.set_gain(1.1) + agc.set_max_gain(100) + + self.assertAlmostEqual(agc.attack_rate(), 1) + self.assertAlmostEqual(agc.decay_rate(), 2) + self.assertAlmostEqual(agc.reference(), 1.1) + self.assertAlmostEqual(agc.gain(), 1.1) + self.assertAlmostEqual(agc.max_gain(), 100) + + def test_003(self): + ''' Test the complex AGC loop (attack and decay rate inputs) ''' + tb = self.tb + + expected_result = \ + ((100.000244140625+7.2191943445432116e-07j), + (0.80881959199905396+0.58764183521270752j), + (0.30894950032234192+0.95084899663925171j), + (-0.30895623564720154+0.95086973905563354j), + (-0.80887287855148315+0.58768033981323242j), + (-0.99984413385391235+5.850709250410091e-09j), + (-0.80889981985092163-0.58770018815994263j), + (-0.30897706747055054-0.95093393325805664j), + (0.30898112058639526-0.95094609260559082j), + (0.80893135070800781-0.58772283792495728j), + (0.99990922212600708-8.7766354184282136e-09j), + (0.80894720554351807+0.58773452043533325j), + (0.30899339914321899+0.95098406076431274j), + (-0.30899572372436523+0.95099133253097534j), + (-0.80896598100662231+0.58774799108505249j), + (-0.99994778633117676+1.4628290578855285e-08j), + (-0.80897533893585205-0.58775502443313599j), + (-0.30900305509567261-0.95101380348205566j), + (0.30900448560714722-0.95101797580718994j), + (0.80898630619049072-0.58776277303695679j), + (0.99997037649154663-1.7554345532744264e-08j), + (0.80899184942245483+0.58776694536209106j), + (0.30900871753692627+0.95103120803833008j), + (-0.30900952219963074+0.95103377103805542j), + (-0.8089984655380249+0.58777159452438354j), + (-0.99998390674591064+2.3406109050938539e-08j), + (-0.809001624584198-0.58777409791946411j), + (-0.30901208519935608-0.95104163885116577j), + (0.30901262164115906-0.95104306936264038j), + (0.80900543928146362-0.587776780128479j), + (0.99999171495437622-2.6332081404234486e-08j), + (0.80900734663009644+0.58777821063995361j), + (0.30901408195495605+0.95104765892028809j), + (-0.30901429057121277+0.95104855298995972j), + (-0.80900967121124268+0.58777981996536255j), + (-0.99999648332595825+3.2183805842578295e-08j), + (-0.80901080369949341-0.58778077363967896j), + (-0.30901527404785156-0.95105135440826416j), + (0.30901545286178589-0.95105189085006714j), + (0.80901217460632324-0.58778166770935059j), + (0.99999916553497314-3.5109700036173308e-08j), + (0.809012770652771+0.58778214454650879j), + (0.30901595950126648+0.9510534405708313j), + (-0.30901598930358887+0.95105385780334473j), + (-0.80901366472244263+0.58778274059295654j), + (-1.0000008344650269+4.0961388947380328e-08j), + (-0.8090139627456665-0.58778303861618042j), + (-0.30901634693145752-0.95105475187301636j), + (0.30901640653610229-0.95105493068695068j), + (0.80901449918746948-0.5877833366394043j)) + + sampling_freq = 100 + src1 = analog.sig_source_c(sampling_freq, analog.GR_SIN_WAVE, + sampling_freq * 0.10, 100) + dst1 = blocks.vector_sink_c() + head = blocks.head(gr.sizeof_gr_complex, int(5*sampling_freq * 0.10)) + + agc = analog.agc2_cc(1e-2, 1e-3, 1, 1, 1000) + + tb.connect(src1, head) + tb.connect(head, agc) + tb.connect(agc, dst1) + + tb.run() + dst_data = dst1.data() + self.assertComplexTuplesAlmostEqual(expected_result, dst_data, 4) + + def test_004_sets(self): + agc = analog.agc2_ff(1e-3, 1e-1, 1, 1, 1000) + + agc.set_attack_rate(1) + agc.set_decay_rate(2) + agc.set_reference(1.1) + agc.set_gain(1.1) + agc.set_max_gain(100) + + self.assertAlmostEqual(agc.attack_rate(), 1) + self.assertAlmostEqual(agc.decay_rate(), 2) + self.assertAlmostEqual(agc.reference(), 1.1) + self.assertAlmostEqual(agc.gain(), 1.1) + self.assertAlmostEqual(agc.max_gain(), 100) + + def test_004(self): + ''' Test the floating point AGC loop (attack and decay rate inputs) ''' + tb = self.tb + + expected_result = \ + (7.2191943445432116e-07, + 58.837181091308594, + 40.194305419921875, + 2.9183335304260254, + 0.67606079578399658, + 8.6260438791896377e-09, + -1.4542514085769653, + -1.9210131168365479, + -1.0450780391693115, + -0.61939650774002075, + -1.2590258613442984e-08, + 1.4308931827545166, + 1.9054338932037354, + 1.0443156957626343, + 0.61937344074249268, + 2.0983527804219193e-08, + -1.4308838844299316, + -1.9054274559020996, + -1.0443152189254761, + -0.61937344074249268, + -2.5180233009791664e-08, + 1.4308837652206421, + 1.9054274559020996, + 1.0443154573440552, + 0.61937344074249268, + 3.3573645197293445e-08, + -1.4308838844299316, + -1.9054274559020996, + -1.0443152189254761, + -0.61937350034713745, + -3.7770352179222755e-08, + 1.4308837652206421, + 1.9054274559020996, + 1.0443154573440552, + 0.61937350034713745, + 4.6163762590367696e-08, + -1.4308838844299316, + -1.9054274559020996, + -1.0443153381347656, + -0.61937344074249268, + -5.0360466019583328e-08, + 1.4308837652206421, + 1.9054274559020996, + 1.0443155765533447, + 0.61937344074249268, + 5.8753879983441948e-08, + -1.4308837652206421, + -1.9054274559020996, + -1.0443153381347656, + -0.61937344074249268) + + sampling_freq = 100 + src1 = analog.sig_source_f(sampling_freq, analog.GR_SIN_WAVE, + sampling_freq * 0.10, 100) + dst1 = blocks.vector_sink_f() + head = blocks.head(gr.sizeof_float, int(5*sampling_freq * 0.10)) + + agc = analog.agc2_ff(1e-2, 1e-3, 1, 1, 1000) + + tb.connect(src1, head) + tb.connect(head, agc) + tb.connect(agc, dst1) + + tb.run() + dst_data = dst1.data() + self.assertFloatTuplesAlmostEqual(expected_result, dst_data, 4) + + + def test_005(self): + ''' Test the complex AGC loop (attack and decay rate inputs) ''' + tb = self.tb + + expected_result = \ + ((100.000244140625+7.2191943445432116e-07j), + (0.80881959199905396+0.58764183521270752j), + (0.30894950032234192+0.95084899663925171j), + (-0.30895623564720154+0.95086973905563354j), + (-0.80887287855148315+0.58768033981323242j), + (-0.99984413385391235+5.850709250410091e-09j), + (-0.80889981985092163-0.58770018815994263j), + (-0.30897706747055054-0.95093393325805664j), + (0.30898112058639526-0.95094609260559082j), + (0.80893135070800781-0.58772283792495728j), + (0.99990922212600708-8.7766354184282136e-09j), + (0.80894720554351807+0.58773452043533325j), + (0.30899339914321899+0.95098406076431274j), + (-0.30899572372436523+0.95099133253097534j), + (-0.80896598100662231+0.58774799108505249j), + (-0.99994778633117676+1.4628290578855285e-08j), + (-0.80897533893585205-0.58775502443313599j), + (-0.30900305509567261-0.95101380348205566j), + (0.30900448560714722-0.95101797580718994j), + (0.80898630619049072-0.58776277303695679j), + (0.99997037649154663-1.7554345532744264e-08j), + (0.80899184942245483+0.58776694536209106j), + (0.30900871753692627+0.95103120803833008j), + (-0.30900952219963074+0.95103377103805542j), + (-0.8089984655380249+0.58777159452438354j), + (-0.99998390674591064+2.3406109050938539e-08j), + (-0.809001624584198-0.58777409791946411j), + (-0.30901208519935608-0.95104163885116577j), + (0.30901262164115906-0.95104306936264038j), + (0.80900543928146362-0.587776780128479j), + (0.99999171495437622-2.6332081404234486e-08j), + (0.80900734663009644+0.58777821063995361j), + (0.30901408195495605+0.95104765892028809j), + (-0.30901429057121277+0.95104855298995972j), + (-0.80900967121124268+0.58777981996536255j), + (-0.99999648332595825+3.2183805842578295e-08j), + (-0.80901080369949341-0.58778077363967896j), + (-0.30901527404785156-0.95105135440826416j), + (0.30901545286178589-0.95105189085006714j), + (0.80901217460632324-0.58778166770935059j), + (0.99999916553497314-3.5109700036173308e-08j), + (0.809012770652771+0.58778214454650879j), + (0.30901595950126648+0.9510534405708313j), + (-0.30901598930358887+0.95105385780334473j), + (-0.80901366472244263+0.58778274059295654j), + (-1.0000008344650269+4.0961388947380328e-08j), + (-0.8090139627456665-0.58778303861618042j), + (-0.30901634693145752-0.95105475187301636j), + (0.30901640653610229-0.95105493068695068j), + (0.80901449918746948-0.5877833366394043j)) + + sampling_freq = 100 + src1 = analog.sig_source_c(sampling_freq, analog.GR_SIN_WAVE, + sampling_freq * 0.10, 100) + dst1 = blocks.vector_sink_c() + head = blocks.head(gr.sizeof_gr_complex, int(5*sampling_freq * 0.10)) + + agc = analog.agc2_cc(1e-2, 1e-3, 1, 1, 1000) + + tb.connect(src1, head) + tb.connect(head, agc) + tb.connect(agc, dst1) + + tb.run() + dst_data = dst1.data() + self.assertComplexTuplesAlmostEqual(expected_result, dst_data, 4) + + def test_100(self): + ''' Test complex feedforward agc with constant input ''' + + length = 8 + gain = 2 + + input_data = 8*(0.0,) + 24*(1.0,) + 24*(0.0,) + expected_result = (8+length-1)*(0.0,) + 24*(gain*1.0,) + (0,) + + src = blocks.vector_source_c(input_data) + agc = analog.feedforward_agc_cc(8, 2.0) + dst = blocks.vector_sink_c() + self.tb.connect(src, agc, dst) + + self.tb.run() + dst_data = dst.data()[0:len(expected_result)] + + self.assertComplexTuplesAlmostEqual(expected_result, dst_data, 4) + + +if __name__ == '__main__': + gr_unittest.run(test_agc, "test_agc.xml") diff --git a/gr-analog/python/analog/qa_cpfsk.py b/gr-analog/python/analog/qa_cpfsk.py new file mode 100755 index 0000000000..a33a6e6092 --- /dev/null +++ b/gr-analog/python/analog/qa_cpfsk.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_cpfsk_bc(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_cpfsk_bc_001(self): + # Test set/gets + + op = analog.cpfsk_bc(2, 1, 2) + + op.set_amplitude(2) + a = op.amplitude() + self.assertEqual(2, a) + + freq = 2*math.pi/2.0 + f = op.freq() + self.assertAlmostEqual(freq, f, 5) + + p = op.phase() + self.assertEqual(0, p) + + def test_cpfsk_bc_002(self): + src_data = 10*[0, 1] + expected_result = map(lambda x: complex(2*x-1,0), src_data) + + src = blocks.vector_source_b(src_data) + op = analog.cpfsk_bc(2, 1, 2) + dst = blocks.vector_sink_c() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + result_data = dst.data()[0:len(expected_result)] + self.assertComplexTuplesAlmostEqual(expected_result, result_data, 4) + +if __name__ == '__main__': + gr_unittest.run(test_cpfsk_bc, "test_cpfsk_bc.xml") + diff --git a/gr-analog/python/analog/qa_ctcss_squelch.py b/gr-analog/python/analog/qa_ctcss_squelch.py new file mode 100755 index 0000000000..f5cfcbf976 --- /dev/null +++ b/gr-analog/python/analog/qa_ctcss_squelch.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_ctcss_squelch(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_ctcss_squelch_001(self): + # Test set/gets + + rate = 1 + rate2 = 2 + freq = 100 + level = 0.5 + length = 1 + ramp = 1 + ramp2 = 2 + gate = True + gate2 = False + + op = analog.ctcss_squelch_ff(rate, freq, level, + length, ramp, gate) + + op.set_ramp(ramp2) + r = op.ramp() + self.assertEqual(ramp2, r) + + op.set_gate(gate2) + g = op.gate() + self.assertEqual(gate2, g) + + def test_ctcss_squelch_002(self): + # Test runtime, gate=True + rate = 1 + freq = 100 + level = 0.0 + length = 1 + ramp = 1 + gate = True + + src_data = map(lambda x: float(x)/10.0, range(1, 40)) + expected_result = src_data + expected_result[0] = 0 + + src = blocks.vector_source_f(src_data) + op = analog.ctcss_squelch_ff(rate, freq, level, + length, ramp, gate) + dst = blocks.vector_sink_f() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + result_data = dst.data() + self.assertFloatTuplesAlmostEqual(expected_result, result_data, 4) + + def test_ctcss_squelch_003(self): + # Test runtime, gate=False + rate = 1 + freq = 100 + level = 0.5 + length = 1 + ramp = 1 + gate = False + + src_data = map(lambda x: float(x)/10.0, range(1, 40)) + src = blocks.vector_source_f(src_data) + op = analog.ctcss_squelch_ff(rate, freq, level, + length, ramp, gate) + dst = blocks.vector_sink_f() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + expected_result = src_data + expected_result[0:5] = [0, 0, 0, 0, 0] + + result_data = dst.data() + self.assertFloatTuplesAlmostEqual(expected_result, result_data, 4) + +if __name__ == '__main__': + gr_unittest.run(test_ctcss_squelch, "test_ctcss_squelch.xml") + diff --git a/gr-analog/python/analog/qa_dpll.py b/gr-analog/python/analog/qa_dpll.py new file mode 100755 index 0000000000..44b1486035 --- /dev/null +++ b/gr-analog/python/analog/qa_dpll.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_dpll_bb(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_dpll_bb_001(self): + # Test set/gets + + period = 1.0 + gain = 0.1 + op = analog.dpll_bb(period, gain) + + op.set_gain(0.2) + g = op.gain() + self.assertAlmostEqual(g, 0.2) + + f = op.freq() + self.assertEqual(1/period, f) + + d0 = 1.0 - 0.5*f; + d1 = op.decision_threshold() + self.assertAlmostEqual(d0, d1) + + p = op.phase() + self.assertEqual(0, p) + + def test_dpll_bb_002(self): + period = 4 + gain = 0.1 + + src_data = 10*((period-1)*[0,] + [1,]) + expected_result = src_data + + src = blocks.vector_source_b(src_data) + op = analog.dpll_bb(period, gain) + dst = blocks.vector_sink_b() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + result_data = dst.data() + self.assertComplexTuplesAlmostEqual(expected_result, result_data, 4) + +if __name__ == '__main__': + gr_unittest.run(test_dpll_bb, "test_dpll_bb.xml") + diff --git a/gr-analog/python/analog/qa_fastnoise.py b/gr-analog/python/analog/qa_fastnoise.py new file mode 100644 index 0000000000..91e1cb87b7 --- /dev/null +++ b/gr-analog/python/analog/qa_fastnoise.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# +# Copyright 2007,2010,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest, analog + +class test_fastnoise_source(gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def test_001(self): + # Just confirm that we can instantiate a noise source + op = analog.fastnoise_source_f(analog.GR_GAUSSIAN, 10, 10) + + def test_002(self): + # Test get methods + set_type = analog.GR_GAUSSIAN + set_ampl = 10 + op = analog.fastnoise_source_f(set_type, set_ampl, 10) + get_type = op.type() + get_ampl = op.amplitude() + + self.assertEqual(get_type, set_type) + self.assertEqual(get_ampl, set_ampl) + + +if __name__ == '__main__': + gr_unittest.run(test_fastnoise_source, "test_fastnoise_source.xml") + diff --git a/gr-analog/python/analog/qa_fmdet.py b/gr-analog/python/analog/qa_fmdet.py new file mode 100755 index 0000000000..a9c88c3b95 --- /dev/null +++ b/gr-analog/python/analog/qa_fmdet.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_fmdet_cf(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_fmdet_cf_001(self): + # Test set/gets + + fh1 = 10 + fh2 = 20 + fl1 = 1 + fl2 = 2 + scale1 = 3 + scale2 = 4 + op = analog.fmdet_cf(1, fl1, fh1, scale1) + + op.set_freq_range(fl2, fh2) + lo = op.freq_low() + hi = op.freq_high() + f = op.freq() + self.assertEqual(fl2, lo) + self.assertEqual(fh2, hi) + self.assertEqual(0, f) + + op.set_scale(scale2) + s = op.scale() + b = op.bias() + eb = 0.5*scale2*(hi + lo) / (hi - lo); + self.assertEqual(scale2, s) + self.assertAlmostEqual(eb, b) + + # FIXME: This passes QA, but the it's only based off what the + # block is saying, not what the values should actually be. + def est_fmdet_cf_002(self): + N = 100 + src = analog.sig_source_c(1, analog.GR_SIN_WAVE, 0.2, 1) + head = blocks.head(gr.sizeof_gr_complex, N) + op = analog.fmdet_cf(1, 0.1, 0.3, 0.1) + dst = blocks.vector_sink_f() + + self.tb.connect(src, head, op) + self.tb.connect(op, dst) + self.tb.run() + + result_data = dst.data()[4:N] + expected_result = (100-4)*[-0.21755,] + self.assertFloatTuplesAlmostEqual(expected_result, result_data, 4) + +if __name__ == '__main__': + gr_unittest.run(test_fmdet_cf, "test_fmdet_cf.xml") + diff --git a/gr-analog/python/analog/qa_frequency_modulator.py b/gr-analog/python/analog/qa_frequency_modulator.py new file mode 100755 index 0000000000..0f5c45b11a --- /dev/null +++ b/gr-analog/python/analog/qa_frequency_modulator.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# Copyright 2004,2007,2010,2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math + +from gnuradio import gr, gr_unittest, analog, blocks + +def sincos(x): + return math.cos(x) + math.sin(x) * 1j + + +class test_frequency_modulator(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_fm_001(self): + pi = math.pi + sensitivity = pi/4 + src_data = (1.0/4, 1.0/2, 1.0/4, -1.0/4, -1.0/2, -1/4.0) + running_sum = (pi/16, 3*pi/16, pi/4, 3*pi/16, pi/16, 0) + expected_result = tuple([sincos(x) for x in running_sum]) + src = blocks.vector_source_f(src_data) + op = analog.frequency_modulator_fc(sensitivity) + dst = blocks.vector_sink_c() + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + result_data = dst.data() + self.assertComplexTuplesAlmostEqual(expected_result, result_data, 5) + +if __name__ == '__main__': + gr_unittest.run(test_frequency_modulator, "test_frequency_modulator.xml") + diff --git a/gr-analog/python/analog/qa_noise.py b/gr-analog/python/analog/qa_noise.py new file mode 100755 index 0000000000..5576773f23 --- /dev/null +++ b/gr-analog/python/analog/qa_noise.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# +# Copyright 2007,2010,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest, analog + +class test_noise_source(gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def test_001(self): + # Just confirm that we can instantiate a noise source + op = analog.noise_source_f(analog.GR_GAUSSIAN, 10, 10) + + def test_002(self): + # Test get methods + set_type = analog.GR_GAUSSIAN + set_ampl = 10 + op = analog.noise_source_f(set_type, set_ampl, 10) + get_type = op.type() + get_ampl = op.amplitude() + + self.assertEqual(get_type, set_type) + self.assertEqual(get_ampl, set_ampl) + + +if __name__ == '__main__': + gr_unittest.run(test_noise_source, "test_noise_source.xml") + diff --git a/gr-analog/python/analog/qa_phase_modulator.py b/gr-analog/python/analog/qa_phase_modulator.py new file mode 100755 index 0000000000..05fe2127c0 --- /dev/null +++ b/gr-analog/python/analog/qa_phase_modulator.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math + +from gnuradio import gr, gr_unittest, analog, blocks + +def sincos(x): + return math.cos(x) + math.sin(x) * 1j + + +class test_phase_modulator(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_fm_001(self): + pi = math.pi + sensitivity = pi/4 + src_data = (1.0/4, 1.0/2, 1.0/4, -1.0/4, -1.0/2, -1/4.0) + expected_result = tuple([sincos(sensitivity*x) for x in src_data]) + + src = blocks.vector_source_f(src_data) + op = analog.phase_modulator_fc(sensitivity) + dst = blocks.vector_sink_c() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + result_data = dst.data() + self.assertComplexTuplesAlmostEqual(expected_result, result_data, 5) + +if __name__ == '__main__': + gr_unittest.run(test_phase_modulator, "test_phase_modulator.xml") + diff --git a/gr-analog/python/analog/qa_pll_carriertracking.py b/gr-analog/python/analog/qa_pll_carriertracking.py new file mode 100755 index 0000000000..d05e49641a --- /dev/null +++ b/gr-analog/python/analog/qa_pll_carriertracking.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python +# +# Copyright 2004,2007,2010-2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_pll_carriertracking(gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block() + + def tearDown (self): + self.tb = None + + def test_pll_carriertracking(self): + expected_result = ((1.00000238419+7.21919457547e-09j), + (0.998025715351+0.062790453434j), + (0.992777824402+0.119947694242j), + (0.985192835331+0.171441286802j), + (0.976061582565+0.217501848936j), + (0.966034710407+0.258409559727j), + (0.95565611124+0.294477283955j), + (0.945357382298+0.326030552387j), + (0.935475051403+0.353395611048j), + (0.926258146763+0.376889169216j), + (0.917895197868+0.39681750536j), + (0.910515546799+0.413470208645j), + (0.904196679592+0.427117019892j), + (0.898972511292+0.438006043434j), + (0.894769787788+0.446523308754j), + (0.891652584076+0.452715367079j), + (0.8895829916+0.456773489714j), + (0.888502895832+0.458873122931j), + (0.888343691826+0.459175437689j), + (0.889035582542+0.457833081484j), + (0.890497922897+0.454985737801j), + (0.892645597458+0.450762689114j), + (0.895388305187+0.445282936096j), + (0.898648142815+0.438664674759j), + (0.902342617512+0.431016951799j), + (0.906392872334+0.422441422939j), + (0.910642921925+0.413191765547j), + (0.915039420128+0.403358519077j), + (0.919594764709+0.392864197493j), + (0.92425006628+0.381792247295j), + (0.928944349289+0.370217680931j), + (0.933634519577+0.358220815659j), + (0.938279032707+0.345874190331j), + (0.942840516567+0.333247303963j), + (0.947280526161+0.32040438056j), + (0.951574921608+0.307409763336j), + (0.955703914165+0.294323593378j), + (0.959648966789+0.281201630831j), + (0.963392794132+0.268095195293j), + (0.966880619526+0.255221515894j), + (0.970162451267+0.242447137833j), + (0.973235487938+0.229809194803j), + (0.97609680891+0.217341512442j), + (0.978744983673+0.20507311821j), + (0.981189727783+0.193033605814j), + (0.983436584473+0.181248426437j), + (0.985490739346+0.169738590717j), + (0.987353682518+0.158523857594j), + (0.989041447639+0.147622272372j), + (0.990563035011+0.137049794197j), + (0.991928339005+0.126818582416j), + (0.993117690086+0.117111675441j), + (0.994156062603+0.107930034399j), + (0.995076179504+0.0990980416536j), + (0.995887458324+0.0906178802252j), + (0.996591091156+0.0824909061193j), + (0.997202515602+0.0747182965279j), + (0.997730851173+0.0672992765903j), + (0.998185396194+0.0602316558361j), + (0.99856698513+0.0535135567188j), + (0.998885989189+0.0471420884132j), + (0.99915266037+0.0411129891872j), + (0.999372899532+0.0354214012623j), + (0.999548316002+0.0300626158714j), + (0.999680638313+0.0252036750317j), + (0.999784469604+0.020652115345j), + (0.999865531921+0.0163950324059j), + (0.999923825264+0.0124222636223j), + (0.999960243702+0.00872156023979j), + (0.999983668327+0.00528120994568j), + (0.999997138977+0.00209015607834j), + (1.00000119209-0.00086285173893j), + (0.999992132187-0.00358882546425j), + (0.999979138374-0.00609711557627j), + (0.999963641167-0.00839691981673j), + (0.999947249889-0.0104993218556j), + (0.999924004078-0.0122378543019j), + (0.999904811382-0.0136305987835j), + (0.999888062477-0.0148707330227j), + (0.9998742342-0.0159679055214j), + (0.999856114388-0.0169314742088j), + (0.999839782715-0.0177700817585j), + (0.999826967716-0.0184917747974j), + (0.999818325043-0.0191045701504j), + (0.999807476997-0.0196143388748j), + (0.999797284603-0.0200265944004j), + (0.999791204929-0.0203481912613j), + (0.99978852272-0.0205836892128j), + (0.99978530407-0.0207380950451j), + (0.999785065651-0.0206423997879j), + (0.999787807465-0.0204866230488j), + (0.999794304371-0.0202808082104j), + (0.999800384045-0.0200312435627j), + (0.999803245068-0.0197458267212j), + (0.9998087883-0.0194311738014j), + (0.999816894531-0.0190933048725j), + (0.999825954437-0.0187371373177j), + (0.999829888344-0.0183679759502j), + (0.999835848808-0.017987690866j), + (0.999844014645-0.0176006518304j)) + + sampling_freq = 10e3 + freq = sampling_freq / 100 + + loop_bw = math.pi/100.0 + maxf = 1 + minf = -1 + + src = analog.sig_source_c(sampling_freq, analog.GR_COS_WAVE, freq, 1.0) + pll = analog.pll_carriertracking_cc(loop_bw, maxf, minf) + head = blocks.head(gr.sizeof_gr_complex, int (freq)) + dst = blocks.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) + +if __name__ == '__main__': + gr_unittest.run(test_pll_carriertracking, "test_pll_carriertracking.xml") diff --git a/gr-analog/python/analog/qa_pll_freqdet.py b/gr-analog/python/analog/qa_pll_freqdet.py new file mode 100755 index 0000000000..0166a40258 --- /dev/null +++ b/gr-analog/python/analog/qa_pll_freqdet.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +# +# Copyright 2004,2007,2010-2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_pll_freqdet(gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block() + + def tearDown (self): + self.tb = None + + def test_pll_freqdet(self): + expected_result = (0.0, + 4.33888922882e-08, + 0.367369994515, + 1.08135249597, + 2.10983253908, + 3.42221529438, + 4.98940390402, + 6.78379190842, + 8.77923286024, + 10.9510106794, + 13.2758363182, + 15.7317829127, + 18.2982902299, + 20.9561068599, + 23.6755271122, + 26.452952094, + 29.2731265301, + 32.1219053479, + 34.9862418188, + 37.8540971414, + 40.7144315483, + 43.5571390869, + 46.3730179743, + 49.1537231663, + 51.8917218889, + 54.58026103, + 57.2015358514, + 59.7513664199, + 62.2380533124, + 64.657612252, + 67.006640002, + 69.2822432184, + 71.4820384499, + 73.6041047056, + 75.6469478817, + 77.6094829742, + 79.4909866472, + 81.2911031615, + 83.0097850853, + 84.6355598352, + 86.1820937186, + 87.6504420946, + 89.0418441206, + 90.3577286819, + 91.5996432431, + 92.7692775646, + 93.8684162704, + 94.8989269904, + 95.8627662892, + 96.7619381633, + 97.598505899, + 98.362769679, + 99.0579904444, + 99.6992633875, + 100.288805948, + 100.828805921, + 101.321421457, + 101.76878699, + 102.17300138, + 102.536116055, + 102.860158727, + 103.147085962, + 103.398830608, + 103.617254366, + 103.792467691, + 103.939387906, + 104.060030865, + 104.15631756, + 104.230085975, + 104.283067372, + 104.316933727, + 104.333238432, + 104.333440018, + 104.318914008, + 104.290941063, + 104.250742554, + 104.187634452, + 104.103822339, + 104.013227468, + 103.916810336, + 103.815448432, + 103.709936239, + 103.600997093, + 103.489283183, + 103.375351833, + 103.259712936, + 103.142828952, + 103.025091195, + 102.90686726, + 102.776726069, + 102.648078982, + 102.521459607, + 102.397294831, + 102.275999684, + 102.157882471, + 102.043215927, + 101.93218978, + 101.824958181, + 101.72159228, + 101.622151366) + + sampling_freq = 10e3 + freq = sampling_freq / 100 + + loop_bw = math.pi/100.0 + maxf = 1 + minf = -1 + + src = analog.sig_source_c(sampling_freq, analog.GR_COS_WAVE, freq, 1.0) + pll = analog.pll_freqdet_cf(loop_bw, maxf, minf) + head = blocks.head(gr.sizeof_float, int (freq)) + dst = blocks.vector_sink_f() + + self.tb.connect(src, pll, head) + self.tb.connect(head, dst) + + self.tb.run() + dst_data = dst.data() + + # convert it from normalized frequency to absolute frequency (Hz) + dst_data = [i*(sampling_freq/(2*math.pi)) for i in dst_data] + + self.assertFloatTuplesAlmostEqual(expected_result, dst_data, 3) + +if __name__ == '__main__': + gr_unittest.run(test_pll_freqdet, "test_pll_freqdet.xml") diff --git a/gr-analog/python/analog/qa_pll_refout.py b/gr-analog/python/analog/qa_pll_refout.py new file mode 100755 index 0000000000..f85a027aa3 --- /dev/null +++ b/gr-analog/python/analog/qa_pll_refout.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python +# +# Copyright 2004,2010,2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_pll_refout(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + 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 = analog.sig_source_c(sampling_freq, analog.GR_COS_WAVE, freq, 1.0) + pll = analog.pll_refout_cc(loop_bw, maxf, minf) + head = blocks.head(gr.sizeof_gr_complex, int (freq)) + dst = blocks.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) + +if __name__ == '__main__': + gr_unittest.run(test_pll_refout, "test_pll_refout.xml") diff --git a/gr-analog/python/analog/qa_probe_avg_mag_sqrd.py b/gr-analog/python/analog/qa_probe_avg_mag_sqrd.py new file mode 100755 index 0000000000..a52c0806e3 --- /dev/null +++ b/gr-analog/python/analog/qa_probe_avg_mag_sqrd.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math + +from gnuradio import gr, gr_unittest, analog, blocks + +def avg_mag_sqrd_c(x, alpha): + y = [0,] + for xi in x: + tmp = alpha*(xi.real*xi.real + xi.imag*xi.imag) + (1-alpha)*y[-1] + y.append(tmp) + return y + +def avg_mag_sqrd_f(x, alpha): + y = [0,] + for xi in x: + tmp = alpha*(xi*xi) + (1-alpha)*y[-1] + y.append(tmp) + return y + +class test_probe_avg_mag_sqrd(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_c_001(self): + alpha = 0.0001 + src_data = [1.0+1.0j, 2.0+2.0j, 3.0+3.0j, 4.0+4.0j, 5.0+5.0j, + 6.0+6.0j, 7.0+7.0j, 8.0+8.0j, 9.0+9.0j, 10.0+10.0j] + expected_result = avg_mag_sqrd_c(src_data, alpha)[-1] + + src = blocks.vector_source_c(src_data) + op = analog.probe_avg_mag_sqrd_c(0, alpha) + + self.tb.connect(src, op) + self.tb.run() + + result_data = op.level() + self.assertAlmostEqual(expected_result, result_data, 5) + + def test_cf_002(self): + alpha = 0.0001 + src_data = [1.0+1.0j, 2.0+2.0j, 3.0+3.0j, 4.0+4.0j, 5.0+5.0j, + 6.0+6.0j, 7.0+7.0j, 8.0+8.0j, 9.0+9.0j, 10.0+10.0j] + expected_result = avg_mag_sqrd_c(src_data, alpha)[0:-1] + + src = blocks.vector_source_c(src_data) + op = analog.probe_avg_mag_sqrd_cf(0, alpha) + dst = blocks.vector_sink_f() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + result_data = dst.data() + self.assertComplexTuplesAlmostEqual(expected_result, result_data, 5) + + def test_f_003(self): + alpha = 0.0001 + src_data = [1.0, 2.0, 3.0, 4.0, 5.0, + 6.0, 7.0, 8.0, 9.0, 10.0] + expected_result = avg_mag_sqrd_f(src_data, alpha)[-1] + + src = blocks.vector_source_f(src_data) + op = analog.probe_avg_mag_sqrd_f(0, alpha) + + self.tb.connect(src, op) + self.tb.run() + + result_data = op.level() + self.assertAlmostEqual(expected_result, result_data, 5) + +if __name__ == '__main__': + gr_unittest.run(test_probe_avg_mag_sqrd, "test_probe_avg_mag_sqrd.xml") + diff --git a/gr-analog/python/analog/qa_pwr_squelch.py b/gr-analog/python/analog/qa_pwr_squelch.py new file mode 100755 index 0000000000..e366192f68 --- /dev/null +++ b/gr-analog/python/analog/qa_pwr_squelch.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_pwr_squelch(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_pwr_squelch_001(self): + # Test set/gets + + alpha = 0.0001 + + thr1 = 10 + thr2 = 20 + ramp = 1 + ramp2 = 2 + gate = True + gate2 = False + + op = analog.pwr_squelch_cc(thr1, alpha, ramp, gate) + + op.set_threshold(thr2) + t = op.threshold() + self.assertEqual(thr2, t) + + op.set_ramp(ramp2) + r = op.ramp() + self.assertEqual(ramp2, r) + + op.set_gate(gate2) + g = op.gate() + self.assertEqual(gate2, g) + + def test_pwr_squelch_002(self): + # Test runtime, gate=True + alpha = 0.0001 + thr = -25 + + src_data = map(lambda x: float(x)/10.0, range(1, 40)) + src = blocks.vector_source_c(src_data) + op = analog.pwr_squelch_cc(thr, alpha) + dst = blocks.vector_sink_c() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + expected_result = src_data + expected_result[0:20] = 20*[0,] + + result_data = dst.data() + self.assertComplexTuplesAlmostEqual(expected_result, result_data, 4) + + def test_pwr_squelch_003(self): + # Test set/gets + + alpha = 0.0001 + + thr1 = 10 + thr2 = 20 + ramp = 1 + ramp2 = 2 + gate = True + gate2 = False + + op = analog.pwr_squelch_ff(thr1, alpha, ramp, gate) + + op.set_threshold(thr2) + t = op.threshold() + self.assertEqual(thr2, t) + + op.set_ramp(ramp2) + r = op.ramp() + self.assertEqual(ramp2, r) + + op.set_gate(gate2) + g = op.gate() + self.assertEqual(gate2, g) + + + def test_pwr_squelch_004(self): + alpha = 0.0001 + thr = -25 + + src_data = map(lambda x: float(x)/10.0, range(1, 40)) + src = blocks.vector_source_f(src_data) + op = analog.pwr_squelch_ff(thr, alpha) + dst = blocks.vector_sink_f() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + expected_result = src_data + expected_result[0:20] = 20*[0,] + + result_data = dst.data() + self.assertFloatTuplesAlmostEqual(expected_result, result_data, 4) + +if __name__ == '__main__': + gr_unittest.run(test_pwr_squelch, "test_pwr_squelch.xml") + diff --git a/gr-analog/python/analog/qa_quadrature_demod.py b/gr-analog/python/analog/qa_quadrature_demod.py new file mode 100755 index 0000000000..bcf9aaa17f --- /dev/null +++ b/gr-analog/python/analog/qa_quadrature_demod.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import cmath + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_quadrature_demod(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_quad_demod_001(self): + f = 1000.0 + fs = 8000.0 + + src_data = [] + for i in xrange(200): + ti = i/fs + src_data.append(cmath.exp(2j*cmath.pi*f*ti)) + + # f/fs is a quarter turn per sample. + # Set the gain based on this to get 1 out. + gain = 1.0/(cmath.pi/4) + + expected_result = [0,] + 199*[1.0] + + src = blocks.vector_source_c(src_data) + op = analog.quadrature_demod_cf(gain) + dst = blocks.vector_sink_f() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + result_data = dst.data() + self.assertComplexTuplesAlmostEqual(expected_result, result_data, 5) + +if __name__ == '__main__': + gr_unittest.run(test_quadrature_demod, "test_quadrature_demod.xml") + diff --git a/gr-analog/python/analog/qa_rail_ff.py b/gr-analog/python/analog/qa_rail_ff.py new file mode 100755 index 0000000000..4a7d3f3f7d --- /dev/null +++ b/gr-analog/python/analog/qa_rail_ff.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest, analog, blocks + +def clip(x, lo, hi): + if(x < lo): + return lo + elif(x > hi): + return hi + else: + return x + +class test_rail(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_rail_001(self): + # Test set/gets + + hi1 = 1 + hi2 = 2 + lo1 = -1 + lo2 = -2 + + op = analog.rail_ff(lo1, hi1) + + op.set_hi(hi2) + h = op.hi() + self.assertEqual(hi2, h) + + op.set_lo(lo2) + l = op.lo() + self.assertEqual(lo2, l) + + def test_rail_002(self): + lo = -0.75 + hi = 0.90 + src_data = [-2, -1, -0.5, -0.25, 0, 0.25, 0.5, 1, 2] + expected_result = map(lambda x: clip(x, lo, hi), src_data) + + src = blocks.vector_source_f(src_data) + op = analog.rail_ff(lo, hi) + dst = blocks.vector_sink_f() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + result_data = dst.data() + self.assertFloatTuplesAlmostEqual(expected_result, result_data, 4) + +if __name__ == '__main__': + gr_unittest.run(test_rail, "test_rail.xml") + diff --git a/gr-analog/python/analog/qa_sig_source.py b/gr-analog/python/analog/qa_sig_source.py new file mode 100755 index 0000000000..5ee4f24af8 --- /dev/null +++ b/gr-analog/python/analog/qa_sig_source.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +# +# Copyright 2004,2007,2010,2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_sig_source(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_const_f(self): + tb = self.tb + expected_result = (1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5) + src1 = analog.sig_source_f(1e6, analog.GR_CONST_WAVE, 0, 1.5) + op = blocks.head(gr.sizeof_float, 10) + dst1 = blocks.vector_sink_f() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertEqual(expected_result, dst_data) + + def test_const_i(self): + tb = self.tb + expected_result = (1, 1, 1, 1) + src1 = analog.sig_source_i(1e6, analog.GR_CONST_WAVE, 0, 1) + op = blocks.head(gr.sizeof_int, 4) + dst1 = blocks.vector_sink_i() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertEqual(expected_result, dst_data) + + def test_sine_f(self): + tb = self.tb + sqrt2 = math.sqrt(2) / 2 + expected_result = (0, sqrt2, 1, sqrt2, 0, -sqrt2, -1, -sqrt2, 0) + src1 = analog.sig_source_f(8, analog.GR_SIN_WAVE, 1.0, 1.0) + op = blocks.head(gr.sizeof_float, 9) + dst1 = blocks.vector_sink_f() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertFloatTuplesAlmostEqual(expected_result, dst_data, 5) + + def test_cosine_f(self): + tb = self.tb + sqrt2 = math.sqrt(2) / 2 + expected_result = (1, sqrt2, 0, -sqrt2, -1, -sqrt2, 0, sqrt2, 1) + src1 = analog.sig_source_f(8, analog.GR_COS_WAVE, 1.0, 1.0) + op = blocks.head(gr.sizeof_float, 9) + dst1 = blocks.vector_sink_f() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertFloatTuplesAlmostEqual(expected_result, dst_data, 5) + + def test_sqr_c(self): + tb = self.tb #arg6 is a bit before -PI/2 + expected_result = (1j, 1j, 0, 0, 1, 1, 1+0j, 1+1j, 1j) + src1 = analog.sig_source_c(8, analog.GR_SQR_WAVE, 1.0, 1.0) + op = blocks.head(gr.sizeof_gr_complex, 9) + dst1 = blocks.vector_sink_c() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertEqual(expected_result, dst_data) + + def test_tri_c(self): + tb = self.tb + expected_result = (1+.5j, .75+.75j, .5+1j, .25+.75j, 0+.5j, + .25+.25j, .5+0j, .75+.25j, 1+.5j) + src1 = analog.sig_source_c(8, analog.GR_TRI_WAVE, 1.0, 1.0) + op = blocks.head(gr.sizeof_gr_complex, 9) + dst1 = blocks.vector_sink_c() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertComplexTuplesAlmostEqual(expected_result, dst_data, 5) + + def test_saw_c(self): + tb = self.tb + expected_result = (.5+.25j, .625+.375j, .75+.5j, .875+.625j, + 0+.75j, .125+.875j, .25+1j, .375+.125j, .5+.25j) + src1 = analog.sig_source_c(8, analog.GR_SAW_WAVE, 1.0, 1.0) + op = blocks.head(gr.sizeof_gr_complex, 9) + dst1 = blocks.vector_sink_c() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertComplexTuplesAlmostEqual(expected_result, dst_data, 5) + + def test_sqr_f(self): + tb = self.tb + expected_result = (0, 0, 0, 0, 1, 1, 1, 1, 0) + src1 = analog.sig_source_f(8, analog.GR_SQR_WAVE, 1.0, 1.0) + op = blocks.head(gr.sizeof_float, 9) + dst1 = blocks.vector_sink_f() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertEqual(expected_result, dst_data) + + def test_tri_f(self): + tb = self.tb + expected_result = (1, .75, .5, .25, 0, .25, .5, .75, 1) + src1 = analog.sig_source_f(8, analog.GR_TRI_WAVE, 1.0, 1.0) + op = blocks.head(gr.sizeof_float, 9) + dst1 = blocks.vector_sink_f() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertFloatTuplesAlmostEqual(expected_result, dst_data, 5) + + def test_saw_f(self): + tb = self.tb + expected_result = (.5, .625, .75, .875, 0, .125, .25, .375, .5) + src1 = analog.sig_source_f(8, analog.GR_SAW_WAVE, 1.0, 1.0) + op = blocks.head(gr.sizeof_float, 9) + dst1 = blocks.vector_sink_f() + tb.connect(src1, op) + tb.connect(op, dst1) + tb.run() + dst_data = dst1.data() + self.assertFloatTuplesAlmostEqual(expected_result, dst_data, 5) + +if __name__ == '__main__': + gr_unittest.run(test_sig_source, "test_sig_source.xml") diff --git a/gr-analog/python/analog/qa_simple_squelch.py b/gr-analog/python/analog/qa_simple_squelch.py new file mode 100755 index 0000000000..35f28a6122 --- /dev/null +++ b/gr-analog/python/analog/qa_simple_squelch.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# Copyright 2012,2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest, analog, blocks + +class test_simple_squelch(gr_unittest.TestCase): + + def setUp(self): + self.tb = gr.top_block() + + def tearDown(self): + self.tb = None + + def test_simple_squelch_001(self): + # Test set/gets + + alpha = 0.0001 + + thr1 = 10 + thr2 = 20 + + op = analog.simple_squelch_cc(thr1, alpha) + + op.set_threshold(thr2) + t = op.threshold() + self.assertEqual(thr2, t) + + def test_simple_squelch_002(self): + alpha = 0.0001 + thr = -25 + + src_data = map(lambda x: float(x)/10.0, range(1, 40)) + src = blocks.vector_source_c(src_data) + op = analog.simple_squelch_cc(thr, alpha) + dst = blocks.vector_sink_c() + + self.tb.connect(src, op) + self.tb.connect(op, dst) + self.tb.run() + + expected_result = src_data + expected_result[0:20] = 20*[0,] + + result_data = dst.data() + self.assertComplexTuplesAlmostEqual(expected_result, result_data, 4) + +if __name__ == '__main__': + gr_unittest.run(test_simple_squelch, "test_simple_squelch.xml") + diff --git a/gr-analog/python/analog/standard_squelch.py b/gr-analog/python/analog/standard_squelch.py new file mode 100644 index 0000000000..3ed9ebceaa --- /dev/null +++ b/gr-analog/python/analog/standard_squelch.py @@ -0,0 +1,78 @@ +# +# Copyright 2005,2007,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math +from gnuradio import gr +from gnuradio import blocks +from gnuradio import filter + +class standard_squelch(gr.hier_block2): + def __init__(self, audio_rate): + gr.hier_block2.__init__(self, "standard_squelch", + gr.io_signature(1, 1, gr.sizeof_float), # Input signature + gr.io_signature(1, 1, gr.sizeof_float)) # Output signature + + self.input_node = blocks.add_const_ff(0) # FIXME kludge + + self.low_iir = filter.iir_filter_ffd((0.0193,0,-0.0193),(1,1.9524,-0.9615)) + self.low_square = blocks.multiply_ff() + self.low_smooth = filter.single_pole_iir_filter_ff(1/(0.01*audio_rate)) # 100ms time constant + + self.hi_iir = filter.iir_filter_ffd((0.0193,0,-0.0193),(1,1.3597,-0.9615)) + self.hi_square = blocks.multiply_ff() + self.hi_smooth = filter.single_pole_iir_filter_ff(1/(0.01*audio_rate)) + + self.sub = blocks.sub_ff(); + self.add = blocks.add_ff(); + self.gate = blocks.threshold_ff(0.3,0.43,0) + self.squelch_lpf = filter.single_pole_iir_filter_ff(1/(0.01*audio_rate)) + + self.div = blocks.divide_ff() + self.squelch_mult = blocks.multiply_ff() + + self.connect(self, self.input_node) + self.connect(self.input_node, (self.squelch_mult, 0)) + + self.connect(self.input_node,self.low_iir) + self.connect(self.low_iir,(self.low_square,0)) + self.connect(self.low_iir,(self.low_square,1)) + self.connect(self.low_square,self.low_smooth,(self.sub,0)) + self.connect(self.low_smooth, (self.add,0)) + + self.connect(self.input_node,self.hi_iir) + self.connect(self.hi_iir,(self.hi_square,0)) + self.connect(self.hi_iir,(self.hi_square,1)) + self.connect(self.hi_square,self.hi_smooth,(self.sub,1)) + self.connect(self.hi_smooth, (self.add,1)) + + self.connect(self.sub, (self.div, 0)) + self.connect(self.add, (self.div, 1)) + self.connect(self.div, self.gate, self.squelch_lpf, (self.squelch_mult,1)) + self.connect(self.squelch_mult, self) + + def set_threshold(self, threshold): + self.gate.set_hi(threshold) + + def threshold(self): + return self.gate.hi() + + def squelch_range(self): + return (0.0, 1.0, 1.0/100) diff --git a/gr-analog/python/analog/wfm_rcv.py b/gr-analog/python/analog/wfm_rcv.py new file mode 100644 index 0000000000..d35d219275 --- /dev/null +++ b/gr-analog/python/analog/wfm_rcv.py @@ -0,0 +1,73 @@ +# +# Copyright 2005,2007,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, filter +from fm_emph import fm_deemph +import math + +try: + from gnuradio import analog +except ImportError: + import analog_swig as analog + +class wfm_rcv(gr.hier_block2): + def __init__ (self, quad_rate, audio_decimation): + """ + Hierarchical block for demodulating a broadcast FM signal. + + The input is the downconverted complex baseband signal (gr_complex). + The output is the demodulated audio (float). + + Args: + quad_rate: input sample rate of complex baseband input. (float) + audio_decimation: how much to decimate quad_rate to get to audio. (integer) + """ + gr.hier_block2.__init__(self, "wfm_rcv", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature(1, 1, gr.sizeof_float)) # Output signature + + volume = 20. + + max_dev = 75e3 + fm_demod_gain = quad_rate/(2*math.pi*max_dev) + audio_rate = quad_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 = analog.quadrature_demod_cf(fm_demod_gain) + + # input: float; output: float + self.deemph = fm_deemph(audio_rate) + + # compute FIR filter taps for audio filter + width_of_transition_band = audio_rate / 32 + audio_coeffs = filter.firdes.low_pass(1.0, # gain + quad_rate, # sampling rate + audio_rate/2 - width_of_transition_band, + width_of_transition_band, + filter.firdes.WIN_HAMMING) + # input: float; output: float + self.audio_filter = filter.fir_filter_fff(audio_decimation, audio_coeffs) + + self.connect (self, self.fm_demod, self.audio_filter, self.deemph, self) diff --git a/gr-analog/python/analog/wfm_rcv_fmdet.py b/gr-analog/python/analog/wfm_rcv_fmdet.py new file mode 100644 index 0000000000..b7cd1458fb --- /dev/null +++ b/gr-analog/python/analog/wfm_rcv_fmdet.py @@ -0,0 +1,228 @@ +# +# Copyright 2005,2006,2012-2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr +from gnuradio import blocks +from gnuradio import filter +from fm_emph import fm_deemph +import math + +try: + from gnuradio import analog +except ImportError: + import analog_swig as analog + +class wfm_rcv_fmdet(gr.hier_block2): + 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 = analog.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 = filter.firdes.low_pass(1.0 , # gain + demod_rate, # sampling rate + 15000 , + width_of_transition_band, + filter.firdes.WIN_HAMMING) + + # input: float; output: float + self.audio_filter = filter.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 = \ + filter.firdes.complex_band_pass(10.0, + demod_rate, + -19020, + -18980, + width_of_transition_band, + filter.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 = \ + filter.firdes.complex_band_pass(20.0, + demod_rate, + 38000-15000/2, + 38000+15000/2, + width_of_transition_band, + filter.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 = \ + filter.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 = blocks.multiply_cc(); + + # Pick off the rds signal + stereo_rds_filter_coeffs = \ + filter.firdes.complex_band_pass(30.0, + demod_rate, + 57000 - 1500, + 57000 + 1500, + width_of_transition_band, + filter.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 = \ + filter.fir_filter_fcc(audio_decimation, + stereo_rds_filter_coeffs) + self.rds_carrier_generator = blocks.multiply_cc(); + self.rds_signal_generator = blocks.multiply_cc(); + self_rds_signal_processor = blocks.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 = analog.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 = blocks.multiply_cc(); + + # pick off the real component of the basebanded L-R + # signal. The imaginary SHOULD be zero + + self.LmR_real = blocks.complex_to_real(); + self.Make_Left = blocks.add_ff(); + self.Make_Right = blocks.sub_ff(); + + self.stereo_dsbsc_filter = \ + filter.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)) + + # NOTE: mono support will require variable number of outputs in hier_block2s + # See ticket:174 in Trac database + #else: + # self.connect (self.fm_demod, self.audio_filter, self) diff --git a/gr-analog/python/analog/wfm_rcv_pll.py b/gr-analog/python/analog/wfm_rcv_pll.py new file mode 100644 index 0000000000..282e2b14be --- /dev/null +++ b/gr-analog/python/analog/wfm_rcv_pll.py @@ -0,0 +1,195 @@ +# +# Copyright 2005,2006,2012-2013 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr +from gnuradio import blocks +from gnuradio import filter +from fm_emph import fm_deemph +import math + +try: + from gnuradio import analog +except ImportError: + import analog_swig as analog + +class wfm_rcv_pll(gr.hier_block2): + 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_pll", + gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature + gr.io_signature(2, 2, gr.sizeof_float)) # Output signature + bandwidth = 250e3 + 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 + loop_bw = 2*math.pi/100.0 + max_freq = 2.0*math.pi*90e3/demod_rate + self.fm_demod = analog.pll_freqdet_cf(loop_bw, max_freq,-max_freq) + + # 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 = filter.firdes.low_pass(1.0 , # gain + demod_rate, # sampling rate + 15000 , + width_of_transition_band, + filter.firdes.WIN_HAMMING) + # input: float; output: float + self.audio_filter = filter.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 = \ + filter.firdes.complex_band_pass(10.0, + demod_rate, + -19020, + -18980, + width_of_transition_band, + filter.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 = \ + filter.firdes.complex_band_pass(20.0, + demod_rate, + 38000-15000/2, + 38000+15000/2, + width_of_transition_band, + filter.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 = \ + filter.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 = blocks.multiply_cc(); + + # Pick off the rds signal + + stereo_rds_filter_coeffs = \ + filter.firdes.complex_band_pass(30.0, + demod_rate, + 57000 - 1500, + 57000 + 1500, + width_of_transition_band, + filter.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 = \ + filter.fir_filter_fcc(audio_decimation, stereo_rds_filter_coeffs) + + self.rds_carrier_generator = blocks.multiply_cc(); + self.rds_signal_generator = blocks.multiply_cc(); + self_rds_signal_processor = blocks.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 = \ + analog.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 = blocks.multiply_cc(); + + # pick off the real component of the basebanded L-R signal. The imaginary SHOULD be zero + + self.LmR_real = blocks.complex_to_real(); + self.Make_Left = blocks.add_ff(); + self.Make_Right = blocks.sub_ff(); + + self.stereo_dsbsc_filter = \ + filter.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 + self.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) + 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 + self.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 + 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)) + # NOTE: mono support will require variable number of outputs in hier_block2s + # See ticket:174 in Trac database + #else: + # self.connect (self.fm_demod, self.audio_filter, self) diff --git a/gr-analog/python/analog/wfm_tx.py b/gr-analog/python/analog/wfm_tx.py new file mode 100644 index 0000000000..be662310db --- /dev/null +++ b/gr-analog/python/analog/wfm_tx.py @@ -0,0 +1,82 @@ +# +# Copyright 2005,2007,2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import math +from gnuradio import gr +from gnuradio import filter +from fm_emph import fm_preemph + +try: + from gnuradio import analog +except ImportError: + import analog_swig as analog + +class wfm_tx(gr.hier_block2): + def __init__(self, audio_rate, quad_rate, tau=75e-6, max_dev=75e3): + """ + Wide Band FM Transmitter. + + Takes a single float input stream of audio samples in the range [-1,+1] + and produces a single FM modulated complex baseband output. + + Args: + audio_rate: sample rate of audio stream, >= 16k (integer) + quad_rate: sample rate of output stream (integer) + tau: preemphasis time constant (default 75e-6) (float) + max_dev: maximum deviation in Hz (default 75e3) (float) + + quad_rate must be an integer multiple of audio_rate. + """ + gr.hier_block2.__init__(self, "wfm_tx", + gr.io_signature(1, 1, gr.sizeof_float), # Input signature + gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature + + # FIXME audio_rate and quad_rate ought to be exact rationals + audio_rate = int(audio_rate) + quad_rate = int(quad_rate) + + if quad_rate % audio_rate != 0: + raise ValueError, "quad_rate is not an integer multiple of audio_rate" + + + do_interp = audio_rate != quad_rate + + if do_interp: + interp_factor = quad_rate / audio_rate + interp_taps = filter.optfir.low_pass(interp_factor, # gain + quad_rate, # Fs + 16000, # passband cutoff + 18000, # stopband cutoff + 0.1, # passband ripple dB + 40) # stopband atten dB + + print "len(interp_taps) =", len(interp_taps) + self.interpolator = filter.interp_fir_filter_fff (interp_factor, interp_taps) + + self.preemph = fm_preemph(quad_rate, tau=tau) + + k = 2 * math.pi * max_dev / quad_rate + self.modulator = analog.frequency_modulator_fc (k) + + if do_interp: + self.connect(self, self.interpolator, self.preemph, self.modulator, self) + else: + self.connect(self, self.preemph, self.modulator, self) |