Biquad filter library in C++ template form. On mbed LPC1768, can run about 100K operations per second with double precision float, or about 200K with single precision float.

Dependents:   EMG_Wiesje

Files at this revision

API Documentation at this revision

Comitter:
mhowellaz
Date:
Thu Feb 16 00:07:57 2012 +0000
Parent:
2:9a9b643fcc66
Commit message:

Changed in this revision

biquad.cpp Show annotated file Show diff for this revision Revisions of this file
biquad.h Show annotated file Show diff for this revision Revisions of this file
--- a/biquad.cpp	Wed Feb 15 18:12:50 2012 +0000
+++ b/biquad.cpp	Thu Feb 16 00:07:57 2012 +0000
@@ -1,9 +1,10 @@
 /*
  *  biquad.cpp
  *
- *  Original source material from Robert Bristow Johnson at 
+ *  Original source material from Robert Bristow Johnson at
  *  http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
- *  
+ *  and Nigel Redmon at http://www.earlevel.com/
+ *
  *  Derived from public domain C implementation by Tom St. Denis
  *
  *  Thanks gents!
@@ -11,63 +12,99 @@
  *  C++-ification by Mark J. Howell
  *
  */
- 
-#include "mbed.h" 
- 
+
+#include "mbed.h"
+
 #include <stdlib.h>
 
 #include "biquad.h"
 
-//#define SPEW
+#define SPEW
 
 template <typename T>
