diff options
author | Tom Rondeau | 2012-06-19 20:59:13 -0400 |
---|---|---|
committer | Tom Rondeau | 2012-06-19 20:59:13 -0400 |
commit | a5a03740b546cc0b4f3dc631e4cf1f354a143652 (patch) | |
tree | e0f1b86a720eaf22204a5dfbba82f0fd8d8fb7b4 /gr-filter/python/optfir.py | |
parent | fad32dc397d731493dc88367d2b4648b35bb905b (diff) | |
download | gnuradio-a5a03740b546cc0b4f3dc631e4cf1f354a143652.tar.gz gnuradio-a5a03740b546cc0b4f3dc631e4cf1f354a143652.tar.bz2 gnuradio-a5a03740b546cc0b4f3dc631e4cf1f354a143652.zip |
filter: Moved over filter-specific Python hier_blocks.
Diffstat (limited to 'gr-filter/python/optfir.py')
-rw-r--r-- | gr-filter/python/optfir.py | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/gr-filter/python/optfir.py b/gr-filter/python/optfir.py new file mode 100644 index 000000000..bccb8c68d --- /dev/null +++ b/gr-filter/python/optfir.py @@ -0,0 +1,339 @@ +# +# Copyright 2004,2005,2009 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. +# + +''' +Routines for designing optimal FIR filters. + +For a great intro to how all this stuff works, see section 6.6 of +"Digital Signal Processing: A Practical Approach", Emmanuael C. Ifeachor +and Barrie W. Jervis, Adison-Wesley, 1993. ISBN 0-201-54413-X. +''' + +import math, cmath +import filter_swig as filter + +# ---------------------------------------------------------------- + +## Builds a low pass filter. +# @param gain Filter gain in the passband (linear) +# @param Fs Sampling rate (sps) +# @param freq1 End of pass band (in Hz) +# @param freq2 Start of stop band (in Hz) +# @param passband_ripple_db Pass band ripple in dB (should be small, < 1) +# @param stopband_atten_db Stop band attenuation in dB (should be large, >= 60) +# @param nextra_taps Extra taps to use in the filter (default=2) +def low_pass (gain, Fs, freq1, freq2, passband_ripple_db, stopband_atten_db, + nextra_taps=2): + passband_dev = passband_ripple_to_dev (passband_ripple_db) + stopband_dev = stopband_atten_to_dev (stopband_atten_db) + desired_ampls = (gain, 0) + (n, fo, ao, w) = remezord ([freq1, freq2], desired_ampls, + [passband_dev, stopband_dev], Fs) + # The remezord typically under-estimates the filter order, so add 2 taps by default + taps = filter.pm_remez (n + nextra_taps, fo, ao, w, "bandpass") + return taps + +## Builds a band pass filter. +# @param gain Filter gain in the passband (linear) +# @param Fs Sampling rate (sps) +# @param freq_sb1 End of stop band (in Hz) +# @param freq_pb1 Start of pass band (in Hz) +# @param freq_pb2 End of pass band (in Hz) +# @param freq_sb2 Start of stop band (in Hz) +# @param passband_ripple_db Pass band ripple in dB (should be small, < 1) +# @param stopband_atten_db Stop band attenuation in dB (should be large, >= 60) +# @param nextra_taps Extra taps to use in the filter (default=2) +def band_pass (gain, Fs, freq_sb1, freq_pb1, freq_pb2, freq_sb2, + passband_ripple_db, stopband_atten_db, + nextra_taps=2): + passband_dev = passband_ripple_to_dev (passband_ripple_db) + stopband_dev = stopband_atten_to_dev (stopband_atten_db) + desired_ampls = (0, gain, 0) + desired_freqs = [freq_sb1, freq_pb1, freq_pb2, freq_sb2] + desired_ripple = [stopband_dev, passband_dev, stopband_dev] + (n, fo, ao, w) = remezord (desired_freqs, desired_ampls, + desired_ripple, Fs) + # The remezord typically under-estimates the filter order, so add 2 taps by default + taps = filter.pm_remez (n + nextra_taps, fo, ao, w, "bandpass") + return taps + + +## Builds a band pass filter with complex taps by making an LPF and +# spinning it up to the right center frequency +# @param gain Filter gain in the passband (linear) +# @param Fs Sampling rate (sps) +# @param freq_sb1 End of stop band (in Hz) +# @param freq_pb1 Start of pass band (in Hz) +# @param freq_pb2 End of pass band (in Hz) +# @param freq_sb2 Start of stop band (in Hz) +# @param passband_ripple_db Pass band ripple in dB (should be small, < 1) +# @param stopband_atten_db Stop band attenuation in dB (should be large, >= 60) +# @param nextra_taps Extra taps to use in the filter (default=2) +def complex_band_pass (gain, Fs, freq_sb1, freq_pb1, freq_pb2, freq_sb2, + passband_ripple_db, stopband_atten_db, + nextra_taps=2): + center_freq = (freq_pb2 + freq_pb1) / 2.0 + lp_pb = (freq_pb2 - center_freq)/1.0 + lp_sb = freq_sb2 - center_freq + lptaps = low_pass(gain, Fs, lp_pb, lp_sb, passband_ripple_db, + stopband_atten_db, nextra_taps) + spinner = [cmath.exp(2j*cmath.pi*center_freq/Fs*i) for i in xrange(len(lptaps))] + taps = [s*t for s,t in zip(spinner, lptaps)] + return taps + + +## Builds a band reject filter +# spinning it up to the right center frequency +# @param gain Filter gain in the passband (linear) +# @param Fs Sampling rate (sps) +# @param freq_pb1 End of pass band (in Hz) +# @param freq_sb1 Start of stop band (in Hz) +# @param freq_sb2 End of stop band (in Hz) +# @param freq_pb2 Start of pass band (in Hz) +# @param passband_ripple_db Pass band ripple in dB (should be small, < 1) +# @param stopband_atten_db Stop band attenuation in dB (should be large, >= 60) +# @param nextra_taps Extra taps to use in the filter (default=2) +def band_reject (gain, Fs, freq_pb1, freq_sb1, freq_sb2, freq_pb2, + passband_ripple_db, stopband_atten_db, + nextra_taps=2): + passband_dev = passband_ripple_to_dev (passband_ripple_db) + stopband_dev = stopband_atten_to_dev (stopband_atten_db) + desired_ampls = (gain, 0, gain) + desired_freqs = [freq_pb1, freq_sb1, freq_sb2, freq_pb2] + desired_ripple = [passband_dev, stopband_dev, passband_dev] + (n, fo, ao, w) = remezord (desired_freqs, desired_ampls, + desired_ripple, Fs) + # Make sure we use an odd number of taps + if((n+nextra_taps)%2 == 1): + n += 1 + # The remezord typically under-estimates the filter order, so add 2 taps by default + taps = filter.pm_remez (n + nextra_taps, fo, ao, w, "bandpass") + return taps + + +## Builds a high pass filter. +# @param gain Filter gain in the passband (linear) +# @param Fs Sampling rate (sps) +# @param freq1 End of stop band (in Hz) +# @param freq2 Start of pass band (in Hz) +# @param passband_ripple_db Pass band ripple in dB (should be small, < 1) +# @param stopband_atten_db Stop band attenuation in dB (should be large, >= 60) +# @param nextra_taps Extra taps to use in the filter (default=2) +def high_pass (gain, Fs, freq1, freq2, passband_ripple_db, stopband_atten_db, + nextra_taps=2): + passband_dev = passband_ripple_to_dev (passband_ripple_db) + stopband_dev = stopband_atten_to_dev (stopband_atten_db) + desired_ampls = (0, 1) + (n, fo, ao, w) = remezord ([freq1, freq2], desired_ampls, + [stopband_dev, passband_dev], Fs) + # For a HPF, we need to use an odd number of taps + # In filter.remez, ntaps = n+1, so n must be even + if((n+nextra_taps)%2 == 1): + n += 1 + + # The remezord typically under-estimates the filter order, so add 2 taps by default + taps = filter.pm_remez (n + nextra_taps, fo, ao, w, "bandpass") + return taps + +# ---------------------------------------------------------------- + +def stopband_atten_to_dev (atten_db): + """Convert a stopband attenuation in dB to an absolute value""" + return 10**(-atten_db/20) + +def passband_ripple_to_dev (ripple_db): + """Convert passband ripple spec expressed in dB to an absolute value""" + return (10**(ripple_db/20)-1)/(10**(ripple_db/20)+1) + +# ---------------------------------------------------------------- + +def remezord (fcuts, mags, devs, fsamp = 2): + ''' + FIR order estimator (lowpass, highpass, bandpass, mulitiband). + + (n, fo, ao, w) = remezord (f, a, dev) + (n, fo, ao, w) = remezord (f, a, dev, fs) + + (n, fo, ao, w) = remezord (f, a, dev) finds the approximate order, + normalized frequency band edges, frequency band amplitudes, and + weights that meet input specifications f, a, and dev, to use with + the remez command. + + * f is a sequence of frequency band edges (between 0 and Fs/2, where + Fs is the sampling frequency), and a is a sequence specifying the + desired amplitude on the bands defined by f. The length of f is + twice the length of a, minus 2. The desired function is + piecewise constant. + + * dev is a sequence the same size as a that specifies the maximum + allowable deviation or ripples between the frequency response + and the desired amplitude of the output filter, for each band. + + Use remez with the resulting order n, frequency sequence fo, + amplitude response sequence ao, and weights w to design the filter b + which approximately meets the specifications given by remezord + input parameters f, a, and dev: + + b = remez (n, fo, ao, w) + + (n, fo, ao, w) = remezord (f, a, dev, Fs) specifies a sampling frequency Fs. + + Fs defaults to 2 Hz, implying a Nyquist frequency of 1 Hz. You can + therefore specify band edges scaled to a particular applications + sampling frequency. + + In some cases remezord underestimates the order n. If the filter + does not meet the specifications, try a higher order such as n+1 + or n+2. + ''' + # get local copies + fcuts = fcuts[:] + mags = mags[:] + devs = devs[:] + + for i in range (len (fcuts)): + fcuts[i] = float (fcuts[i]) / fsamp + + nf = len (fcuts) + nm = len (mags) + nd = len (devs) + nbands = nm + + if nm != nd: + raise ValueError, "Length of mags and devs must be equal" + + if nf != 2 * (nbands - 1): + raise ValueError, "Length of f must be 2 * len (mags) - 2" + + for i in range (len (mags)): + if mags[i] != 0: # if not stopband, get relative deviation + devs[i] = devs[i] / mags[i] + + # separate the passband and stopband edges + f1 = fcuts[0::2] + f2 = fcuts[1::2] + + n = 0 + min_delta = 2 + for i in range (len (f1)): + if f2[i] - f1[i] < min_delta: + n = i + min_delta = f2[i] - f1[i] + + if nbands == 2: + # lowpass or highpass case (use formula) + l = lporder (f1[n], f2[n], devs[0], devs[1]) + else: + # bandpass or multipass case + # try different lowpasses and take the worst one that + # goes through the BP specs + l = 0 + for i in range (1, nbands-1): + l1 = lporder (f1[i-1], f2[i-1], devs[i], devs[i-1]) + l2 = lporder (f1[i], f2[i], devs[i], devs[i+1]) + l = max (l, l1, l2) + + n = int (math.ceil (l)) - 1 # need order, not length for remez + + # cook up remez compatible result + ff = [0] + fcuts + [1] + for i in range (1, len (ff) - 1): + ff[i] *= 2 + + aa = [] + for a in mags: + aa = aa + [a, a] + + max_dev = max (devs) + wts = [1] * len(devs) + for i in range (len (wts)): + wts[i] = max_dev / devs[i] + + return (n, ff, aa, wts) + +# ---------------------------------------------------------------- + +def lporder (freq1, freq2, delta_p, delta_s): + ''' + FIR lowpass filter length estimator. freq1 and freq2 are + normalized to the sampling frequency. delta_p is the passband + deviation (ripple), delta_s is the stopband deviation (ripple). + + Note, this works for high pass filters too (freq1 > freq2), but + doesnt work well if the transition is near f == 0 or f == fs/2 + + From Herrmann et al (1973), Practical design rules for optimum + finite impulse response filters. Bell System Technical J., 52, 769-99 + ''' + df = abs (freq2 - freq1) + ddp = math.log10 (delta_p) + dds = math.log10 (delta_s) + + a1 = 5.309e-3 + a2 = 7.114e-2 + a3 = -4.761e-1 + a4 = -2.66e-3 + a5 = -5.941e-1 + a6 = -4.278e-1 + + b1 = 11.01217 + b2 = 0.5124401 + + t1 = a1 * ddp * ddp + t2 = a2 * ddp + t3 = a4 * ddp * ddp + t4 = a5 * ddp + + dinf=((t1 + t2 + a3) * dds) + (t3 + t4 + a6) + ff = b1 + b2 * (ddp - dds) + n = dinf / df - ff * df + 1 + return n + + +def bporder (freq1, freq2, delta_p, delta_s): + ''' + FIR bandpass filter length estimator. freq1 and freq2 are + normalized to the sampling frequency. delta_p is the passband + deviation (ripple), delta_s is the stopband deviation (ripple). + + From Mintzer and Liu (1979) + ''' + df = abs (freq2 - freq1) + ddp = math.log10 (delta_p) + dds = math.log10 (delta_s) + + a1 = 0.01201 + a2 = 0.09664 + a3 = -0.51325 + a4 = 0.00203 + a5 = -0.57054 + a6 = -0.44314 + + t1 = a1 * ddp * ddp + t2 = a2 * ddp + t3 = a4 * ddp * ddp + t4 = a5 * ddp + + cinf = dds * (t1 + t2 + a3) + t3 + t4 + a6 + ginf = -14.6 * math.log10 (delta_p / delta_s) - 16.9 + n = cinf / df + ginf * df + 1 + return n + |