Skip to content

Commit cd341e7

Browse files
Aliyu kamalAliyu kamal
Aliyu kamal
authored and
Aliyu kamal
committed
Implement IIRFilter class with detailed documentation and methods for coefficient setting and sample processing
1 parent 1f368da commit cd341e7

File tree

1 file changed

+49
-51
lines changed

1 file changed

+49
-51
lines changed

audio_filters/iir_filter.py

+49-51
Original file line numberDiff line numberDiff line change
@@ -2,81 +2,79 @@
22

33

44
class IIRFilter:
5-
r"""
6-
N-Order IIR filter
7-
Assumes working with float samples normalized on [-1, 1]
5+
"""
6+
Represents an N-order Infinite Impulse Response (IIR) filter.
7+
8+
This class implements a digital filter that operates on floating-point samples normalized
9+
to the range [-1, 1].
810
9-
---
11+
Attributes:
12+
order (int): The order of the filter.
13+
a_coeffs (list[float]): The coefficients of the denominator polynomial (feedback coefficients).
14+
b_coeffs (list[float]): The coefficients of the numerator polynomial (feedforward coefficients).
15+
input_history (list[float]): History of input samples for processing.
16+
output_history (list[float]): History of output samples for processing.
1017
11-
Implementation details:
12-
Based on the 2nd-order function from
13-
https://en.wikipedia.org/wiki/Digital_biquad_filter,
14-
this generalized N-order function was made.
18+
Note:
19+
This class assumes that the filter operates in real-time and processes one sample at a time.
1520
16-
Using the following transfer function
17-
H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}}
18-
we can rewrite this to
19-
y[n]={\frac{1}{a_{0}}}\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right)
21+
Example:
22+
>>> filt = IIRFilter(2)
23+
>>> filt.set_coefficients([1.0, -1.0, 0.5], [1.0, 0.0, -0.5])
24+
>>> output = filt.process(0.5)
2025
"""
2126

2227
def __init__(self, order: int) -> None:
23-
self.order = order
28+
"""
29+
Initializes the IIRFilter with the specified order.
30+
31+
Args:
32+
order (int): The order of the filter.
2433
25-
# a_{0} ... a_{k}
34+
Note:
35+
The initial coefficients are set to unity (1.0) for both the numerator and denominator.
36+
"""
37+
self.order = order
2638
self.a_coeffs = [1.0] + [0.0] * order
27-
# b_{0} ... b_{k}
2839
self.b_coeffs = [1.0] + [0.0] * order
29-
30-
# x[n-1] ... x[n-k]
31-
self.input_history = [0.0] * self.order
32-
# y[n-1] ... y[n-k]
33-
self.output_history = [0.0] * self.order
40+
self.input_history = [0.0] * order
41+
self.output_history = [0.0] * order
3442

3543
def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None:
3644
"""
37-
Set the coefficients for the IIR filter. These should both be of size order + 1.
38-
a_0 may be left out, and it will use 1.0 as default value.
39-
40-
This method works well with scipy's filter design functions
41-
>>> # Make a 2nd-order 1000Hz butterworth lowpass filter
42-
>>> import scipy.signal
43-
>>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000,
44-
... btype='lowpass',
45-
... fs=48000)
46-
>>> filt = IIRFilter(2)
47-
>>> filt.set_coefficients(a_coeffs, b_coeffs)
48-
"""
49-
if len(a_coeffs) < self.order:
50-
a_coeffs = [1.0, *a_coeffs]
45+
Sets the coefficients for the IIR filter.
5146
52-
if len(a_coeffs) != self.order + 1:
53-
msg = (
54-
f"Expected a_coeffs to have {self.order + 1} elements "
55-
f"for {self.order}-order filter, got {len(a_coeffs)}"
56-
)
57-
raise ValueError(msg)
47+
Args:
48+
a_coeffs (list[float]): The coefficients of the denominator polynomial (feedback coefficients).
49+
b_coeffs (list[float]): The coefficients of the numerator polynomial (feedforward coefficients).
5850
51+
Raises:
52+
ValueError: If the length of `a_coeffs` or `b_coeffs` does not match the order of the filter.
53+
"""
54+
if len(a_coeffs) != self.order + 1:
55+
raise ValueError(f"Expected {self.order + 1} coefficients for `a_coeffs`, got {len(a_coeffs)}")
5956
if len(b_coeffs) != self.order + 1:
60-
msg = (
61-
f"Expected b_coeffs to have {self.order + 1} elements "
62-
f"for {self.order}-order filter, got {len(a_coeffs)}"
63-
)
64-
raise ValueError(msg)
57+
raise ValueError(f"Expected {self.order + 1} coefficients for `b_coeffs`, got {len(b_coeffs)}")
6558

6659
self.a_coeffs = a_coeffs
6760
self.b_coeffs = b_coeffs
6861

6962
def process(self, sample: float) -> float:
7063
"""
71-
Calculate y[n]
64+
Processes a single input sample through the IIR filter.
7265
73-
>>> filt = IIRFilter(2)
74-
>>> filt.process(0)
75-
0.0
66+
Args:
67+
sample (float): The input sample to be filtered.
68+
69+
Returns:
70+
float: The filtered output sample.
71+
72+
Example:
73+
>>> filt = IIRFilter(2)
74+
>>> filt.set_coefficients([1.0, -1.0, 0.5], [1.0, 0.0, -0.5])
75+
>>> output = filt.process(0.5)
7676
"""
7777
result = 0.0
78-
79-
# Start at index 1 and do index 0 at the end.
8078
for i in range(1, self.order + 1):
8179
result += (
8280
self.b_coeffs[i] * self.input_history[i - 1]

0 commit comments

Comments
 (0)