biquad 0.4__py3-none-any.whl → 0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
biquad/__init__.py CHANGED
@@ -1,55 +1,55 @@
1
- __version__ = "0.4"
2
-
3
-
4
- from .allpass import allpass
5
- from .bandpass import bandpass
6
- from .biquad import biquad
7
- from .highpass import highpass
8
- from .highshelf import highshelf
9
- from .lowpass import lowpass
10
- from .lowshelf import lowshelf
11
- from .notch import notch
12
- from .peak import peak
13
-
14
-
15
- def filter(name, sr, **kwargs):
16
- """
17
- Create a filter instance of the specified filter name.
18
-
19
- Parameters
20
- ----------
21
- sr : int or float
22
- Sample rate in hertz.
23
- f : int or float, optional
24
- Persistent filter frequency parameter in hertz.
25
- q : int or float, optional
26
- Persistent filter quality parameter.
27
- """
28
-
29
- name = str(name).lower()
30
-
31
- if name in ['allpass', 'all', 'ap', 'apf']:
32
- return allpass(sr, **kwargs)
33
-
34
- if name in ['bandpass', 'band', 'bp', 'bpf']:
35
- return bandpass(sr, **kwargs)
36
-
37
- if name in ['highpass', 'high', 'hp', 'hpf']:
38
- return highpass(sr, **kwargs)
39
-
40
- if name in ['highshelf', 'hs', 'hsf']:
41
- return highshelf(sr, **kwargs)
42
-
43
- if name in ['lowpass', 'low', 'lp', 'lpf']:
44
- return lowpass(sr, **kwargs)
45
-
46
- if name in ['lowshelf', 'ls', 'lsf']:
47
- return lowshelf(sr, **kwargs)
48
-
49
- if name in ['notch', 'nf']:
50
- return notch(sr, **kwargs)
51
-
52
- if name in ['peak', 'pf']:
53
- return peak(sr, **kwargs)
54
-
55
- return biquad(sr, **kwargs)
1
+ __version__ = "0.5"
2
+
3
+
4
+ from .allpass import allpass
5
+ from .bandpass import bandpass
6
+ from .biquad import biquad
7
+ from .highpass import highpass
8
+ from .highshelf import highshelf
9
+ from .lowpass import lowpass
10
+ from .lowshelf import lowshelf
11
+ from .notch import notch
12
+ from .peak import peak
13
+
14
+
15
+ def filter(name, sr, **kwargs):
16
+ """
17
+ Create a filter instance of the specified filter name.
18
+
19
+ Parameters
20
+ ----------
21
+ sr : int or float
22
+ Sample rate in hertz.
23
+ f : int or float, optional
24
+ Persistent filter frequency parameter in hertz.
25
+ q : int or float, optional
26
+ Persistent filter quality parameter.
27
+ """
28
+
29
+ name = str(name).lower()
30
+
31
+ if name in ['allpass', 'all', 'ap', 'apf']:
32
+ return allpass(sr, **kwargs)
33
+
34
+ if name in ['bandpass', 'band', 'bp', 'bpf']:
35
+ return bandpass(sr, **kwargs)
36
+
37
+ if name in ['highpass', 'high', 'hp', 'hpf']:
38
+ return highpass(sr, **kwargs)
39
+
40
+ if name in ['highshelf', 'hs', 'hsf']:
41
+ return highshelf(sr, **kwargs)
42
+
43
+ if name in ['lowpass', 'low', 'lp', 'lpf']:
44
+ return lowpass(sr, **kwargs)
45
+
46
+ if name in ['lowshelf', 'ls', 'lsf']:
47
+ return lowshelf(sr, **kwargs)
48
+
49
+ if name in ['notch', 'nf']:
50
+ return notch(sr, **kwargs)
51
+
52
+ if name in ['peak', 'pf']:
53
+ return peak(sr, **kwargs)
54
+
55
+ return biquad(sr, **kwargs)
biquad/biquad.py CHANGED
@@ -1,270 +1,268 @@
1
- """
2
- Copyright (c) 2023 Juergen Hock
3
-
4
- SPDX-License-Identifier: MIT
5
-
6
- Source: https://github.com/jurihock/biquad
7
- """
8
-
9
- import numba
10
- import numpy
11
-
12
-
13
- @numba.jit(nopython=True, fastmath=True)
14
- def __df1__(g, ba, xy, x, y, i):
15
- """
16
- Compute filter output y[i] based on filter input x[i]
17
- as well as specified filter gain g, coeffs ba and delay line xy,
18
- according to the Direct Form 1.
19
- """
20
-
21
- # roll x
22
- xy[0, 2] = xy[0, 1]
23
- xy[0, 1] = xy[0, 0]
24
-
25
- # roll y
26
- xy[1, 2] = xy[1, 1]
27
- xy[1, 1] = xy[1, 0]
28
-
29
- # update x
30
- xy[0, 0] = x[i]
31
-
32
- # compute intermediate results b*x and a*y
33
- bx = ba[0, 0] * xy[0, 0] + ba[0, 1] * xy[0, 1] + ba[0, 2] * xy[0, 2]
34
- ay = ba[1, 1] * xy[1, 1] + ba[1, 2] * xy[1, 2]
35
-
36
- # update y
37
- xy[1, 0] = (bx * g - ay) / ba[1, 0]
38
-
39
- # return y
40
- y[i] = xy[1, 0]
41
-
42
-
43
- def __gain__(x, divisor=20):
44
- """
45
- Convert the specified decibel gain to the linear gain.
46
- """
47
-
48
- if numpy.isscalar(x):
49
-
50
- y = x / divisor
51
- y = 10 ** y
52
-
53
- else:
54
-
55
- y = numpy.atleast_1d(x) / divisor
56
- numpy.power(10, y, out=y)
57
-
58
- return y
59
-
60
-
61
- def __resize__(x, shape):
62
- """
63
- Resize the specified value to the ndarray of desired shape.
64
- """
65
-
66
- if numpy.isscalar(x):
67
-
68
- y = numpy.full(shape, x)
69
-
70
- else:
71
-
72
- x = numpy.atleast_1d(x)
73
- y = numpy.resize(x, shape)
74
-
75
- return y
76
-
77
-
78
- class biquad:
79
- """
80
- Biquad filter base class.
81
- """
82
-
83
- ba = numpy.array([[1, 0, 0], [1, 0, 0]], float)
84
- """
85
- Biquad filter coefficient matrix of shape (2, 3) excl. gain factor:
86
- - ba[0] holds b coefficients
87
- - ba[1] holds a coefficients
88
- """
89
-
90
- xy = numpy.array([[0, 0, 0], [0, 0, 0]], float)
91
- """
92
- Biquad filter delay line matrix of shape (2, 3):
93
- - xy[0] holds input values
94
- - xy[1] holds output values
95
- """
96
-
97
- def __init__(self, sr, f=None, g=None, q=None):
98
- """
99
- Create a new filter instance.
100
-
101
- Parameters
102
- ----------
103
- sr : int or float
104
- Sample rate in hertz.
105
- f : int or float, optional
106
- Persistent filter frequency parameter in hertz.
107
- g : int or float, optional
108
- Persistent filter gain parameter in decibel.
109
- q : int or float, optional
110
- Persistent filter quality parameter.
111
- """
112
-
113
- assert (sr is not None) and (numpy.isscalar(sr) and numpy.isreal(sr))
114
- assert (f is None) or (numpy.isscalar(f) and numpy.isreal(f))
115
- assert (g is None) or (numpy.isscalar(g) and numpy.isreal(g))
116
- assert (q is None) or (numpy.isscalar(q) and numpy.isreal(q))
117
-
118
- self.sr = sr
119
- self.f = (sr / 4) if f is None else f
120
- self.g = __gain__(0 if g is None else g)
121
- self.q = 1 if q is None else q
122
-
123
- # warmup numba
124
- g = self.g
125
- ba = self.ba
126
- xy = self.xy
127
- x = numpy.zeros(1, float)
128
- y = numpy.zeros(x.shape, x.dtype)
129
- __df1__(g, ba, xy, x, y, 0)
130
-
131
- def __call__(self, x, f=None, g=None, q=None):
132
- """
133
- Process single or multiple contiguous signal values at once.
134
-
135
- Parameters
136
- ----------
137
- x : scalar or array like
138
- Filter input data.
139
- f : scalar or array like, optional
140
- Instantaneous filter frequency parameter in hertz.
141
- g : scalar or array like, optional
142
- Instantaneous filter gain parameter in decibel.
143
- q : scalar or array like, optional
144
- Instantaneous filter quality parameter.
145
-
146
- Returns
147
- -------
148
- y : scalar or ndarray
149
- Filter output data of the same shape and dtype as the input x.
150
- """
151
-
152
- scalar = numpy.isscalar(x)
153
-
154
- ba = self.ba
155
- xy = self.xy
156
-
157
- x = numpy.atleast_1d(x)
158
- y = numpy.zeros(x.shape, x.dtype)
159
-
160
- f = __resize__(self.f if f is None else f, x.shape)
161
- g = __resize__(self.g if g is None else __gain__(g), x.shape)
162
- q = __resize__(self.q if q is None else q, x.shape)
163
-
164
- self.__filter__(ba, xy, x, y, g)
165
-
166
- self.f = f[-1]
167
- self.g = g[-1]
168
- self.q = q[-1]
169
-
170
- return y[0] if scalar else y
171
-
172
- @staticmethod
173
- @numba.jit(nopython=True, fastmath=True)
174
- def __filter__(ba, xy, x, y, g):
175
-
176
- for i in range(x.size):
177
-
178
- __df1__(g[i], ba, xy, x, y, i)
179
-
180
- def response(self, *, norm=False, log=False):
181
- """
182
- Returns frequency and phase response of the transfer function given by the ba coefficients.
183
-
184
- Parameters
185
- ----------
186
- norm : bool, optional
187
- Option whether to normalize the output frequency response.
188
- log : bool, optional
189
- Option whether to express the output frequency values logarithmically.
190
-
191
- Returns
192
- -------
193
- w : array
194
- Corresponding frequency values.
195
- h : array
196
- Complex filter response values.
197
-
198
- See also
199
- --------
200
- scipy.signal.freqz
201
- """
202
-
203
- (b, a), sr = self.ba, self.sr
204
-
205
- b *= self.g
206
-
207
- n = int(sr / 2)
208
-
209
- # compute frequencies from 0 to pi or sr/2 but excluding the Nyquist frequency
210
- w = numpy.linspace(0, numpy.pi, n, endpoint=False) \
211
- if not log else \
212
- numpy.logspace(numpy.log10(1), numpy.log10(numpy.pi), n, endpoint=False, base=10)
213
-
214
- # compute the z-domain transfer function
215
- z = numpy.exp(-1j * w)
216
- x = numpy.polynomial.polynomial.polyval(z, a, tensor=False)
217
- y = numpy.polynomial.polynomial.polyval(z, b, tensor=False)
218
- h = y / x
219
-
220
- # normalize frequency amplitudes
221
- h /= len(h) if norm else 1
222
-
223
- # normalize frequency values according to sr
224
- w = (w * sr) / (2 * numpy.pi)
225
-
226
- return w, h
227
-
228
- def plot(self, *, log=False):
229
- """
230
- Creates filter frequency and phase response plot.
231
-
232
- Parameters
233
- ----------
234
- log : bool, optional
235
- Option whether to express frequency values logarithmically.
236
- """
237
-
238
- import matplotlib.pyplot as pyplot
239
-
240
- w, h = self.response()
241
-
242
- with numpy.errstate(divide='ignore', invalid='ignore'):
243
- habs = 20 * numpy.log10(numpy.abs(h))
244
-
245
- harg = numpy.angle(h)
246
-
247
- pyplot.plot(w, habs, color='b', alpha=0.9)
248
- axis1 = pyplot.gca()
249
- axis2 = axis1.twinx()
250
- axis2.plot(w, harg, color='g', alpha=0.9)
251
-
252
- axis1.set_xlabel('Hz')
253
- axis1.set_ylabel('dB', color='b')
254
- axis2.set_ylabel('rad', color='g')
255
-
256
- habsmin = numpy.min(habs[numpy.isfinite(habs)])
257
- habsmax = numpy.max(habs[numpy.isfinite(habs)])
258
-
259
- habsmin = numpy.minimum(habsmin, -100)
260
- habsmax = numpy.maximum(habsmax, +10)
261
-
262
- hargmin = -numpy.pi
263
- hargmax = +numpy.pi
264
-
265
- axis1.set_ylim((habsmin * 1.1, habsmax * 1.1))
266
- axis2.set_ylim((hargmin * 1.1, hargmax * 1.1))
267
-
268
- if log: axis1.set_xscale('log')
269
-
270
- return pyplot
1
+ """
2
+ Copyright (c) 2023 Juergen Hock
3
+
4
+ SPDX-License-Identifier: MIT
5
+
6
+ Source: https://github.com/jurihock/biquad
7
+ """
8
+
9
+ import numba
10
+ import numpy
11
+
12
+
13
+ @numba.jit(nopython=True, fastmath=True)
14
+ def __df1__(g, ba, xy, x, y, i):
15
+ """
16
+ Compute filter output y[i] based on filter input x[i]
17
+ as well as specified filter gain g, coeffs ba and delay line xy,
18
+ according to the Direct Form 1.
19
+ """
20
+
21
+ # roll x
22
+ xy[0, 2] = xy[0, 1]
23
+ xy[0, 1] = xy[0, 0]
24
+
25
+ # roll y
26
+ xy[1, 2] = xy[1, 1]
27
+ xy[1, 1] = xy[1, 0]
28
+
29
+ # update x
30
+ xy[0, 0] = x[i]
31
+
32
+ # compute intermediate results b*x and a*y
33
+ bx = ba[0, 0] * xy[0, 0] + ba[0, 1] * xy[0, 1] + ba[0, 2] * xy[0, 2]
34
+ ay = ba[1, 1] * xy[1, 1] + ba[1, 2] * xy[1, 2]
35
+
36
+ # update y
37
+ xy[1, 0] = (bx * g - ay) / ba[1, 0]
38
+
39
+ # return y
40
+ y[i] = xy[1, 0]
41
+
42
+
43
+ def __gain__(x, divisor=20):
44
+ """
45
+ Convert the specified decibel gain to the linear gain.
46
+ """
47
+
48
+ if numpy.isscalar(x):
49
+
50
+ y = x / divisor
51
+ y = 10 ** y
52
+
53
+ else:
54
+
55
+ y = numpy.atleast_1d(x) / divisor
56
+ numpy.power(10, y, out=y)
57
+
58
+ return y
59
+
60
+
61
+ def __resize__(x, shape):
62
+ """
63
+ Resize the specified value to the ndarray of desired shape.
64
+ """
65
+
66
+ if numpy.isscalar(x):
67
+
68
+ y = numpy.full(shape, x)
69
+
70
+ else:
71
+
72
+ x = numpy.atleast_1d(x)
73
+ y = numpy.resize(x, shape)
74
+
75
+ return y
76
+
77
+
78
+ class biquad:
79
+ """
80
+ Biquad filter base class.
81
+
82
+ Parameters
83
+ ----------
84
+ ba : Biquad filter coefficient matrix of shape (2, 3) excl. gain factor:
85
+ - ba[0] holds b coefficients
86
+ - ba[1] holds a coefficients
87
+ xy : Biquad filter delay line matrix of shape (2, 3):
88
+ - xy[0] holds input values
89
+ - xy[1] holds output values
90
+ """
91
+
92
+ def __init__(self, sr, f=None, g=None, q=None):
93
+ """
94
+ Create a new filter instance.
95
+
96
+ Parameters
97
+ ----------
98
+ sr : int or float
99
+ Sample rate in hertz.
100
+ f : int or float, optional
101
+ Persistent filter frequency parameter in hertz.
102
+ g : int or float, optional
103
+ Persistent filter gain parameter in decibel.
104
+ q : int or float, optional
105
+ Persistent filter quality parameter.
106
+ """
107
+
108
+ assert (sr is not None) and (numpy.isscalar(sr) and numpy.isreal(sr))
109
+ assert (f is None) or (numpy.isscalar(f) and numpy.isreal(f))
110
+ assert (g is None) or (numpy.isscalar(g) and numpy.isreal(g))
111
+ assert (q is None) or (numpy.isscalar(q) and numpy.isreal(q))
112
+
113
+ self.sr = sr
114
+ self.f = (sr / 4) if f is None else f
115
+ self.g = __gain__(0 if g is None else g)
116
+ self.q = 1 if q is None else q
117
+
118
+ self.ba = numpy.array([[1, 0, 0], [1, 0, 0]], float)
119
+ self.xy = numpy.array([[0, 0, 0], [0, 0, 0]], float)
120
+
121
+ # warmup numba
122
+ g = self.g
123
+ ba = self.ba
124
+ xy = self.xy
125
+ x = numpy.zeros(1, float)
126
+ y = numpy.zeros(x.shape, x.dtype)
127
+ __df1__(g, ba, xy, x, y, 0)
128
+
129
+ def __call__(self, x, f=None, g=None, q=None):
130
+ """
131
+ Process single or multiple contiguous signal values at once.
132
+
133
+ Parameters
134
+ ----------
135
+ x : scalar or array like
136
+ Filter input data.
137
+ f : scalar or array like, optional
138
+ Instantaneous filter frequency parameter in hertz.
139
+ g : scalar or array like, optional
140
+ Instantaneous filter gain parameter in decibel.
141
+ q : scalar or array like, optional
142
+ Instantaneous filter quality parameter.
143
+
144
+ Returns
145
+ -------
146
+ y : scalar or ndarray
147
+ Filter output data of the same shape and dtype as the input x.
148
+ """
149
+
150
+ scalar = numpy.isscalar(x)
151
+
152
+ ba = self.ba
153
+ xy = self.xy
154
+
155
+ x = numpy.atleast_1d(x)
156
+ y = numpy.zeros(x.shape, x.dtype)
157
+
158
+ f = __resize__(self.f if f is None else f, x.shape)
159
+ g = __resize__(self.g if g is None else __gain__(g), x.shape)
160
+ q = __resize__(self.q if q is None else q, x.shape)
161
+
162
+ self.__filter__(ba, xy, x, y, g)
163
+
164
+ self.f = f[-1]
165
+ self.g = g[-1]
166
+ self.q = q[-1]
167
+
168
+ return y[0] if scalar else y
169
+
170
+ @staticmethod
171
+ @numba.jit(nopython=True, fastmath=True)
172
+ def __filter__(ba, xy, x, y, g):
173
+
174
+ for i in range(x.size):
175
+
176
+ __df1__(g[i], ba, xy, x, y, i)
177
+
178
+ def response(self, *, norm=False, log=False):
179
+ """
180
+ Returns frequency and phase response of the transfer function given by the ba coefficients.
181
+
182
+ Parameters
183
+ ----------
184
+ norm : bool, optional
185
+ Option whether to normalize the output frequency response.
186
+ log : bool, optional
187
+ Option whether to express the output frequency values logarithmically.
188
+
189
+ Returns
190
+ -------
191
+ w : array
192
+ Corresponding frequency values.
193
+ h : array
194
+ Complex filter response values.
195
+
196
+ See also
197
+ --------
198
+ scipy.signal.freqz
199
+ """
200
+
201
+ (b, a), sr = self.ba, self.sr
202
+
203
+ b *= self.g
204
+
205
+ n = int(sr / 2)
206
+
207
+ # compute frequencies from 0 to pi or sr/2 but excluding the Nyquist frequency
208
+ w = numpy.linspace(0, numpy.pi, n, endpoint=False) \
209
+ if not log else \
210
+ numpy.logspace(numpy.log10(1), numpy.log10(numpy.pi), n, endpoint=False, base=10)
211
+
212
+ # compute the z-domain transfer function
213
+ z = numpy.exp(-1j * w)
214
+ x = numpy.polynomial.polynomial.polyval(z, a, tensor=False)
215
+ y = numpy.polynomial.polynomial.polyval(z, b, tensor=False)
216
+ h = y / x
217
+
218
+ # normalize frequency amplitudes
219
+ h /= len(h) if norm else 1
220
+
221
+ # normalize frequency values according to sr
222
+ w = (w * sr) / (2 * numpy.pi)
223
+
224
+ return w, h
225
+
226
+ def plot(self, *, log=False):
227
+ """
228
+ Creates filter frequency and phase response plot.
229
+
230
+ Parameters
231
+ ----------
232
+ log : bool, optional
233
+ Option whether to express frequency values logarithmically.
234
+ """
235
+
236
+ import matplotlib.pyplot as pyplot
237
+
238
+ w, h = self.response()
239
+
240
+ with numpy.errstate(divide='ignore', invalid='ignore'):
241
+ habs = 20 * numpy.log10(numpy.abs(h))
242
+
243
+ harg = numpy.angle(h)
244
+
245
+ pyplot.plot(w, habs, color='b', alpha=0.9)
246
+ axis1 = pyplot.gca()
247
+ axis2 = axis1.twinx()
248
+ axis2.plot(w, harg, color='g', alpha=0.9)
249
+
250
+ axis1.set_xlabel('Hz')
251
+ axis1.set_ylabel('dB', color='b')
252
+ axis2.set_ylabel('rad', color='g')
253
+
254
+ habsmin = numpy.min(habs[numpy.isfinite(habs)])
255
+ habsmax = numpy.max(habs[numpy.isfinite(habs)])
256
+
257
+ habsmin = numpy.minimum(habsmin, -100)
258
+ habsmax = numpy.maximum(habsmax, +10)
259
+
260
+ hargmin = -numpy.pi
261
+ hargmax = +numpy.pi
262
+
263
+ axis1.set_ylim((habsmin * 1.1, habsmax * 1.1))
264
+ axis2.set_ylim((hargmin * 1.1, hargmax * 1.1))
265
+
266
+ if log: axis1.set_xscale('log')
267
+
268
+ return pyplot