annotate Source/Utility/IIRFilter.h @ 56:b4a2d2ae43cf tip

merge
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Fri, 23 Nov 2018 15:48:14 +0000
parents 3580ffe87dc8
children
rev   line source
andrewm@0 1 /*
andrewm@0 2 TouchKeys: multi-touch musical keyboard control software
andrewm@0 3 Copyright (c) 2013 Andrew McPherson
andrewm@0 4
andrewm@0 5 This program is free software: you can redistribute it and/or modify
andrewm@0 6 it under the terms of the GNU General Public License as published by
andrewm@0 7 the Free Software Foundation, either version 3 of the License, or
andrewm@0 8 (at your option) any later version.
andrewm@0 9
andrewm@0 10 This program is distributed in the hope that it will be useful,
andrewm@0 11 but WITHOUT ANY WARRANTY; without even the implied warranty of
andrewm@0 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
andrewm@0 13 GNU General Public License for more details.
andrewm@0 14
andrewm@0 15 You should have received a copy of the GNU General Public License
andrewm@0 16 along with this program. If not, see <http://www.gnu.org/licenses/>.
andrewm@0 17
andrewm@0 18 =====================================================================
andrewm@0 19
andrewm@0 20 IIRFilter.h: template class handling an Nth-order IIR filter on data
andrewm@0 21 in a given Node.
andrewm@0 22 */
andrewm@0 23
andrewm@0 24 #ifndef touchkeys_IIRFilter_h
andrewm@0 25 #define touchkeys_IIRFilter_h
andrewm@0 26
andrewm@0 27
andrewm@0 28 #include <iostream>
andrewm@0 29 #include <exception>
andrewm@0 30 #include <vector>
andrewm@0 31 #include "Node.h"
andrewm@0 32
andrewm@0 33 /*
andrewm@0 34 * IIRFilterNode
andrewm@0 35 *
andrewm@0 36 * This template class performs IIR (infinite impulse response) filtering on incoming Node data.
andrewm@0 37 * It does not take into account timestamps so assumes the data is regularly sampled. The filter
andrewm@0 38 * coefficients can be specified and changed, and the operation is selectable between always
andrewm@0 39 * filtering on each new sample or only filtering on request. In the latter case, it will go back
andrewm@0 40 * and filter from the most recent available sample, assuming the signal starts from 0 if there is
andrewm@0 41 * any break in data between what was already calculated and what input data is now available.
andrewm@0 42 */
andrewm@0 43
andrewm@0 44 template<typename DataType>
andrewm@0 45 class IIRFilterNode : public Node<DataType> {
andrewm@0 46 public:
andrewm@0 47 typedef typename Node<DataType>::capacity_type capacity_type;
andrewm@0 48 //typedef typename Node<return_type>::size_type size_type;
andrewm@0 49
andrewm@0 50 // ***** Constructors *****
andrewm@0 51
andrewm@0 52 IIRFilterNode(capacity_type capacity, Node<DataType>& input) : Node<DataType>(capacity), input_(input),
andrewm@0 53 autoCalculate_(false), inputHistory_(0), outputHistory_(0), lastInputIndex_(0) {
andrewm@0 54 }
andrewm@0 55
andrewm@0 56 // Copy constructor
andrewm@0 57 IIRFilterNode(IIRFilterNode<DataType> const& obj) : Node<DataType>(obj), input_(obj.input_), autoCalculate_(obj.autoCalculate_),
andrewm@0 58 aCoefficients_(obj.aCoefficients_), bCoefficients_(obj.bCoefficients_), lastInputIndex_(obj.lastInputIndex_) {
andrewm@0 59 if(obj.inputHistory_ != 0)
andrewm@0 60 inputHistory_ = new boost::circular_buffer<DataType>(*obj.inputHistory_);
andrewm@0 61 else
andrewm@0 62 inputHistory_ = 0;
andrewm@0 63 if(obj.outputHistory_ != 0)
andrewm@0 64 outputHistory_ = new boost::circular_buffer<DataType>(*obj.outputHistory_);
andrewm@0 65 else
andrewm@0 66 outputHistory_ = 0;
andrewm@0 67 if(autoCalculate_) {
andrewm@0 68 // Bring up to date and register for further updates
andrewm@0 69 calculate();
andrewm@0 70 this->registerForTrigger(&input_);
andrewm@0 71 }
andrewm@0 72 }
andrewm@0 73
andrewm@0 74 // ***** Destructor *****
andrewm@0 75 ~IIRFilterNode() {
andrewm@0 76 if(inputHistory_ != 0)
andrewm@0 77 delete inputHistory_;
andrewm@0 78 if(outputHistory_ != 0)
andrewm@0 79 delete outputHistory_;
andrewm@0 80 }
andrewm@0 81
andrewm@0 82 // ***** Modifiers *****
andrewm@0 83 //
andrewm@0 84 // Override this method to clear the input sample buffer
andrewm@0 85
andrewm@0 86 void clear() {
andrewm@0 87 Node<DataType>::clear();
andrewm@0 88 clearInputOutputHistory();
andrewm@0 89 }
andrewm@0 90
andrewm@0 91 // Switch whether calculations happen automatically or only upon request
andrewm@0 92 // If switching on, optionally specify how far back to calculate in bringing
andrewm@0 93 // the filter up to date.
andrewm@0 94 void setAutoCalculate(bool newAutoCalculate, int maximumLookback = -1) {
andrewm@0 95 if(autoCalculate_ && !newAutoCalculate)
andrewm@0 96 this->unregisterForTrigger(&input_);
andrewm@0 97 else if(!autoCalculate_ && newAutoCalculate) {
andrewm@0 98 // Bring the buffer up to date
andrewm@0 99 calculate(maximumLookback);
andrewm@0 100 this->registerForTrigger(&input_);
andrewm@0 101 }
andrewm@0 102 autoCalculate_ = newAutoCalculate;
andrewm@0 103 }
andrewm@0 104
andrewm@0 105 // Set the coefficients of the filter and allocate the proper buffer
andrewm@0 106 // to hold past inputs. Optional last argument specifies whether to
andrewm@0 107 // clear the past sample history or not (defaults to clearing it).
andrewm@0 108 // If filter lengths are different, the buffer is always cleared.
andrewm@0 109 void setCoefficients(std::vector<DataType> const& bCoeffs,
andrewm@0 110 std::vector<DataType> const& aCoeffs,
andrewm@0 111 bool clearBuffer = true) {
andrewm@0 112 if(bCoeffs.empty()) // Can't have an empty feedforward coefficient set
andrewm@0 113 return;
andrewm@0 114 bool shouldClear = clearBuffer;
andrewm@0 115 aCoefficients_ = aCoeffs;
andrewm@0 116 bCoefficients_ = bCoeffs;
andrewm@0 117
andrewm@0 118 if(inputHistory_ == 0) {
andrewm@0 119 inputHistory_ = new boost::circular_buffer<DataType>(bCoeffs.size());
andrewm@0 120 shouldClear = true;
andrewm@0 121 }
andrewm@0 122 else if(bCoeffs.size() != inputHistory_->capacity()) {
andrewm@0 123 inputHistory_->set_capacity(bCoeffs.size());
andrewm@0 124 shouldClear = true;
andrewm@0 125 }
andrewm@0 126
andrewm@0 127 if(outputHistory_ == 0) {
andrewm@0 128 outputHistory_ = new boost::circular_buffer<DataType>(aCoeffs.size());
andrewm@0 129 shouldClear = true;
andrewm@0 130 }
andrewm@0 131 else if(aCoeffs.size() != outputHistory_->capacity()) {
andrewm@0 132 outputHistory_->set_capacity(aCoeffs.size());
andrewm@0 133 shouldClear = true;
andrewm@0 134 }
andrewm@0 135
andrewm@0 136 if(shouldClear)
andrewm@0 137 clearInputOutputHistory();
andrewm@0 138 }
andrewm@0 139
andrewm@0 140 // If not automatically calculating, bring the samples up to date by
andrewm@0 141 // processing any available input that we have not yet seen. Returns
andrewm@0 142 // the most recent output. Optional argument specifies how far back
andrewm@0 143 // to look before starting fresh (if more samples have elapsed since
andrewm@0 144 // last calculation).
andrewm@0 145 typename Node<DataType>::return_value_type calculate(int maximumLookback = -1) {
andrewm@0 146 typename Node<DataType>::size_type index = lastInputIndex_;
andrewm@0 147
andrewm@0 148 if(maximumLookback >= 0 && index < input_.endIndex() - 1 - maximumLookback) {
andrewm@0 149 //std::cout << "IIRFilterNode: clearing history at index " << index << std::endl;
andrewm@0 150 // More samples gone by than we want to calculate... clear input
andrewm@0 151 clearInputOutputHistory();
andrewm@0 152 index = input_.endIndex() - 1 - maximumLookback;
andrewm@0 153 if(index < input_.beginIndex())
andrewm@0 154 index = input_.beginIndex();
andrewm@0 155 }
andrewm@0 156 else if(index < input_.beginIndex()) {
andrewm@0 157 // More samples gone by than are now available... clear input
andrewm@0 158 //std::cout << "IIRFilterNode: clearing history at index " << index << std::endl;
andrewm@0 159 clearInputOutputHistory();
andrewm@0 160 index = input_.beginIndex();
andrewm@0 161 }
andrewm@0 162 while(index < input_.endIndex()) {
andrewm@0 163 processOneSample(input_[index], input_.timestampAt(index));
andrewm@0 164 index++;
andrewm@0 165 }
andrewm@0 166
andrewm@0 167 lastInputIndex_ = index;
andrewm@0 168 if(!this->empty())
andrewm@0 169 return this->latest();
andrewm@0 170 //std::cout << "IIRFilterNode: empty\n";
andrewm@0 171 return missing_value<DataType>::missing();
andrewm@0 172 }
andrewm@0 173
andrewm@0 174 // ***** Evaluator *****
andrewm@0 175 //
andrewm@0 176 // This is called when the input gets a new data point. Accumulate its value and store it in our buffer.
andrewm@0 177 // Storing it will also cause a trigger to be sent to anyone who's listening.
andrewm@0 178
andrewm@0 179 void triggerReceived(TriggerSource* who, timestamp_type timestamp) {
andrewm@0 180 if(who != &input_ || !autoCalculate_)
andrewm@0 181 return;
andrewm@0 182
andrewm@0 183 processOneSample(input_.latest(), timestamp);
andrewm@0 184 }
andrewm@0 185
andrewm@0 186 private:
andrewm@0 187 // ***** Internal Methods *****
andrewm@0 188 // Run the filter once with a new sample. Put the result into the
andrewm@0 189 // end of the buffer.
andrewm@0 190 void processOneSample(DataType const& sample, timestamp_type timestamp) {
andrewm@0 191 if(!bCoefficients_.empty()) {
andrewm@0 192 // Always need at least one feedforward coefficient
andrewm@0 193 DataType result = bCoefficients_[0] * sample;
andrewm@0 194 typename boost::circular_buffer<DataType>::reverse_iterator rit = inputHistory_->rbegin();
andrewm@0 195
andrewm@0 196 // Feedforward part
andrewm@0 197 for(int i = 1; i < bCoefficients_.size() && rit != inputHistory_->rend(); i++) {
andrewm@0 198 result += *rit * bCoefficients_[i];
andrewm@0 199 rit++;
andrewm@0 200 }
andrewm@0 201 // Feedback part
andrewm@0 202 rit = outputHistory_->rbegin();
andrewm@0 203 for(int i = 0; i < aCoefficients_.size() && rit != outputHistory_->rend(); i++) {
andrewm@0 204 result -= *rit * aCoefficients_[i];
andrewm@0 205 rit++;
andrewm@0 206 }
andrewm@0 207
andrewm@0 208 // Update input history and put output in our buffer
andrewm@0 209 inputHistory_->push_back(sample);
andrewm@0 210 outputHistory_->push_back(result);
andrewm@0 211 this->insert(result, timestamp);
andrewm@0 212 }
andrewm@0 213 else {
andrewm@0 214 // Pass through when no coefficients present
andrewm@0 215 this->insert(sample, timestamp);
andrewm@0 216 }
andrewm@0 217 }
andrewm@0 218
andrewm@0 219 // Clear the recent history of input/output data and fill it with zeros
andrewm@0 220 void clearInputOutputHistory() {
andrewm@0 221 if(inputHistory_ != 0) {
andrewm@0 222 inputHistory_->clear();
andrewm@0 223 while(!inputHistory_->full())
andrewm@0 224 inputHistory_->push_back(DataType());
andrewm@0 225 }
andrewm@0 226 if(outputHistory_ != 0) {
andrewm@0 227 outputHistory_->clear();
andrewm@0 228 while(!outputHistory_->full())
andrewm@0 229 outputHistory_->push_back(DataType());
andrewm@0 230 }
andrewm@0 231 }
andrewm@0 232
andrewm@0 233
andrewm@0 234 // ***** Member Variables *****
andrewm@0 235
andrewm@0 236 Node<DataType>& input_;
andrewm@0 237 bool autoCalculate_; // Whether we're automatically calculating new output values
andrewm@0 238
andrewm@0 239 // Variables below are for filter calculation. We need to hold the past input samples
andrewm@0 240 // ourselves because we can't consistently count on enough samples in the source buffer.
andrewm@0 241 // Likewise, we need to hold past output samples, even though we have our own buffer, because
andrewm@0 242 // when we clear the buffer for new calculations we don't want to lose what we've previously
andrewm@0 243 // calculated.
andrewm@0 244 boost::circular_buffer<DataType>* inputHistory_;
andrewm@0 245 boost::circular_buffer<DataType>* outputHistory_;
andrewm@0 246 std::vector<DataType> aCoefficients_, bCoefficients_;
andrewm@0 247 typename Node<DataType>::size_type lastInputIndex_; // Where in the input buffer we had the last sample
andrewm@0 248 };
andrewm@0 249
andrewm@0 250 // ***** Static Filter Design Methods *****
andrewm@0 251
andrewm@0 252 // These methods calculate specific coefficients and store them in the provided
andrewm@0 253 // A and B vectors. These will only work for double/float datatypes or others that
andrewm@0 254 // can be multiplied with them. Details: http://freeverb3.sourceforge.net/iir_filter.shtml
andrewm@0 255
andrewm@0 256 void designFirstOrderLowpass(std::vector<double>& bCoeffs, std::vector<double>& aCoeffs,
andrewm@0 257 double cutoffFrequency, double sampleFrequency);
andrewm@0 258
andrewm@0 259 void designFirstOrderHighpass(std::vector<double>& bCoeffs, std::vector<double>& aCoeffs,
andrewm@0 260 double cutoffFrequency, double sampleFrequency);
andrewm@0 261
andrewm@0 262 void designSecondOrderLowpass(std::vector<double>& bCoeffs, std::vector<double>& aCoeffs,
andrewm@0 263 double cutoffFrequency, double q, double sampleFrequency);
andrewm@0 264
andrewm@0 265 void designSecondOrderHighpass(std::vector<double>& bCoeffs, std::vector<double>& aCoeffs,
andrewm@0 266 double cutoffFrequency, double q, double sampleFrequency);
andrewm@0 267
andrewm@0 268 void designSecondOrderBandpass(std::vector<double>& bCoeffs, std::vector<double>& aCoeffs,
andrewm@0 269 double cutoffFrequency, double q, double sampleFrequency);
andrewm@0 270
andrewm@0 271 #endif