-Biquad<T>::Biquad(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate, T bandwidth) {
+Biquad<T>::Biquad(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate, T Q, BIQUAD_FORMULATION_TYPE form) {
 
 #ifdef SPEW
-    printf("Biquad::Biquad(%d, %f, %f, %f, %f\n", type, dbGain, freq, srate, bandwidth);
+    printf("Biquad::Biquad(%d, %f, %f, %f, %f)\n", type, dbGain, freq, srate, Q);
 #endif
 
-    Reset(type, dbGain, freq, srate, bandwidth, true);
+    Reset(type, dbGain, freq, srate, Q, true, form);
 
 }
 
 template <typename T>
-void Biquad<T>::Reset(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate, T bandwidth, bool clearHistory) {
-    
+void Biquad<T>::Reset(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate, T Q, bool clearHistory, BIQUAD_FORMULATION_TYPE _form) {
+
+    form = _form;
+
+#ifdef SPEW
+    printf("Biquad::Reset(%d, %f, %f, %f, %f, %s, %d)\n", type, dbGain, freq, srate, Q, (clearHistory ? "true" : "false"), form);
+#endif
+
+#if 0
+    switch (form) {
+        case DIRECT_FORM_I:
+            CalcCoefficientsDirectI(type, dbGain, freq, srate, Q);
+            break;
+            
+        case TRANSPOSED_DIRECT_FORM_II:
+            CalcCoefficientsTransposedDirectII(type, dbGain, freq, srate,Q);
+            break;
+            
+        default:
+            break;
+    }
+#else
+    CalcCoefficientsDirectI(type, dbGain, freq, srate, Q);
+    CalcCoefficientsTransposedDirectII(type, dbGain, freq, srate, Q);    
+#endif
+
+    if (clearHistory) {
+
+        /* zero initial samples */
+        x1 = x2 = 0;
+        y1 = y2 = 0;
+        z1 = z2 = 0;
+
+    }
+
+}
+
+template <typename T>
+void Biquad<T>::CalcCoefficientsDirectI(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate, T Q) {
+
     T A, omega, sn, cs, alpha, beta;
     T a0 = 0, a1 = 0, a2 = 0, b0 = 0, b1 = 0, b2 = 0;
 
-#ifdef SPEW
-    printf("Biquad::Reset(%d, %f, %f, %f, %f, %s\n", type, dbGain, freq, srate, bandwidth, (clearHistory ? "true" : "false"));
-#endif
-
     /* setup variables */
-    A = pow(10, dbGain /40);
-    omega = 2 * M_PI * freq /srate;
+    A = pow(10, dbGain / 40); 
+    omega = 2 * M_PI * freq / srate;
     sn = sin(omega);
     cs = cos(omega);
-    alpha = sn * sinh(M_LN2 /2 * bandwidth * omega /sn);
+    //alpha = sn * sinh(M_LN2 / 2 * bandwidth * omega / sn); // for BW
+    //alpha = sn / 2 * sqrt( ( A + 1 / A) * (1 / S - 1) + 2) // for S
+    alpha = sn / (2 * Q); // for Q
     beta = sqrt(A + A);
 
     switch (type) {
         case LPF:
-            b0 = (1 - cs) /2;
+            b0 = (1 - cs) / 2;
             b1 = 1 - cs;
-            b2 = (1 - cs) /2;
+            b2 = (1 - cs) / 2;
             a0 = 1 + alpha;
             a1 = -2 * cs;
             a2 = 1 - alpha;
             break;
-            
+
         case HPF:
-            b0 = (1 + cs) /2;
+            b0 = (1 + cs) / 2;
             b1 = -(1 + cs);
-            b2 = (1 + cs) /2;
+            b2 = (1 + cs) / 2;
             a0 = 1 + alpha;
             a1 = -2 * cs;
             a2 = 1 - alpha;
             break;
-            
+
         case BPF:
             b0 = alpha;
             b1 = 0;
@@ -76,7 +113,7 @@
             a1 = -2 * cs;
             a2 = 1 - alpha;
             break;
-            
+
         case NOTCH:
             b0 = 1;
             b1 = -2 * cs;
@@ -85,16 +122,16 @@
             a1 = -2 * cs;
             a2 = 1 - alpha;
             break;
-            
+
         case PEQ:
             b0 = 1 + (alpha * A);
             b1 = -2 * cs;
             b2 = 1 - (alpha * A);
-            a0 = 1 + (alpha /A);
+            a0 = 1 + (alpha / A);
             a1 = -2 * cs;
-            a2 = 1 - (alpha /A);
+            a2 = 1 - (alpha / A);
             break;
-            
+
         case LSH:
             b0 = A * ((A + 1) - (A - 1) * cs + beta * sn);
             b1 = 2 * A * ((A - 1) - (A + 1) * cs);
@@ -103,7 +140,7 @@
             a1 = -2 * ((A - 1) + (A + 1) * cs);
             a2 = (A + 1) + (A - 1) * cs - beta * sn;
             break;
-            
+
         case HSH:
             b0 = A * ((A + 1) + (A - 1) * cs + beta * sn);
             b1 = -2 * A * ((A - 1) + (A + 1) * cs);
@@ -112,45 +149,152 @@
             a1 = 2 * ((A - 1) - (A + 1) * cs);
             a2 = (A + 1) - (A - 1) * cs - beta * sn;
             break;
-            
+
         default:
             break;
     }
 
+#ifdef SPEW
+    printf("calculated coefficients a0 %f, a1 %f, a2 %f, b0 %f, b1 %f, b2 %f\n", a0, a1, a2, b0, b1, b2);
+#endif
+
     /* precompute the coefficients */
-    this->a0 = b0 /a0;
-    this->a1 = b1 /a0;
-    this->a2 = b2 /a0;
-    this->a3 = a1 /a0;
-    this->a4 = a2 /a0;
+    // DIRECT_FORM_I
+    this->a0_I = b0 /a0;
+    this->a1_I = b1 /a0;
+    this->a2_I = b2 /a0;
+    this->a3_I = a1 /a0;
+    this->a4_I = a2 /a0;
+
+}
+
+template <typename T>
+void Biquad<T>::CalcCoefficientsTransposedDirectII(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate,  T Q) {
+
+    T a0 = 0, a1 = 0, a2 = 0, b1 = 0, b2 = 0, norm = 0, V, K;   
+    
+    V = pow(10, fabs(dbGain) / 20);
+    K = tan(M_PI * freq / srate);
+    
+    switch (type) {
+        case LPF:
+            norm = 1 / (1 + K / Q + K * K);
+            a0 = K * K * norm;
+            a1 = 2 * a0;
+            a2 = a0;
+            b1 = 2 * (K * K - 1) * norm;
+            b2 = (1 - K / Q + K * K) * norm;            
+            break;
+
+        case HPF:
+            norm = 1 / (1 + K / Q + K * K);
+            a0 = 1 * norm;
+            a1 = -2 * a0;
+            a2 = a0;
+            b1 = 2 * (K * K - 1) * norm;
+            b2 = (1 - K / Q + K * K) * norm;
+            break;
+
+        case BPF:
+            norm = 1 / (1 + K / Q + K * K);
+            a0 = K / Q * norm;
+            a1 = 0;
+            a2 = -a0;
+            b1 = 2 * (K * K - 1) * norm;
+            b2 = (1 - K / Q + K * K) * norm;
+            break;
 
-    if (clearHistory) {
+        case NOTCH:
+            norm = 1 / (1 + K / Q + K * K);
+            a0 = (1 + K * K) * norm;
+            a1 = 2 * (K * K - 1) * norm;
+            a2 = a0;
+            b1 = a1;
+            b2 = (1 - K / Q + K * K) * norm;
+            break;
 
-        /* zero initial samples */
-        x1 = x2 = 0;
-        y1 = y2 = 0;
-        
+        case PEQ:
+            if (dbGain >= 0) { // boost
+                norm = 1 / (1 + 1 / Q * K + K * K);
+                a0 = (1 + V / Q * K + K * K) * norm;
+                a1 = 2 * (K * K - 1) * norm;
+                a2 = (1 - V / Q * K + K * K) * norm;
+                b1 = a1;
+                b2 = (1 - 1 / Q * K + K * K) * norm;
+            } else { // cut
+                norm = 1 / (1 + V / Q * K + K * K);
+                a0 = (1 + 1 / Q * K + K * K) * norm;
+                a1 = 2 * (K * K - 1) * norm;
+                a2 = (1 - 1 / Q * K + K * K) * norm;
+                b1 = a1;
+                b2 = (1 - V / Q * K + K * K) * norm;
+            }
+            break;
+
+        case LSH:
+            // TBI
+            break;
+
+        case HSH:
+            // TBI
+            break;
+
+        default:
+            break;
     }
-    
+
+#ifdef SPEW
+    printf("calculated coefficients a0 %f, a1 %f, a2 %f, b1 %f, b2 %f\n", a0, a1, a2, b1, b2);
+#endif
+
+    // TRANSPOSED_DIRECT_FORM_II
+    this->a0_II = a0;
+    this->a1_II = a1;
+    this->a2_II = a2;
+    this->b1_II = b1;
+    this->b2_II = b2;
+
 }
 
 template <typename T>
 /* Computes a BiQuad filter on a sample */
 T Biquad<T>::Calculate(T sample) {
 
-    T result;
+    T result = 0;
+
+    switch (form) {    
+        case DIRECT_FORM_I:
+            /* compute result */
+            result = a0_I * sample + a1_I * x1 + a2_I * x2 -
+                     a3_I * y1 - a4_I * y2;
 
-    /* compute result */
-    result = a0 * sample + a1 * x1 + a2 * x2 -
-             a3 * y1 - a4 * y2;
+            /* shift x1 to x2, sample to x1 */
+            x2 = x1;
+            x1 = sample;
+
+            /* shift y1 to y2, result to y1 */
+            y2 = y1;
+            y1 = result;
+            break;
 
-    /* shift x1 to x2, sample to x1 */
-    x2 = x1;
-    x1 = sample;
+        case TRANSPOSED_DIRECT_FORM_II:
+#if 1        
+            result = sample * a0_II + z1;
+            z1 = sample * a1_II + z2 - b1_II * result;
+            z2 = sample * a2_II - b2_II * result;
+#else
+            result = a0_II * sample + a1_II * x1 + a2_II * x2 - b1_II * y1 - b2_II * y2;
+            x2 = x1;
+            x1 = sample;
+            y2 = y1;
+            y1 = result;
+#endif            
+            break;
 
-    /* shift y1 to y2, result to y1 */
-    y2 = y1;
-    y1 = result;
+        default:
+            break;
+
+    }
 
     return result;
 }
--- a/biquad.h	Wed Feb 15 18:12:50 2012 +0000
+++ b/biquad.h	Thu Feb 16 00:07:57 2012 +0000
@@ -1,8 +1,9 @@
 /*
  *  biquad.h
  *
- *  Original source material from Robert Bristow Johnson at 
+ *  Original source material from Robert Bristow-Johnson at 
  *  http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
+ *  and Nigel Redmon at http://www.earlevel.com/ 
  *  
  *  Derived from public domain C implementation by Tom St. Denis
  *
@@ -25,6 +26,12 @@
 #define M_PI        3.14159265358979323846
 #endif
 
+/* formulation types */
+typedef enum {
+    DIRECT_FORM_I = 0,
+    TRANSPOSED_DIRECT_FORM_II
+} BIQUAD_FORMULATION_TYPE;
+
 /* filter types */
 typedef enum {
     LPF, /* low pass filter */
@@ -41,17 +48,29 @@
 
 public:
     Biquad(BIQUAD_FILTER_TYPE type, 
-        T dbGain,  /* gain of filter */
+        T dbGain,  /* peak gain of filter */
         T freq,  /* center frequency */
         T srate,  /* sampling rate */
-        T bandwidth); /* bandwidth in octaves */
+        T Q, /* Q */
+        BIQUAD_FORMULATION_TYPE form = DIRECT_FORM_I);                       
         
-    void Reset(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate, T bandwidth, bool clearHistory = false);
+    void Reset(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate, T Q, bool clearHistory = false, BIQUAD_FORMULATION_TYPE form = DIRECT_FORM_I);
+    
+    void CalcCoefficientsDirectI(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate, T Q);
+    
+    void CalcCoefficientsTransposedDirectII(BIQUAD_FILTER_TYPE type, T dbGain, T freq, T srate, T Q);
     
     T Calculate(T sample);
     
-    T a0, a1, a2, a3, a4;
+    BIQUAD_FORMULATION_TYPE form;    
+    
+    // used for DIRECT_FORM_1
+    T a0_I, a1_I, a2_I, a3_I, a4_I; 
     T x1, x2, y1, y2;
+    
+    // used for TRANSPOSED_DIRECT_FORM_II
+    T a0_II, a1_II, a2_II, b1_II, b2_II;    
+    T z1, z2;        
 
 };