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 +55 -55
- biquad/biquad.py +268 -270
- biquad/highshelf.py +115 -115
- biquad/lowshelf.py +115 -115
- {biquad-0.4.dist-info → biquad-0.5.dist-info}/METADATA +103 -102
- biquad-0.5.dist-info/RECORD +15 -0
- {biquad-0.4.dist-info → biquad-0.5.dist-info}/WHEEL +1 -1
- {biquad-0.4.dist-info → biquad-0.5.dist-info/licenses}/LICENSE +21 -21
- biquad-0.4.dist-info/RECORD +0 -15
- {biquad-0.4.dist-info → biquad-0.5.dist-info}/top_level.txt +0 -0
biquad/__init__.py
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
__version__ = "0.
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
self.
|
|
119
|
-
self.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
x
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
Instantaneous filter
|
|
141
|
-
|
|
142
|
-
Instantaneous filter
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
self.
|
|
165
|
-
|
|
166
|
-
self.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
Option whether to
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|