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
|