#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (C) 2017, Paul Lutus *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation; either version 2 of the License, or *
# * (at your option) any later version. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU General Public License for more details. *
# * *
# * You should have received a copy of the GNU General Public License *
# * along with this program; if not, write to the *
# * Free Software Foundation, Inc., *
# * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
# ***************************************************************************
import math
class Biquad:
# pretend enumeration
LOWPASS, HIGHPASS, BANDPASS, PEAK, NOTCH, LOWSHELF, HIGHSHELF = range(7)
def __init__(self, typ, freq, srate, Q, dbGain=0):
types = {
Biquad.LOWPASS : Biquad.lowpass,
Biquad.HIGHPASS : Biquad.highpass,
Biquad.BANDPASS : Biquad.bandpass,
Biquad.PEAK : Biquad.peak,
Biquad.NOTCH : Biquad.notch,
Biquad.LOWSHELF : Biquad.lowshelf,
Biquad.HIGHSHELF : Biquad.highshelf
}
assert typ in types
self.typ = typ
self.freq = float(freq)
self.srate = float(srate)
self.Q = float(Q)
self.dbGain = float(dbGain)
self.a0 = self.a1 = self.a2 = 0
self.b0 = self.b1 = self.b2 = 0
self.x1 = self.x2 = 0
self.y1 = self.y2 = 0
# only used for peaking and shelving filter types
A = math.pow(10, dbGain / 40)
omega = 2 * math.pi * self.freq / self.srate
sn = math.sin(omega)
cs = math.cos(omega)
alpha = sn / (2*Q)
beta = math.sqrt(A + A)
types[typ](self,A, omega, sn, cs, alpha, beta)
# prescale constants
self.b0 /= self.a0
self.b1 /= self.a0
self.b2 /= self.a0
self.a1 /= self.a0
self.a2 /= self.a0
def lowpass(self,A, omega, sn, cs, alpha, beta):
self.b0 = (1 - cs) /2
self.b1 = 1 - cs
self.b2 = (1 - cs) /2
self.a0 = 1 + alpha
self.a1 = -2 * cs
self.a2 = 1 - alpha
def highpass(self, A, omega, sn, cs, alpha, beta):
self.b0 = (1 + cs) /2
self.b1 = -(1 + cs)
self.b2 = (1 + cs) /2
self.a0 = 1 + alpha
self.a1 = -2 * cs
self.a2 = 1 - alpha
def bandpass(self, A, omega, sn, cs, alpha, beta):
self.b0 = alpha
self.b1 = 0
self.b2 = -alpha
self.a0 = 1 + alpha
self.a1 = -2 * cs
self.a2 = 1 - alpha
def notch(self, A, omega, sn, cs, alpha, beta):
self.b0 = 1
self.b1 = -2 * cs
self.b2 = 1
self.a0 = 1 + alpha
self.a1 = -2 * cs
self.a2 = 1 - alpha
def peak(self, A, omega, sn, cs, alpha, beta):
self.b0 = 1 + (alpha * A)
self.b1 = -2 * cs
self.b2 = 1 - (alpha * A)
self.a0 = 1 + (alpha /A)
self.a1 = -2 * cs
self.a2 = 1 - (alpha /A)
def lowshelf(self, A, omega, sn, cs, alpha, beta):
self.b0 = A * ((A + 1) - (A - 1) * cs + beta * sn)
self.b1 = 2 * A * ((A - 1) - (A + 1) * cs)
self.b2 = A * ((A + 1) - (A - 1) * cs - beta * sn)
self.a0 = (A + 1) + (A - 1) * cs + beta * sn
self.a1 = -2 * ((A - 1) + (A + 1) * cs)
self.a2 = (A + 1) + (A - 1) * cs - beta * sn
def highshelf(self, A, omega, sn, cs, alpha, beta):
self.b0 = A * ((A + 1) + (A - 1) * cs + beta * sn)
self.b1 = -2 * A * ((A - 1) + (A + 1) * cs)
self.b2 = A * ((A + 1) + (A - 1) * cs - beta * sn)
self.a0 = (A + 1) - (A - 1) * cs + beta * sn
self.a1 = 2 * ((A - 1) - (A + 1) * cs)
self.a2 = (A + 1) - (A - 1) * cs - beta * sn
# perform filtering function
def __call__(self, x):
y = self.b0 * x + self.b1 * self.x1 + self.b2 * self.x2 - self.a1 * self.y1 - self.a2 * self.y2
self.x2 = self.x1
self.x1 = x
self.y2 = self.y1
self.y1 = y
return y
# provide a static result for a given frequency f
def result(self, f):
phi = (math.sin(math.pi * f * 2/(2*self.srate)))**2
r =((self.b0+self.b1+self.b2)**2 - \
4*(self.b0*self.b1 + 4*self.b0*self.b2 + \
self.b1*self.b2)*phi + 16*self.b0*self.b2*phi*phi) / \
((1+self.a1+self.a2)**2 - 4*(self.a1 + 4*self.a2 + \
self.a1*self.a2)*phi + 16*self.a2*phi*phi)
if(r < 0):
r = 0
return r**(.5)
# provide a static log result for a given frequency f
def log_result(self, f):
try:
r = 20 * math.log10(self.result(f))
except:
r = -200
return r
# return computed constants
def constants(self):
return self.a1, self.a2, self.b0, self.b1, self.b2
def __str__(self):
return "Type:%d,Freq:%.1f,Rate:%.1f,Q:%.1f,Gain:%.1f" % (self.typ,self.freq,self.srate,self.Q,self.dbGain)
package biquaddesigner;
/**
*
* @author lutusp
*/
// http://en.wikipedia.org/wiki/Digital_biquad_filter
final public class BiQuadraticFilter {
final static int LOWPASS = 0;
final static int HIGHPASS = 1;
final static int BANDPASS = 2;
final static int PEAK = 3;
final static int NOTCH = 4;
final static int LOWSHELF = 5;
final static int HIGHSHELF = 6;
double a0, a1, a2, b0, b1, b2;
double x1, x2, y, y1, y2;
double gain_abs;
int type;
double center_freq, sample_rate, Q, gainDB;
public BiQuadraticFilter() {
}
public BiQuadraticFilter(int type, double center_freq, double sample_rate, double Q, double gainDB) {
configure(type, center_freq, sample_rate, Q, gainDB);
}
// constructor without gain setting
public BiQuadraticFilter(int type, double center_freq, double sample_rate, double Q) {
configure(type, center_freq, sample_rate, Q, 0);
}
public void reset() {
x1 = x2 = y1 = y2 = 0;
}
public double frequency() {
return center_freq;
}
public void configure(int type, double center_freq, double sample_rate, double Q, double gainDB) {
reset();
Q = (Q == 0) ? 1e-9 : Q;
this.type = type;
this.sample_rate = sample_rate;
this.Q = Q;
this.gainDB = gainDB;
reconfigure(center_freq);
}
public void configure(int type, double center_freq, double sample_rate, double Q) {
configure(type, center_freq, sample_rate, Q, 0);
}
// allow parameter change while running
public void reconfigure(double cf) {
center_freq = cf;
// only used for peaking and shelving filters
gain_abs = Math.pow(10, gainDB / 40);
double omega = 2 * Math.PI * cf / sample_rate;
double sn = Math.sin(omega);
double cs = Math.cos(omega);
double alpha = sn / (2 * Q);
double beta = Math.sqrt(gain_abs + gain_abs);
switch (type) {
case BANDPASS:
b0 = alpha;
b1 = 0;
b2 = -alpha;
a0 = 1 + alpha;
a1 = -2 * cs;
a2 = 1 - alpha;
break;
case LOWPASS:
b0 = (1 - cs) / 2;
b1 = 1 - cs;
b2 = (1 - cs) / 2;
a0 = 1 + alpha;
a1 = -2 * cs;
a2 = 1 - alpha;
break;
case HIGHPASS:
b0 = (1 + cs) / 2;
b1 = -(1 + cs);
b2 = (1 + cs) / 2;
a0 = 1 + alpha;
a1 = -2 * cs;
a2 = 1 - alpha;
break;
case NOTCH:
b0 = 1;
b1 = -2 * cs;
b2 = 1;
a0 = 1 + alpha;
a1 = -2 * cs;
a2 = 1 - alpha;
break;
case PEAK:
b0 = 1 + (alpha * gain_abs);
b1 = -2 * cs;
b2 = 1 - (alpha * gain_abs);
a0 = 1 + (alpha / gain_abs);
a1 = -2 * cs;
a2 = 1 - (alpha / gain_abs);
break;
case LOWSHELF:
b0 = gain_abs * ((gain_abs + 1) - (gain_abs - 1) * cs + beta * sn);
b1 = 2 * gain_abs * ((gain_abs - 1) - (gain_abs + 1) * cs);
b2 = gain_abs * ((gain_abs + 1) - (gain_abs - 1) * cs - beta * sn);
a0 = (gain_abs + 1) + (gain_abs - 1) * cs + beta * sn;
a1 = -2 * ((gain_abs - 1) + (gain_abs + 1) * cs);
a2 = (gain_abs + 1) + (gain_abs - 1) * cs - beta * sn;
break;
case HIGHSHELF:
b0 = gain_abs * ((gain_abs + 1) + (gain_abs - 1) * cs + beta * sn);
b1 = -2 * gain_abs * ((gain_abs - 1) + (gain_abs + 1) * cs);
b2 = gain_abs * ((gain_abs + 1) + (gain_abs - 1) * cs - beta * sn);
a0 = (gain_abs + 1) - (gain_abs - 1) * cs + beta * sn;
a1 = 2 * ((gain_abs - 1) - (gain_abs + 1) * cs);
a2 = (gain_abs + 1) - (gain_abs - 1) * cs - beta * sn;
break;
}
// prescale flter constants
b0 /= a0;
b1 /= a0;
b2 /= a0;
a1 /= a0;
a2 /= a0;
}
// provide a static amplitude result for testing
public double result(double f) {
double phi = Math.pow((Math.sin(2.0 * Math.PI * f / (2.0 * sample_rate))), 2.0);
double r = (Math.pow(b0 + b1 + b2, 2.0) - 4.0 * (b0 * b1 + 4.0 * b0 * b2 + b1 * b2) * phi + 16.0 * b0 * b2 * phi * phi) / (Math.pow(1.0 + a1 + a2, 2.0) - 4.0 * (a1 + 4.0 * a2 + a1 * a2) * phi + 16.0 * a2 * phi * phi);
if(r < 0) {
r = 0;
}
return Math.sqrt(r);
}
// provide a static decibel result for testing
public double log_result(double f) {
double r;
try {
r = 20 * Math.log10(result(f));
} catch (Exception e) {
r = -100;
}
if(Double.isInfinite(r) || Double.isNaN(r)) {
r = -100;
}
return r;
}
// return the constant set for this filter
public double[] constants() {
return new double[]{a1, a2, b0, b1, b2};
}
// perform one filtering step
public double filter(double x) {
y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
x2 = x1;
x1 = x;
y2 = y1;
y1 = y;
return (y);
}
}