From bc89a046d437735bee5d618516563cb29aafa272 Mon Sep 17 00:00:00 2001 From: Bernd Porr Date: Mon, 15 Apr 2024 13:33:35 +0100 Subject: [PATCH] Added a test for creating a moving average. --- Fir1.h | 366 ++++++++++++++++++++++---------------------- test/CMakeLists.txt | 3 + test/initavg.cpp | 26 ++++ 3 files changed, 212 insertions(+), 183 deletions(-) create mode 100644 test/initavg.cpp diff --git a/Fir1.h b/Fir1.h index 2dabb4c..bc1512d 100644 --- a/Fir1.h +++ b/Fir1.h @@ -1,26 +1,26 @@ /* -License: MIT License (http://www.opensource.org/licenses/mit-license.php) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. + License: MIT License (http://www.opensource.org/licenses/mit-license.php) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. */ -/* (C) 2013-2021 Graeme Hattan & Bernd Porr */ +/* (C) 2013-2024 Graeme Hattan & Bernd Porr */ #ifndef FIR1_H #define FIR1_H @@ -35,190 +35,190 @@ THE SOFTWARE. **/ class Fir1 { public: - /** - * Coefficients as a const double array. Because the array is const - * the number of taps is identical to the length of the array. - * \param _coefficients A const double array with the impulse response. - **/ - template Fir1(const double (&_coefficients)[nTaps]) : - coefficients(new double[nTaps]), - buffer(new double[nTaps]()), - taps(nTaps) { - for(unsigned i=0;i Fir1(const double (&_coefficients)[nTaps]) : + coefficients(new double[nTaps]), + buffer(new double[nTaps]()), + taps(nTaps) { + for(unsigned i=0;i _coefficients) { - initWithVector(_coefficients); - } - - /** - * Coefficients as a (non-constant-) double array where the length needs to be specified. - * \param coefficients Coefficients as double array. - * \param number_of_taps Number of taps (needs to match the number of coefficients - **/ - Fir1(const double *coefficients,const unsigned number_of_taps); - - /** Coefficients as a text file (for example from Python) - * The number of taps is automatically detected - * when the taps are kept zero. - * \param coeffFile Patht to textfile where every line contains one coefficient - * \param number_of_taps Number of taps (0 = autodetect) - **/ - Fir1(const char* coeffFile, unsigned number_of_taps = 0); - - /** - * Inits all coefficients and the buffer to a constant value - * This is useful for adaptive filters where we start with - * zero valued coefficients or moving average filters. - **/ - Fir1(unsigned number_of_taps, double value = 0); - - /** - * Releases the coefficients and buffer. - **/ - ~Fir1(); + } + + /** + * Coefficients as a C++ vector + * \param _coefficients is a Vector of doubles. + **/ + Fir1(std::vector _coefficients) { + initWithVector(_coefficients); + } + + /** + * Coefficients as a (non-constant-) double array where the length needs to be specified. + * \param coefficients Coefficients as double array. + * \param number_of_taps Number of taps (needs to match the number of coefficients + **/ + Fir1(const double *coefficients,const unsigned number_of_taps); + + /** Coefficients as a text file (for example from Python) + * The number of taps is automatically detected + * when the taps are kept zero. + * \param coeffFile Patht to textfile where every line contains one coefficient + * \param number_of_taps Number of taps (0 = autodetect) + **/ + Fir1(const char* coeffFile, unsigned number_of_taps = 0); + + /** + * Inits all coefficients and the buffer to a constant value + * This is useful for adaptive filters where we start with + * zero valued coefficients or moving average filters. + **/ + Fir1(unsigned number_of_taps, double value = 0); + + /** + * Releases the coefficients and buffer. + **/ + ~Fir1(); - /** - * The actual filter function operation: it receives one sample - * and returns one sample. - * \param input The input sample. - **/ - inline double filter(double input) { - const double *coeff = coefficients; - const double *const coeff_end = coefficients + taps; + /** + * The actual filter function operation: it receives one sample + * and returns one sample. + * \param input The input sample. + **/ + inline double filter(double input) { + const double *coeff = coefficients; + const double *const coeff_end = coefficients + taps; - double *buf_val = buffer + offset; + double *buf_val = buffer + offset; - *buf_val = input; - double output_ = 0; + *buf_val = input; + double output_ = 0; - while(buf_val >= buffer) - output_ += *buf_val-- * *coeff++; + while(buf_val >= buffer) + output_ += *buf_val-- * *coeff++; - buf_val = buffer + taps-1; + buf_val = buffer + taps-1; - while(coeff < coeff_end) - output_ += *buf_val-- * *coeff++; + while(coeff < coeff_end) + output_ += *buf_val-- * *coeff++; - if(++offset >= taps) - offset = 0; + if(++offset >= taps) + offset = 0; - return output_; - } - - - /** - * LMS adaptive filter weight update: - * Every filter coefficient is updated with: - * w_k(n+1) = w_k(n) + learning_rate * buffer_k(n) * error(n) - * \param error Is the term error(n), the error which adjusts the FIR conefficients. - **/ - inline void lms_update(double error) { - double *coeff = coefficients; - const double *coeff_end = coefficients + taps; + return output_; + } + + + /** + * LMS adaptive filter weight update: + * Every filter coefficient is updated with: + * w_k(n+1) = w_k(n) + learning_rate * buffer_k(n) * error(n) + * \param error Is the term error(n), the error which adjusts the FIR conefficients. + **/ + inline void lms_update(double error) { + double *coeff = coefficients; + const double *coeff_end = coefficients + taps; - double *buf_val = buffer + offset; + double *buf_val = buffer + offset; - while(buf_val >= buffer) { - *coeff++ += *buf_val-- * error * mu; - } + while(buf_val >= buffer) { + *coeff++ += *buf_val-- * error * mu; + } - buf_val = buffer + taps-1; + buf_val = buffer + taps-1; - while(coeff < coeff_end) { - *coeff++ += *buf_val-- * error * mu; - } - } - - /** - * Setting the learning rate for the adaptive filter. - * \param _mu The learning rate (i.e. rate of the change by the error signal) - **/ - void setLearningRate(double _mu) {mu = _mu;}; - - /** - * Getting the learning rate for the adaptive filter. - **/ - double getLearningRate() {return mu;}; - - /** - * Resets the buffer (but not the coefficients) - **/ - void reset(); - - /** - * Sets all coefficients to zero - **/ - void zeroCoeff(); - - /** - * Copies the current filter coefficients into a provided array. - * Useful after an adaptive filter has been trained to query - * the result of its training. - * \param coeff_data target where coefficients are copied - * \param number_of_taps number of doubles to be copied - * \throws std::out_of_range number_of_taps is less the actual number of taps. - */ - void getCoeff(double* coeff_data, unsigned number_of_taps) const; - - /** - * @brief Externally sets the coefficient array. This is useful when the - * actually running filter is at a different place as where the updating - * filter is employed. - * - * @param coeff_data New coefficients to set. - * @param number_of_taps Number of taps in the coefficient array. If this is - * not equal to the number of taps used in this filter, a runtime error is - * thrown. - */ - void setCoeff(const double *coeff_data, const unsigned number_of_taps); - - /** - * Returns the coefficients as a vector - **/ - std::vector getCoeffVector() const { - return std::vector(coefficients,coefficients+taps); + while(coeff < coeff_end) { + *coeff++ += *buf_val-- * error * mu; } - - /** - * Returns the number of taps. - **/ - unsigned getTaps() {return taps;}; - - /** - * Returns the power of the of the buffer content: - * sum_k buffer[k]^2 - * which is needed to implement a normalised LMS algorithm. - **/ - inline double getTapInputPower() { - double *buf_val = buffer; + } + + /** + * Setting the learning rate for the adaptive filter. + * \param _mu The learning rate (i.e. rate of the change by the error signal) + **/ + void setLearningRate(double _mu) {mu = _mu;}; + + /** + * Getting the learning rate for the adaptive filter. + **/ + double getLearningRate() {return mu;}; + + /** + * Resets the buffer (but not the coefficients) + **/ + void reset(); + + /** + * Sets all coefficients to zero + **/ + void zeroCoeff(); + + /** + * Copies the current filter coefficients into a provided array. + * Useful after an adaptive filter has been trained to query + * the result of its training. + * \param coeff_data target where coefficients are copied + * \param number_of_taps number of doubles to be copied + * \throws std::out_of_range number_of_taps is less the actual number of taps. + */ + void getCoeff(double* coeff_data, unsigned number_of_taps) const; + + /** + * @brief Externally sets the coefficient array. This is useful when the + * actually running filter is at a different place as where the updating + * filter is employed. + * + * @param coeff_data New coefficients to set. + * @param number_of_taps Number of taps in the coefficient array. If this is + * not equal to the number of taps used in this filter, a runtime error is + * thrown. + */ + void setCoeff(const double *coeff_data, const unsigned number_of_taps); + + /** + * Returns the coefficients as a vector + **/ + std::vector getCoeffVector() const { + return std::vector(coefficients,coefficients+taps); + } + + /** + * Returns the number of taps. + **/ + unsigned getTaps() {return taps;}; + + /** + * Returns the power of the of the buffer content: + * sum_k buffer[k]^2 + * which is needed to implement a normalised LMS algorithm. + **/ + inline double getTapInputPower() { + double *buf_val = buffer; - double p = 0; + double p = 0; - for(unsigned i = 0; i < taps; i++) { - p += (*buf_val) * (*buf_val); - buf_val++; - } - - return p; + for(unsigned i = 0; i < taps; i++) { + p += (*buf_val) * (*buf_val); + buf_val++; } + + return p; + } private: - void initWithVector(std::vector _coefficients); + void initWithVector(std::vector _coefficients); - double *coefficients; - double *buffer; - unsigned taps; - unsigned offset = 0; - double mu = 0; + double *coefficients; + double *buffer; + unsigned taps; + unsigned offset = 0; + double mu = 0; }; #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 93810b5..6340b0a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -40,3 +40,6 @@ add_executable (getcoeffvector getcoeffvector.cpp) target_link_libraries(getcoeffvector fir_static) add_test(TestGetCoeffsVector getcoeffvector) +add_executable (initavg initavg.cpp) +target_link_libraries(initavg fir_static) +add_test(TestInitavgs initavg) diff --git a/test/initavg.cpp b/test/initavg.cpp new file mode 100644 index 0000000..40c2da5 --- /dev/null +++ b/test/initavg.cpp @@ -0,0 +1,26 @@ +#include "Fir1.h" +#include "assert_print.h" +#include +#include + +int main (int,char**) +{ + // inits the filter + Fir1 fir(10,0.1); + + // resets the delay line to zero + fir.reset (); + + for(int i=0;i<30;i++) + { + float a=1; + double b = fir.filter(a); + if (i < 10) { + const double a = 0.1*(i+1); + assert_print(fabs(b - a) < 1E-6,"Average wrongly calculated."); + } + if (i > 10) { + assert_print(fabs(b - 1.0) < 1E-6,"Average wrongly calculated."); + } + } +}