andrewm@0: /*
andrewm@0: TouchKeys: multi-touch musical keyboard control software
andrewm@0: Copyright (c) 2013 Andrew McPherson
andrewm@0:
andrewm@0: This program is free software: you can redistribute it and/or modify
andrewm@0: it under the terms of the GNU General Public License as published by
andrewm@0: the Free Software Foundation, either version 3 of the License, or
andrewm@0: (at your option) any later version.
andrewm@0:
andrewm@0: This program is distributed in the hope that it will be useful,
andrewm@0: but WITHOUT ANY WARRANTY; without even the implied warranty of
andrewm@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
andrewm@0: GNU General Public License for more details.
andrewm@0:
andrewm@0: You should have received a copy of the GNU General Public License
andrewm@0: along with this program. If not, see .
andrewm@0:
andrewm@0: =====================================================================
andrewm@0:
andrewm@0: IIRFilter.h: template class handling an Nth-order IIR filter on data
andrewm@0: in a given Node.
andrewm@0: */
andrewm@0:
andrewm@0: #ifndef touchkeys_IIRFilter_h
andrewm@0: #define touchkeys_IIRFilter_h
andrewm@0:
andrewm@0:
andrewm@0: #include
andrewm@0: #include
andrewm@0: #include
andrewm@0: #include "Node.h"
andrewm@0:
andrewm@0: /*
andrewm@0: * IIRFilterNode
andrewm@0: *
andrewm@0: * This template class performs IIR (infinite impulse response) filtering on incoming Node data.
andrewm@0: * It does not take into account timestamps so assumes the data is regularly sampled. The filter
andrewm@0: * coefficients can be specified and changed, and the operation is selectable between always
andrewm@0: * filtering on each new sample or only filtering on request. In the latter case, it will go back
andrewm@0: * and filter from the most recent available sample, assuming the signal starts from 0 if there is
andrewm@0: * any break in data between what was already calculated and what input data is now available.
andrewm@0: */
andrewm@0:
andrewm@0: template
andrewm@0: class IIRFilterNode : public Node {
andrewm@0: public:
andrewm@0: typedef typename Node::capacity_type capacity_type;
andrewm@0: //typedef typename Node::size_type size_type;
andrewm@0:
andrewm@0: // ***** Constructors *****
andrewm@0:
andrewm@0: IIRFilterNode(capacity_type capacity, Node& input) : Node(capacity), input_(input),
andrewm@0: autoCalculate_(false), inputHistory_(0), outputHistory_(0), lastInputIndex_(0) {
andrewm@0: }
andrewm@0:
andrewm@0: // Copy constructor
andrewm@0: IIRFilterNode(IIRFilterNode const& obj) : Node(obj), input_(obj.input_), autoCalculate_(obj.autoCalculate_),
andrewm@0: aCoefficients_(obj.aCoefficients_), bCoefficients_(obj.bCoefficients_), lastInputIndex_(obj.lastInputIndex_) {
andrewm@0: if(obj.inputHistory_ != 0)
andrewm@0: inputHistory_ = new boost::circular_buffer(*obj.inputHistory_);
andrewm@0: else
andrewm@0: inputHistory_ = 0;
andrewm@0: if(obj.outputHistory_ != 0)
andrewm@0: outputHistory_ = new boost::circular_buffer(*obj.outputHistory_);
andrewm@0: else
andrewm@0: outputHistory_ = 0;
andrewm@0: if(autoCalculate_) {
andrewm@0: // Bring up to date and register for further updates
andrewm@0: calculate();
andrewm@0: this->registerForTrigger(&input_);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // ***** Destructor *****
andrewm@0: ~IIRFilterNode() {
andrewm@0: if(inputHistory_ != 0)
andrewm@0: delete inputHistory_;
andrewm@0: if(outputHistory_ != 0)
andrewm@0: delete outputHistory_;
andrewm@0: }
andrewm@0:
andrewm@0: // ***** Modifiers *****
andrewm@0: //
andrewm@0: // Override this method to clear the input sample buffer
andrewm@0:
andrewm@0: void clear() {
andrewm@0: Node::clear();
andrewm@0: clearInputOutputHistory();
andrewm@0: }
andrewm@0:
andrewm@0: // Switch whether calculations happen automatically or only upon request
andrewm@0: // If switching on, optionally specify how far back to calculate in bringing
andrewm@0: // the filter up to date.
andrewm@0: void setAutoCalculate(bool newAutoCalculate, int maximumLookback = -1) {
andrewm@0: if(autoCalculate_ && !newAutoCalculate)
andrewm@0: this->unregisterForTrigger(&input_);
andrewm@0: else if(!autoCalculate_ && newAutoCalculate) {
andrewm@0: // Bring the buffer up to date
andrewm@0: calculate(maximumLookback);
andrewm@0: this->registerForTrigger(&input_);
andrewm@0: }
andrewm@0: autoCalculate_ = newAutoCalculate;
andrewm@0: }
andrewm@0:
andrewm@0: // Set the coefficients of the filter and allocate the proper buffer
andrewm@0: // to hold past inputs. Optional last argument specifies whether to
andrewm@0: // clear the past sample history or not (defaults to clearing it).
andrewm@0: // If filter lengths are different, the buffer is always cleared.
andrewm@0: void setCoefficients(std::vector const& bCoeffs,
andrewm@0: std::vector const& aCoeffs,
andrewm@0: bool clearBuffer = true) {
andrewm@0: if(bCoeffs.empty()) // Can't have an empty feedforward coefficient set
andrewm@0: return;
andrewm@0: bool shouldClear = clearBuffer;
andrewm@0: aCoefficients_ = aCoeffs;
andrewm@0: bCoefficients_ = bCoeffs;
andrewm@0:
andrewm@0: if(inputHistory_ == 0) {
andrewm@0: inputHistory_ = new boost::circular_buffer(bCoeffs.size());
andrewm@0: shouldClear = true;
andrewm@0: }
andrewm@0: else if(bCoeffs.size() != inputHistory_->capacity()) {
andrewm@0: inputHistory_->set_capacity(bCoeffs.size());
andrewm@0: shouldClear = true;
andrewm@0: }
andrewm@0:
andrewm@0: if(outputHistory_ == 0) {
andrewm@0: outputHistory_ = new boost::circular_buffer(aCoeffs.size());
andrewm@0: shouldClear = true;
andrewm@0: }
andrewm@0: else if(aCoeffs.size() != outputHistory_->capacity()) {
andrewm@0: outputHistory_->set_capacity(aCoeffs.size());
andrewm@0: shouldClear = true;
andrewm@0: }
andrewm@0:
andrewm@0: if(shouldClear)
andrewm@0: clearInputOutputHistory();
andrewm@0: }
andrewm@0:
andrewm@0: // If not automatically calculating, bring the samples up to date by
andrewm@0: // processing any available input that we have not yet seen. Returns
andrewm@0: // the most recent output. Optional argument specifies how far back
andrewm@0: // to look before starting fresh (if more samples have elapsed since
andrewm@0: // last calculation).
andrewm@0: typename Node::return_value_type calculate(int maximumLookback = -1) {
andrewm@0: typename Node::size_type index = lastInputIndex_;
andrewm@0:
andrewm@0: if(maximumLookback >= 0 && index < input_.endIndex() - 1 - maximumLookback) {
andrewm@0: //std::cout << "IIRFilterNode: clearing history at index " << index << std::endl;
andrewm@0: // More samples gone by than we want to calculate... clear input
andrewm@0: clearInputOutputHistory();
andrewm@0: index = input_.endIndex() - 1 - maximumLookback;
andrewm@0: if(index < input_.beginIndex())
andrewm@0: index = input_.beginIndex();
andrewm@0: }
andrewm@0: else if(index < input_.beginIndex()) {
andrewm@0: // More samples gone by than are now available... clear input
andrewm@0: //std::cout << "IIRFilterNode: clearing history at index " << index << std::endl;
andrewm@0: clearInputOutputHistory();
andrewm@0: index = input_.beginIndex();
andrewm@0: }
andrewm@0: while(index < input_.endIndex()) {
andrewm@0: processOneSample(input_[index], input_.timestampAt(index));
andrewm@0: index++;
andrewm@0: }
andrewm@0:
andrewm@0: lastInputIndex_ = index;
andrewm@0: if(!this->empty())
andrewm@0: return this->latest();
andrewm@0: //std::cout << "IIRFilterNode: empty\n";
andrewm@0: return missing_value::missing();
andrewm@0: }
andrewm@0:
andrewm@0: // ***** Evaluator *****
andrewm@0: //
andrewm@0: // This is called when the input gets a new data point. Accumulate its value and store it in our buffer.
andrewm@0: // Storing it will also cause a trigger to be sent to anyone who's listening.
andrewm@0:
andrewm@0: void triggerReceived(TriggerSource* who, timestamp_type timestamp) {
andrewm@0: if(who != &input_ || !autoCalculate_)
andrewm@0: return;
andrewm@0:
andrewm@0: processOneSample(input_.latest(), timestamp);
andrewm@0: }
andrewm@0:
andrewm@0: private:
andrewm@0: // ***** Internal Methods *****
andrewm@0: // Run the filter once with a new sample. Put the result into the
andrewm@0: // end of the buffer.
andrewm@0: void processOneSample(DataType const& sample, timestamp_type timestamp) {
andrewm@0: if(!bCoefficients_.empty()) {
andrewm@0: // Always need at least one feedforward coefficient
andrewm@0: DataType result = bCoefficients_[0] * sample;
andrewm@0: typename boost::circular_buffer::reverse_iterator rit = inputHistory_->rbegin();
andrewm@0:
andrewm@0: // Feedforward part
andrewm@0: for(int i = 1; i < bCoefficients_.size() && rit != inputHistory_->rend(); i++) {
andrewm@0: result += *rit * bCoefficients_[i];
andrewm@0: rit++;
andrewm@0: }
andrewm@0: // Feedback part
andrewm@0: rit = outputHistory_->rbegin();
andrewm@0: for(int i = 0; i < aCoefficients_.size() && rit != outputHistory_->rend(); i++) {
andrewm@0: result -= *rit * aCoefficients_[i];
andrewm@0: rit++;
andrewm@0: }
andrewm@0:
andrewm@0: // Update input history and put output in our buffer
andrewm@0: inputHistory_->push_back(sample);
andrewm@0: outputHistory_->push_back(result);
andrewm@0: this->insert(result, timestamp);
andrewm@0: }
andrewm@0: else {
andrewm@0: // Pass through when no coefficients present
andrewm@0: this->insert(sample, timestamp);
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0: // Clear the recent history of input/output data and fill it with zeros
andrewm@0: void clearInputOutputHistory() {
andrewm@0: if(inputHistory_ != 0) {
andrewm@0: inputHistory_->clear();
andrewm@0: while(!inputHistory_->full())
andrewm@0: inputHistory_->push_back(DataType());
andrewm@0: }
andrewm@0: if(outputHistory_ != 0) {
andrewm@0: outputHistory_->clear();
andrewm@0: while(!outputHistory_->full())
andrewm@0: outputHistory_->push_back(DataType());
andrewm@0: }
andrewm@0: }
andrewm@0:
andrewm@0:
andrewm@0: // ***** Member Variables *****
andrewm@0:
andrewm@0: Node& input_;
andrewm@0: bool autoCalculate_; // Whether we're automatically calculating new output values
andrewm@0:
andrewm@0: // Variables below are for filter calculation. We need to hold the past input samples
andrewm@0: // ourselves because we can't consistently count on enough samples in the source buffer.
andrewm@0: // Likewise, we need to hold past output samples, even though we have our own buffer, because
andrewm@0: // when we clear the buffer for new calculations we don't want to lose what we've previously
andrewm@0: // calculated.
andrewm@0: boost::circular_buffer* inputHistory_;
andrewm@0: boost::circular_buffer* outputHistory_;
andrewm@0: std::vector aCoefficients_, bCoefficients_;
andrewm@0: typename Node::size_type lastInputIndex_; // Where in the input buffer we had the last sample
andrewm@0: };
andrewm@0:
andrewm@0: // ***** Static Filter Design Methods *****
andrewm@0:
andrewm@0: // These methods calculate specific coefficients and store them in the provided
andrewm@0: // A and B vectors. These will only work for double/float datatypes or others that
andrewm@0: // can be multiplied with them. Details: http://freeverb3.sourceforge.net/iir_filter.shtml
andrewm@0:
andrewm@0: void designFirstOrderLowpass(std::vector& bCoeffs, std::vector& aCoeffs,
andrewm@0: double cutoffFrequency, double sampleFrequency);
andrewm@0:
andrewm@0: void designFirstOrderHighpass(std::vector& bCoeffs, std::vector& aCoeffs,
andrewm@0: double cutoffFrequency, double sampleFrequency);
andrewm@0:
andrewm@0: void designSecondOrderLowpass(std::vector& bCoeffs, std::vector& aCoeffs,
andrewm@0: double cutoffFrequency, double q, double sampleFrequency);
andrewm@0:
andrewm@0: void designSecondOrderHighpass(std::vector& bCoeffs, std::vector& aCoeffs,
andrewm@0: double cutoffFrequency, double q, double sampleFrequency);
andrewm@0:
andrewm@0: void designSecondOrderBandpass(std::vector& bCoeffs, std::vector& aCoeffs,
andrewm@0: double cutoffFrequency, double q, double sampleFrequency);
andrewm@0:
andrewm@0: #endif