diff options
-rw-r--r-- | gr-filter/include/filter/CMakeLists.txt | 3 | ||||
-rw-r--r-- | gr-filter/include/filter/polyphase_filterbank.h | 148 | ||||
-rw-r--r-- | gr-filter/lib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | gr-filter/lib/pfb_channelizer_ccf_impl.cc | 102 | ||||
-rw-r--r-- | gr-filter/lib/pfb_channelizer_ccf_impl.h | 24 | ||||
-rw-r--r-- | gr-filter/lib/polyphase_filterbank.cc | 114 |
6 files changed, 302 insertions, 90 deletions
diff --git a/gr-filter/include/filter/CMakeLists.txt b/gr-filter/include/filter/CMakeLists.txt index c64178696..0c067fcba 100644 --- a/gr-filter/include/filter/CMakeLists.txt +++ b/gr-filter/include/filter/CMakeLists.txt @@ -76,10 +76,11 @@ add_custom_target(filter_generated_includes DEPENDS install(FILES api.h firdes.h - pm_remez.h fir_filter.h fir_filter_with_buffer.h fft_filter.h + pm_remez.h + polyphase_filterbank.h ${generated_includes} dc_blocker_cc.h dc_blocker_ff.h diff --git a/gr-filter/include/filter/polyphase_filterbank.h b/gr-filter/include/filter/polyphase_filterbank.h new file mode 100644 index 000000000..50d087340 --- /dev/null +++ b/gr-filter/include/filter/polyphase_filterbank.h @@ -0,0 +1,148 @@ +/* -*- c++ -*- */ +/* + * 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. + */ + + +#ifndef INCLUDED_FILTER_POLYPHASE_FILTERBANK_H +#define INCLUDED_FILTER_POLYPHASE_FILTERBANK_H + +#include <filter/api.h> +#include <filter/fir_filter.h> +#include <fft/fft.h> + +namespace gr { + namespace filter { + namespace kernel { + + /*! + * \class polyphase_filterbank + * + * \brief Polyphase filterbank parent class + * + * \ingroup filter_blk + * \ingroup pfb_blk + * + * This block takes in complex inputs and channelizes it to + * <EM>M</EM> channels of equal bandwidth. Each of the resulting + * channels is decimated to the new rate that is the input + * sampling rate <EM>fs</EM> divided by the number of channels, + * <EM>M</EM>. + * + * The PFB channelizer code takes the taps generated above and + * builds a set of filters. The set contains <EM>M</EM> number + * of filters and each filter contains ceil(taps.size()/decim) + * number of taps. Each tap from the filter prototype is + * sequentially inserted into the next filter. When all of the + * input taps are used, the remaining filters in the filterbank + * are filled out with 0's to make sure each filter has the same + * number of taps. + * + * Each filter operates using the gr_fir filter classs of GNU + * Radio, which takes the input stream at <EM>i</EM> and + * performs the inner product calculation to <EM>i+(n-1)</EM> + * where <EM>n</EM> is the number of filter taps. To efficiently + * handle this in the GNU Radio structure, each filter input + * must come from its own input stream. So the channelizer must + * be provided with <EM>M</EM> streams where the input stream + * has been deinterleaved. This is most easily done using the + * gr_stream_to_streams block. + * + * The output is then produced as a vector, where index + * <EM>i</EM> in the vector is the next sample from the + * <EM>i</EM>th channel. This is most easily handled by sending + * the output to a gr_vector_to_streams block to handle the + * conversion and passing <EM>M</EM> streams out. + * + * The input and output formatting is done using a hier_block2 + * called pfb_channelizer_ccf. This can take in a single stream + * and outputs <EM>M</EM> streams based on the behavior + * described above. + * + * The filter's taps should be based on the input sampling rate. + * + * For example, using the GNU Radio's firdes utility to building + * filters, we build a low-pass filter with a sampling rate of + * <EM>fs</EM>, a 3-dB bandwidth of <EM>BW</EM> and a transition + * bandwidth of <EM>TB</EM>. We can also specify the out-of-band + * attenuation to use, <EM>ATT</EM>, and the filter window + * function (a Blackman-harris window in this case). The first + * input is the gain of the filter, which we specify here as + * unity. + * + * <B><EM>self._taps = gr.firdes.low_pass_2(1, fs, BW, TB, + * attenuation_dB=ATT, window=gr.firdes.WIN_BLACKMAN_hARRIS)</EM></B> + * + * More on the theory of polyphase filterbanks can be found in + * the following book. + * + * <B><EM>f. harris, "Multirate Signal Processing for + * Communication Systems," Upper Saddle River, NJ: + * Prentice Hall, Inc. 2004.</EM></B> + * + */ + + class FILTER_API polyphase_filterbank + { + protected: + unsigned int d_nfilts; + std::vector<kernel::fir_filter_ccf*> d_filters; + std::vector< std::vector<float> > d_taps; + unsigned int d_taps_per_filter; + fft::fft_complex *d_fft; + + public: + /*! + * Build the polyphase filterbank decimator. + * \param nfilts (unsigned integer) Specifies the number of + * channels <EM>M</EM> + * \param taps (vector/list of floats) The prototype filter to + * populate the filterbank. + */ + polyphase_filterbank(unsigned int nfilts, + const std::vector<float> &taps); + + ~polyphase_filterbank(); + + /*! + * Update the filterbank's filter taps from a prototype + * filter. + * + * \param taps (vector/list of floats) The prototype filter to + * populate the filterbank. + */ + void set_taps(const std::vector<float> &taps); + + /*! + * Print all of the filterbank taps to screen. + */ + void print_taps(); + + /*! + * Return a vector<vector<>> of the filterbank taps + */ + std::vector<std::vector<float> > taps() const; + }; + + } /* namespace kernel */ + } /* namespace filter */ +} /* namespace gr */ + +#endif /* INCLUDED_FILTER_POLYPHASE_FILTERBANK_H */ diff --git a/gr-filter/lib/CMakeLists.txt b/gr-filter/lib/CMakeLists.txt index 5930ec9bb..b7577ad5e 100644 --- a/gr-filter/lib/CMakeLists.txt +++ b/gr-filter/lib/CMakeLists.txt @@ -110,6 +110,7 @@ list(APPEND filter_sources fft_filter.cc firdes.cc pm_remez.cc + polyphase_filterbank.cc ${generated_sources} dc_blocker_cc_impl.cc dc_blocker_ff_impl.cc diff --git a/gr-filter/lib/pfb_channelizer_ccf_impl.cc b/gr-filter/lib/pfb_channelizer_ccf_impl.cc index 4006db789..4e06cbc5f 100644 --- a/gr-filter/lib/pfb_channelizer_ccf_impl.cc +++ b/gr-filter/lib/pfb_channelizer_ccf_impl.cc @@ -26,26 +26,26 @@ #include "pfb_channelizer_ccf_impl.h" #include <gr_io_signature.h> -#include <cstdio> namespace gr { namespace filter { - pfb_channelizer_ccf::sptr pfb_channelizer_ccf::make(unsigned int numchans, + pfb_channelizer_ccf::sptr pfb_channelizer_ccf::make(unsigned int nfilts, const std::vector<float> &taps, float oversample_rate) { - return gnuradio::get_initial_sptr(new pfb_channelizer_ccf_impl(numchans, taps, + return gnuradio::get_initial_sptr(new pfb_channelizer_ccf_impl(nfilts, taps, oversample_rate)); } - pfb_channelizer_ccf_impl::pfb_channelizer_ccf_impl(unsigned int numchans, + pfb_channelizer_ccf_impl::pfb_channelizer_ccf_impl(unsigned int nfilts, const std::vector<float> &taps, float oversample_rate) : gr_block("pfb_channelizer_ccf", - gr_make_io_signature(numchans, numchans, sizeof(gr_complex)), - gr_make_io_signature(1, numchans, sizeof(gr_complex))), - d_updated(false), d_numchans(numchans), d_oversample_rate(oversample_rate) + gr_make_io_signature(nfilts, nfilts, sizeof(gr_complex)), + gr_make_io_signature(1, nfilts, sizeof(gr_complex))), + polyphase_filterbank(nfilts, taps), + d_updated(false), d_oversample_rate(oversample_rate) { // The over sampling rate must be rationally related to the number of channels // in that it must be N/i for i in [1,N], which gives an outputsample rate @@ -53,110 +53,62 @@ namespace gr { // This tests the specified input sample rate to see if it conforms to this // requirement within a few significant figures. double intp = 0; - double fltp = modf(numchans / oversample_rate, &intp); + double fltp = modf(nfilts / oversample_rate, &intp); if(fltp != 0.0) throw std::invalid_argument("pfb_channelizer: oversample rate must be N/i for i in [1, N]"); set_relative_rate(1.0/intp); - d_filters = std::vector<kernel::fir_filter_ccf*>(d_numchans); - d_channel_map.resize(d_numchans); - - // Create an FIR filter for each channel and zero out the taps - std::vector<float> vtaps(0, d_numchans); - for(unsigned int i = 0; i < d_numchans; i++) { - d_filters[i] = new kernel::fir_filter_ccf(1, vtaps); + // Default channel map + d_channel_map.resize(d_nfilts); + for(unsigned int i = 0; i < d_nfilts; i++) { d_channel_map[i] = i; } - // Now, actually set the filters' taps - set_taps(taps); - - // Create the FFT to handle the output de-spinning of the channels - d_fft = new fft::fft_complex(d_numchans, false); - // Although the filters change, we use this look up table // to set the index of the FFT input buffer, which equivalently // performs the FFT shift operation on every other turn. - d_rate_ratio = (int)rintf(d_numchans / d_oversample_rate); - d_idxlut = new int[d_numchans]; - for(unsigned int i = 0; i < d_numchans; i++) { - d_idxlut[i] = d_numchans - ((i + d_rate_ratio) % d_numchans) - 1; + d_rate_ratio = (int)rintf(d_nfilts / d_oversample_rate); + d_idxlut = new int[d_nfilts]; + for(unsigned int i = 0; i < d_nfilts; i++) { + d_idxlut[i] = d_nfilts - ((i + d_rate_ratio) % d_nfilts) - 1; } // Calculate the number of filtering rounds to do to evenly // align the input vectors with the output channels d_output_multiple = 1; - while((d_output_multiple * d_rate_ratio) % d_numchans != 0) + while((d_output_multiple * d_rate_ratio) % d_nfilts != 0) d_output_multiple++; set_output_multiple(d_output_multiple); + + set_history (d_taps_per_filter+1); } pfb_channelizer_ccf_impl::~pfb_channelizer_ccf_impl() { delete [] d_idxlut; - delete d_fft; - - for(unsigned int i = 0; i < d_numchans; i++) { - delete d_filters[i]; - } } void - pfb_channelizer_ccf_impl::set_taps (const std::vector<float> &taps) + pfb_channelizer_ccf_impl::set_taps(const std::vector<float> &taps) { gruel::scoped_lock guard(d_mutex); - unsigned int i,j; - - unsigned int ntaps = taps.size(); - d_taps_per_filter = (unsigned int)ceil((double)ntaps/(double)d_numchans); - - // Create d_numchan vectors to store each channel's taps - d_taps.resize(d_numchans); - // Make a vector of the taps plus fill it out with 0's to fill - // each polyphase filter with exactly d_taps_per_filter - std::vector<float> tmp_taps; - tmp_taps = taps; - while((float)(tmp_taps.size()) < d_numchans*d_taps_per_filter) { - tmp_taps.push_back(0.0); - } - - // Partition the filter - for(i = 0; i < d_numchans; i++) { - // Each channel uses all d_taps_per_filter with 0's if not enough taps to fill out - d_taps[i] = std::vector<float>(d_taps_per_filter, 0); - for(j = 0; j < d_taps_per_filter; j++) { - d_taps[i][j] = tmp_taps[i + j*d_numchans]; // add taps to channels in reverse order - } - - // Build a filter for each channel and add it's taps to it - d_filters[i]->set_taps(d_taps[i]); - } - - // Set the history to ensure enough input items for each filter + polyphase_filterbank::set_taps(taps); set_history (d_taps_per_filter+1); - d_updated = true; } void pfb_channelizer_ccf_impl::print_taps() { - unsigned int i, j; - for(i = 0; i < d_numchans; i++) { - printf("filter[%d]: [", i); - for(j = 0; j < d_taps_per_filter; j++) { - printf(" %.4e", d_taps[i][j]); - } - printf("]\n\n"); - } + polyphase_filterbank::print_taps(); } - std::vector< std::vector<float> > + std::vector<std::vector<float> > pfb_channelizer_ccf_impl::taps() const { - return d_taps; + return polyphase_filterbank::taps(); } void @@ -167,7 +119,7 @@ namespace gr { if(map.size() > 0) { unsigned int max = (unsigned int)*std::max_element(map.begin(), map.end()); unsigned int min = (unsigned int)*std::min_element(map.begin(), map.end()); - if((max >= d_numchans) || (min < 0)) { + if((max >= d_nfilts) || (min < 0)) { throw std::invalid_argument("pfb_channelizer_ccf_impl::set_channel_map: map range out of bounds.\n"); } d_channel_map = map; @@ -202,7 +154,7 @@ namespace gr { int toconsume = (int)rintf(noutput_items/d_oversample_rate); while(n <= toconsume) { j = 0; - i = (i + d_rate_ratio) % d_numchans; + i = (i + d_rate_ratio) % d_nfilts; last = i; while(i >= 0) { in = (gr_complex*)input_items[j]; @@ -211,7 +163,7 @@ namespace gr { i--; } - i = d_numchans-1; + i = d_nfilts-1; while(i > last) { in = (gr_complex*)input_items[j]; d_fft->get_inbuf()[d_idxlut[j]] = d_filters[i]->filter(&in[n-1]); @@ -219,7 +171,7 @@ namespace gr { i--; } - n += (i+d_rate_ratio) >= (int)d_numchans; + n += (i+d_rate_ratio) >= (int)d_nfilts; // despin through FFT d_fft->execute(); diff --git a/gr-filter/lib/pfb_channelizer_ccf_impl.h b/gr-filter/lib/pfb_channelizer_ccf_impl.h index ee9327663..8cabc4e34 100644 --- a/gr-filter/lib/pfb_channelizer_ccf_impl.h +++ b/gr-filter/lib/pfb_channelizer_ccf_impl.h @@ -24,30 +24,26 @@ #define INCLUDED_FILTER_PFB_CHANNELIZER_CCF_IMPL_H #include <filter/pfb_channelizer_ccf.h> +#include <filter/polyphase_filterbank.h> #include <filter/fir_filter.h> #include <fft/fft.h> namespace gr { namespace filter { - class FILTER_API pfb_channelizer_ccf_impl : public pfb_channelizer_ccf + class FILTER_API pfb_channelizer_ccf_impl : public pfb_channelizer_ccf, kernel::polyphase_filterbank { private: - bool d_updated; - unsigned int d_numchans; - float d_oversample_rate; - std::vector<kernel::fir_filter_ccf*> d_filters; - std::vector< std::vector<float> > d_taps; - unsigned int d_taps_per_filter; - fft::fft_complex *d_fft; - int *d_idxlut; - int d_rate_ratio; - int d_output_multiple; - std::vector<int> d_channel_map; - gruel::mutex d_mutex; // mutex to protect set/work access + bool d_updated; + float d_oversample_rate; + int *d_idxlut; + int d_rate_ratio; + int d_output_multiple; + std::vector<int> d_channel_map; + gruel::mutex d_mutex; // mutex to protect set/work access public: - pfb_channelizer_ccf_impl(unsigned int numchans, + pfb_channelizer_ccf_impl(unsigned int nfilts, const std::vector<float> &taps, float oversample_rate); diff --git a/gr-filter/lib/polyphase_filterbank.cc b/gr-filter/lib/polyphase_filterbank.cc new file mode 100644 index 000000000..9d881d76e --- /dev/null +++ b/gr-filter/lib/polyphase_filterbank.cc @@ -0,0 +1,114 @@ +/* -*- c++ -*- */ +/* + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <filter/polyphase_filterbank.h> +#include <cstdio> + +namespace gr { + namespace filter { + namespace kernel { + + polyphase_filterbank::polyphase_filterbank(unsigned int nfilts, + const std::vector<float> &taps) + : d_nfilts(nfilts) + { + d_filters = std::vector<kernel::fir_filter_ccf*>(d_nfilts); + + // Create an FIR filter for each channel and zero out the taps + std::vector<float> vtaps(0, d_nfilts); + for(unsigned int i = 0; i < d_nfilts; i++) { + d_filters[i] = new kernel::fir_filter_ccf(1, vtaps); + } + + // Now, actually set the filters' taps + set_taps(taps); + + // Create the FFT to handle the output de-spinning of the channels + d_fft = new fft::fft_complex(d_nfilts, false); + } + + polyphase_filterbank::~polyphase_filterbank() + { + delete d_fft; + for(unsigned int i = 0; i < d_nfilts; i++) { + delete d_filters[i]; + } + } + + void + polyphase_filterbank::set_taps (const std::vector<float> &taps) + { + unsigned int i,j; + + unsigned int ntaps = taps.size(); + d_taps_per_filter = (unsigned int)ceil((double)ntaps/(double)d_nfilts); + + // Create d_numchan vectors to store each channel's taps + d_taps.resize(d_nfilts); + + // Make a vector of the taps plus fill it out with 0's to fill + // each polyphase filter with exactly d_taps_per_filter + std::vector<float> tmp_taps; + tmp_taps = taps; + while((float)(tmp_taps.size()) < d_nfilts*d_taps_per_filter) { + tmp_taps.push_back(0.0); + } + + // Partition the filter + for(i = 0; i < d_nfilts; i++) { + // Each channel uses all d_taps_per_filter with 0's if not enough taps to fill out + d_taps[i] = std::vector<float>(d_taps_per_filter, 0); + for(j = 0; j < d_taps_per_filter; j++) { + d_taps[i][j] = tmp_taps[i + j*d_nfilts]; // add taps to channels in reverse order + } + + // Build a filter for each channel and add it's taps to it + d_filters[i]->set_taps(d_taps[i]); + } + } + + void + polyphase_filterbank::print_taps() + { + unsigned int i, j; + for(i = 0; i < d_nfilts; i++) { + printf("filter[%d]: [", i); + for(j = 0; j < d_taps_per_filter; j++) { + printf(" %.4e", d_taps[i][j]); + } + printf("]\n\n"); + } + } + + std::vector< std::vector<float> > + polyphase_filterbank::taps() const + { + return d_taps; + } + + } /* namespace kernel */ + } /* namespace filter */ +} /* namespace gr */ |