lasp/src/lasp/filter/biquad.py

250 lines
6.4 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""!
Author: J.A. de Jong - ASCEE V.O.F.
Description: Filter design implementation of common biquad filters that are
often used in parametric equalizers.
Major source is Audio EQ Cookbook:
https://archive.is/20121220231853/http://www.musicdsp.org/
files/Audio-EQ-Cookbook.txt
The definition of the BiQuad filter coefficients as coming out of these
functions defines the filter as:
y[n] = 1/ba[3] * ( ba[0] * x[n] + ba[1] * x[n-1] + ba[2] * x[n-2] +
+ ba[4] * y[n-1] + ba[5] * y[n-2]
)
*Note that all filters are normalized such that ba[3] is by definition equal to
1.0!*
"""
__all__ = ['peaking', 'biquadTF', 'notch', 'lowpass', 'highpass',
'highshelf', 'lowshelf', 'LPcompensator', 'HPcompensator']
from numpy import array, cos, pi, sin, sqrt
from scipy.interpolate import interp1d
from scipy.signal import sosfreqz, bilinear_zpk, zpk2sos
def peaking(fs, f0, Q, gain):
"""
Design of peaking biquad filter
Args:
fs: Sampling frequency [Hz]
f0: Center frequency
Q: Quality factor (~ inverse of bandwidth)
gain: Increase in level at the center frequency [dB]
"""
A = sqrt(10**(gain/20))
omg0 = 2*pi*f0/fs
alpha = sin(omg0)/Q/2
b0 = 1+alpha*A
b1 = -2*cos(omg0)
b2 = 1-alpha*A
a0 = 1 + alpha/A
a1 = -2*cos(omg0)
a2 = 1-alpha/A
return array([b0/a0, b1/a0, b2/a0, a0/a0, a1/a0, a2/a0])
def notch(fs, f0, Q, gain=None):
"""
Notch filter, parameter gain not used
Args:
fs: Sampling frequency [Hz]
f0: Center frequency [Hz]
Q: Quality factor (~ inverse of bandwidth)
"""
omg0 = 2*pi*f0/fs
alpha = sin(omg0)/Q/2
b0 = 1
b1 = -2*cos(omg0)
b2 = 1
a0 = 1 + alpha
a1 = -2*cos(omg0)
a2 = 1 - alpha
return array([b0/a0, b1/a0, b2/a0, a0/a0, a1/a0, a2/a0])
def lowpass(fs, f0, Q, gain=None):
"""
Second order low pass filter, parameter gain not used
Args:
fs: Sampling frequency [Hz]
f0: Cut-off frequency [Hz]
Q: Quality factor (~ inverse of bandwidth)
"""
w0 = 2*pi*f0/fs
alpha = sin(w0)/Q/2
b0 = (1 - cos(w0))/2
b1 = 1 - cos(w0)
b2 = (1 - cos(w0))/2
a0 = 1 + alpha
a1 = -2*cos(w0)
a2 = 1 - alpha
return array([b0/a0, b1/a0, b2/a0, a0/a0, a1/a0, a2/a0])
def highpass(fs, f0, Q, gain=None):
"""
Second order high pass filter, parameter gain not used
Args:
fs: Sampling frequency [Hz]
f0: Cut-on frequency [Hz]
Q: Quality factor (~ inverse of bandwidth)
"""
w0 = 2*pi*f0/fs
alpha = sin(w0)/Q/2
b0 = (1 + cos(w0))/2
b1 = -(1 + cos(w0))
b2 = (1 + cos(w0))/2
a0 = 1 + alpha
a1 = -2*cos(w0)
a2 = 1 - alpha
return array([b0/a0, b1/a0, b2/a0, a0/a0, a1/a0, a2/a0])
def highshelf(fs, f0, Q, gain):
"""
High shelving filter
Args:
fs: Sampling frequency [Hz]
f0: Cut-on frequency [Hz]
Q: Quality factor (~ inverse of bandwidth)
gain: Increase in level w.r.t. "wire" [dB]
"""
w0 = 2*pi*f0/fs
alpha = sin(w0)/Q/2
A = 10**(gain/40)
b0 = A*((A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha)
b1 = -2*A*((A-1) + (A+1)*cos(w0))
b2 = A*((A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha)
a0 = (A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha
a1 = 2*((A-1) - (A+1)*cos(w0))
a2 = (A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha
return array([b0/a0, b1/a0, b2/a0, a0/a0, a1/a0, a2/a0])
def lowshelf(fs, f0, Q, gain):
"""
Low shelving filter
Args:
fs: Sampling frequency [Hz]
f0: Cut-on frequency [Hz]
Q: Quality factor (~ inverse of bandwidth)
gain: Increase in level w.r.t. "wire" [dB]
"""
w0 = 2*pi*f0/fs
alpha = sin(w0)/Q/2
A = 10**(gain/40)
b0 = A*((A+1) - (A-1)*cos(w0) + 2*sqrt(A)*alpha)
b1 = 2*A*((A-1) - (A+1)*cos(w0))
b2 = A*((A+1) - (A-1)*cos(w0) - 2*sqrt(A)*alpha)
a0 = (A+1) + (A-1)*cos(w0) + 2*sqrt(A)*alpha
a1 = -2*((A-1) + (A+1)*cos(w0))
a2 = (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha
return array([b0/a0, b1/a0, b2/a0, a0/a0, a1/a0, a2/a0])
def LPcompensator(fs, f0o, Qo, f0n, Qn):
"""
Shelving type filter that, when multiplied with a second-order low-pass
filter, alters the response of that filter to a different second-order
low-pass filter.
Args:
fs: Sampling frequency [Hz]
f0o: Cut-off frequency of the original filter [Hz]
Qo: Quality factor of the original filter (~inverse of bandwidth)
f0n: Desired cut-off frequency [Hz]
Qn: Desired quality factor(~inverse of bandwidth)
"""
omg0o = 2*pi*f0o
omg0n = 2*pi*f0n
zRe = omg0o/(2*Qo)
zIm = omg0o*sqrt(1-1/(4*Qo**2))
z1 = -zRe + zIm*1j
z2 = -zRe - zIm*1j
pRe = omg0n/(2*Qn)
pIm = omg0n*sqrt(1-1/(4*Qn**2))
p1 = -pRe + pIm*1j
p2 = -pRe - pIm*1j
z= [z1, z2]
p = [p1, p2]
k = (pRe**2 + pIm**2)/(zRe**2 + zIm**2)
zd, pd, kd = bilinear_zpk(z, p, k, fs)
sos = zpk2sos(zd,pd,kd)
return sos[0]
def HPcompensator(fs, f0o, Qo, f0n, Qn):
"""
Shelving type filter that, when multiplied with a second-order high-pass
filter, alters the response of that filter to a different second-order
high-pass filter.
Args:
fs: Sampling frequency [Hz]
f0o: Cut-on frequency of the original filter [Hz]
Qo: Quality factor of the original filter (~inverse of bandwidth)
f0n: Desired cut-on frequency [Hz]
Qn: Desired quality factor(~inverse of bandwidth)
"""
omg0o = 2*pi*f0o
omg0n = 2*pi*f0n
zRe = omg0o/(2*Qo)
zIm = omg0o*sqrt(1-1/(4*Qo**2))
z1 = -zRe + zIm*1j
z2 = -zRe - zIm*1j
pRe = omg0n/(2*Qn)
pIm = omg0n*sqrt(1-1/(4*Qn**2))
p1 = -pRe + pIm*1j
p2 = -pRe - pIm*1j
z= [z1, z2]
p = [p1, p2]
k = 1
zd, pd, kd = bilinear_zpk(z, p, k, fs)
sos = zpk2sos(zd,pd,kd)
return sos[0]
def biquadTF(fs, freq, sos):
"""
Computes the transfer function of the biquad.
Interpolates the frequency response to `freq`
Args:
fs: Sampling frequency [Hz]
freq: Frequency array to compute the
ba: Biquad filter coefficients in common form.
TODO: This code is not yet tested
"""
freq2, h = sosfreqz(sos, worN=freq.size, fs=fs)
interpolator = interp1d(freq2, h, kind='quadratic')
return interpolator(freq)