biquad 0.3__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/allpass.py +27 -21
- biquad/bandpass.py +33 -27
- biquad/biquad.py +117 -35
- biquad/highpass.py +24 -18
- biquad/highshelf.py +115 -113
- biquad/lowpass.py +24 -18
- biquad/lowshelf.py +115 -113
- biquad/notch.py +25 -19
- biquad/peak.py +29 -29
- {biquad-0.3.dist-info → biquad-0.5.dist-info}/METADATA +103 -88
- biquad-0.5.dist-info/RECORD +15 -0
- {biquad-0.3.dist-info → biquad-0.5.dist-info}/WHEEL +1 -1
- {biquad-0.3.dist-info → biquad-0.5.dist-info/licenses}/LICENSE +21 -21
- biquad/plot.py +0 -96
- biquad-0.3.dist-info/RECORD +0 -16
- {biquad-0.3.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/allpass.py
CHANGED
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT
|
|
|
6
6
|
Source: https://github.com/jurihock/biquad
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from .biquad import biquad, __df1__
|
|
9
|
+
from .biquad import biquad, __df1__, __gain__, __resize__
|
|
10
10
|
|
|
11
11
|
import numba
|
|
12
12
|
import numpy
|
|
@@ -17,7 +17,7 @@ class allpass(biquad):
|
|
|
17
17
|
Allpass filter (APF).
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
def __init__(self, sr,
|
|
20
|
+
def __init__(self, sr, f=None, g=0, q=1):
|
|
21
21
|
"""
|
|
22
22
|
Create a new filter instance.
|
|
23
23
|
|
|
@@ -27,15 +27,17 @@ class allpass(biquad):
|
|
|
27
27
|
Sample rate in hertz.
|
|
28
28
|
f : int or float, optional
|
|
29
29
|
Persistent filter frequency parameter in hertz.
|
|
30
|
+
g : int or float, optional
|
|
31
|
+
Persistent filter gain parameter in decibel.
|
|
30
32
|
q : int or float, optional
|
|
31
33
|
Persistent filter quality parameter.
|
|
32
34
|
"""
|
|
33
35
|
|
|
34
|
-
super().__init__(sr=sr, f=f, q=q)
|
|
36
|
+
super().__init__(sr=sr, f=f, g=g, q=q)
|
|
35
37
|
|
|
36
|
-
self.__call__(0
|
|
38
|
+
self.__call__(0) # warmup numba
|
|
37
39
|
|
|
38
|
-
def __call__(self, x, f=None, q=None):
|
|
40
|
+
def __call__(self, x, f=None, g=None, q=None):
|
|
39
41
|
"""
|
|
40
42
|
Process single or multiple contiguous signal values at once.
|
|
41
43
|
|
|
@@ -45,6 +47,8 @@ class allpass(biquad):
|
|
|
45
47
|
Filter input data.
|
|
46
48
|
f : scalar or array like, optional
|
|
47
49
|
Instantaneous filter frequency parameter in hertz.
|
|
50
|
+
g : scalar or array like, optional
|
|
51
|
+
Instantaneous filter gain parameter in decibel.
|
|
48
52
|
q : scalar or array like, optional
|
|
49
53
|
Instantaneous filter quality parameter.
|
|
50
54
|
|
|
@@ -62,21 +66,23 @@ class allpass(biquad):
|
|
|
62
66
|
x = numpy.atleast_1d(x)
|
|
63
67
|
y = numpy.zeros(x.shape, x.dtype)
|
|
64
68
|
|
|
65
|
-
f =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
f = numpy.resize(f, x.shape)
|
|
69
|
-
q = numpy.resize(q, x.shape)
|
|
69
|
+
f = __resize__(self.f if f is None else f, x.shape)
|
|
70
|
+
g = __resize__(self.g if g is None else __gain__(g), x.shape)
|
|
71
|
+
q = __resize__(self.q if q is None else q, x.shape)
|
|
70
72
|
|
|
71
73
|
sr = self.sr
|
|
72
74
|
|
|
73
|
-
self.__filter__(ba, xy, x, y, f, q, sr)
|
|
75
|
+
self.__filter__(ba, xy, x, y, f, g, q, sr)
|
|
76
|
+
|
|
77
|
+
self.f = f[-1]
|
|
78
|
+
self.g = g[-1]
|
|
79
|
+
self.q = q[-1]
|
|
74
80
|
|
|
75
81
|
return y[0] if scalar else y
|
|
76
82
|
|
|
77
83
|
@staticmethod
|
|
78
84
|
@numba.jit(nopython=True, fastmath=True)
|
|
79
|
-
def __filter__(ba, xy, x, y, f, q, sr):
|
|
85
|
+
def __filter__(ba, xy, x, y, f, g, q, sr):
|
|
80
86
|
|
|
81
87
|
rs = 2 * numpy.pi / sr
|
|
82
88
|
|
|
@@ -87,18 +93,18 @@ class allpass(biquad):
|
|
|
87
93
|
cosw = numpy.cos(w)
|
|
88
94
|
sinw = numpy.sin(w)
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
alpha = sinw / (+2 * q[i])
|
|
97
|
+
beta = cosw * (-2)
|
|
92
98
|
|
|
93
99
|
# update b
|
|
94
|
-
ba[0, 0] = 1 -
|
|
95
|
-
ba[0, 1] =
|
|
96
|
-
ba[0, 2] = 1 +
|
|
100
|
+
ba[0, 0] = 1 - alpha
|
|
101
|
+
ba[0, 1] = beta
|
|
102
|
+
ba[0, 2] = 1 + alpha
|
|
97
103
|
|
|
98
104
|
# update a
|
|
99
|
-
ba[1, 0] = 1 +
|
|
100
|
-
ba[1, 1] =
|
|
101
|
-
ba[1, 2] = 1 -
|
|
105
|
+
ba[1, 0] = 1 + alpha
|
|
106
|
+
ba[1, 1] = beta
|
|
107
|
+
ba[1, 2] = 1 - alpha
|
|
102
108
|
|
|
103
109
|
# update y
|
|
104
|
-
__df1__(ba, xy, x, y, i)
|
|
110
|
+
__df1__(g[i], ba, xy, x, y, i)
|
biquad/bandpass.py
CHANGED
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT
|
|
|
6
6
|
Source: https://github.com/jurihock/biquad
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from .biquad import biquad, __df1__
|
|
9
|
+
from .biquad import biquad, __df1__, __gain__, __resize__
|
|
10
10
|
|
|
11
11
|
import numba
|
|
12
12
|
import numpy
|
|
@@ -17,7 +17,7 @@ class bandpass(biquad):
|
|
|
17
17
|
Bandpass filter (BPF).
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
def __init__(self, sr,
|
|
20
|
+
def __init__(self, sr, f=None, g=0, q=0.7071, *, mode='peak'):
|
|
21
21
|
"""
|
|
22
22
|
Create a new filter instance.
|
|
23
23
|
|
|
@@ -25,23 +25,25 @@ class bandpass(biquad):
|
|
|
25
25
|
----------
|
|
26
26
|
sr : int or float
|
|
27
27
|
Sample rate in hertz.
|
|
28
|
-
gain : str, skirt or peak, optional
|
|
29
|
-
Choice between constant skirt gain or constant 0 dB peak gain.
|
|
30
28
|
f : int or float, optional
|
|
31
29
|
Persistent filter frequency parameter in hertz.
|
|
30
|
+
g : int or float, optional
|
|
31
|
+
Persistent filter gain parameter in decibel.
|
|
32
32
|
q : int or float, optional
|
|
33
33
|
Persistent filter quality parameter.
|
|
34
|
+
mode : str, peak or skirt, optional
|
|
35
|
+
Choice between constant 0 dB peak gain or constant skirt gain.
|
|
34
36
|
"""
|
|
35
37
|
|
|
36
|
-
super().__init__(sr=sr, f=f, q=q)
|
|
38
|
+
super().__init__(sr=sr, f=f, g=g, q=q)
|
|
37
39
|
|
|
38
|
-
assert
|
|
40
|
+
assert mode in ['peak', 'skirt']
|
|
39
41
|
|
|
40
|
-
self.
|
|
42
|
+
self.mode = mode
|
|
41
43
|
|
|
42
|
-
self.__call__(0
|
|
44
|
+
self.__call__(0) # warmup numba
|
|
43
45
|
|
|
44
|
-
def __call__(self, x, f=None, q=None):
|
|
46
|
+
def __call__(self, x, f=None, g=None, q=None):
|
|
45
47
|
"""
|
|
46
48
|
Process single or multiple contiguous signal values at once.
|
|
47
49
|
|
|
@@ -51,6 +53,8 @@ class bandpass(biquad):
|
|
|
51
53
|
Filter input data.
|
|
52
54
|
f : scalar or array like, optional
|
|
53
55
|
Instantaneous filter frequency parameter in hertz.
|
|
56
|
+
g : scalar or array like, optional
|
|
57
|
+
Instantaneous filter gain parameter in decibel.
|
|
54
58
|
q : scalar or array like, optional
|
|
55
59
|
Instantaneous filter quality parameter.
|
|
56
60
|
|
|
@@ -68,25 +72,27 @@ class bandpass(biquad):
|
|
|
68
72
|
x = numpy.atleast_1d(x)
|
|
69
73
|
y = numpy.zeros(x.shape, x.dtype)
|
|
70
74
|
|
|
71
|
-
f =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
f = numpy.resize(f, x.shape)
|
|
75
|
-
q = numpy.resize(q, x.shape)
|
|
75
|
+
f = __resize__(self.f if f is None else f, x.shape)
|
|
76
|
+
g = __resize__(self.g if g is None else __gain__(g), x.shape)
|
|
77
|
+
q = __resize__(self.q if q is None else q, x.shape)
|
|
76
78
|
|
|
77
79
|
sr = self.sr
|
|
78
|
-
|
|
80
|
+
mode = self.mode
|
|
81
|
+
|
|
82
|
+
self.__filter__(ba, xy, x, y, f, g, q, sr, mode)
|
|
79
83
|
|
|
80
|
-
self.
|
|
84
|
+
self.f = f[-1]
|
|
85
|
+
self.g = g[-1]
|
|
86
|
+
self.q = q[-1]
|
|
81
87
|
|
|
82
88
|
return y[0] if scalar else y
|
|
83
89
|
|
|
84
90
|
@staticmethod
|
|
85
91
|
@numba.jit(nopython=True, fastmath=True)
|
|
86
|
-
def __filter__(ba, xy, x, y, f, q, sr,
|
|
92
|
+
def __filter__(ba, xy, x, y, f, g, q, sr, mode):
|
|
87
93
|
|
|
88
94
|
rs = 2 * numpy.pi / sr
|
|
89
|
-
skirt =
|
|
95
|
+
skirt = mode == 'skirt'
|
|
90
96
|
|
|
91
97
|
for i in range(x.size):
|
|
92
98
|
|
|
@@ -95,19 +101,19 @@ class bandpass(biquad):
|
|
|
95
101
|
cosw = numpy.cos(w)
|
|
96
102
|
sinw = numpy.sin(w)
|
|
97
103
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
alpha = sinw / (+2 * q[i])
|
|
105
|
+
beta = cosw * (-2)
|
|
106
|
+
gamma = sinw / (+2) if skirt else alpha
|
|
101
107
|
|
|
102
108
|
# update b
|
|
103
|
-
ba[0, 0] = +
|
|
109
|
+
ba[0, 0] = +gamma
|
|
104
110
|
ba[0, 1] = 0
|
|
105
|
-
ba[0, 2] = -
|
|
111
|
+
ba[0, 2] = -gamma
|
|
106
112
|
|
|
107
113
|
# update a
|
|
108
|
-
ba[1, 0] = 1 +
|
|
109
|
-
ba[1, 1] =
|
|
110
|
-
ba[1, 2] = 1 -
|
|
114
|
+
ba[1, 0] = 1 + alpha
|
|
115
|
+
ba[1, 1] = beta
|
|
116
|
+
ba[1, 2] = 1 - alpha
|
|
111
117
|
|
|
112
118
|
# update y
|
|
113
|
-
__df1__(ba, xy, x, y, i)
|
|
119
|
+
__df1__(g[i], ba, xy, x, y, i)
|
biquad/biquad.py
CHANGED
|
@@ -6,17 +6,15 @@ SPDX-License-Identifier: MIT
|
|
|
6
6
|
Source: https://github.com/jurihock/biquad
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from .plot import plot
|
|
10
|
-
|
|
11
9
|
import numba
|
|
12
10
|
import numpy
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
@numba.jit(nopython=True, fastmath=True)
|
|
16
|
-
def __df1__(ba, xy, x, y, i):
|
|
14
|
+
def __df1__(g, ba, xy, x, y, i):
|
|
17
15
|
"""
|
|
18
|
-
|
|
19
|
-
as well as specified filter coeffs ba and delay line xy,
|
|
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,
|
|
20
18
|
according to the Direct Form 1.
|
|
21
19
|
"""
|
|
22
20
|
|
|
@@ -31,37 +29,67 @@ def __df1__(ba, xy, x, y, i):
|
|
|
31
29
|
# update x
|
|
32
30
|
xy[0, 0] = x[i]
|
|
33
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
|
+
|
|
34
36
|
# update y
|
|
35
|
-
xy[1, 0] = (
|
|
36
|
-
ba[0, 1] * xy[0, 1] + \
|
|
37
|
-
ba[0, 2] * xy[0, 2] - \
|
|
38
|
-
ba[1, 1] * xy[1, 1] - \
|
|
39
|
-
ba[1, 2] * xy[1, 2]) / ba[1, 0]
|
|
37
|
+
xy[1, 0] = (bx * g - ay) / ba[1, 0]
|
|
40
38
|
|
|
41
39
|
# return y
|
|
42
40
|
y[i] = xy[1, 0]
|
|
43
41
|
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
def __gain__(x, divisor=20):
|
|
46
44
|
"""
|
|
47
|
-
|
|
45
|
+
Convert the specified decibel gain to the linear gain.
|
|
48
46
|
"""
|
|
49
47
|
|
|
50
|
-
|
|
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):
|
|
51
62
|
"""
|
|
52
|
-
|
|
53
|
-
- ba[0] holds b coefficients
|
|
54
|
-
- ba[1] holds a coefficients
|
|
63
|
+
Resize the specified value to the ndarray of desired shape.
|
|
55
64
|
"""
|
|
56
65
|
|
|
57
|
-
|
|
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:
|
|
58
79
|
"""
|
|
59
|
-
Biquad filter
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
90
|
"""
|
|
63
91
|
|
|
64
|
-
def __init__(self, sr,
|
|
92
|
+
def __init__(self, sr, f=None, g=None, q=None):
|
|
65
93
|
"""
|
|
66
94
|
Create a new filter instance.
|
|
67
95
|
|
|
@@ -71,26 +99,34 @@ class biquad:
|
|
|
71
99
|
Sample rate in hertz.
|
|
72
100
|
f : int or float, optional
|
|
73
101
|
Persistent filter frequency parameter in hertz.
|
|
102
|
+
g : int or float, optional
|
|
103
|
+
Persistent filter gain parameter in decibel.
|
|
74
104
|
q : int or float, optional
|
|
75
105
|
Persistent filter quality parameter.
|
|
76
106
|
"""
|
|
77
107
|
|
|
78
108
|
assert (sr is not None) and (numpy.isscalar(sr) and numpy.isreal(sr))
|
|
79
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))
|
|
80
111
|
assert (q is None) or (numpy.isscalar(q) and numpy.isreal(q))
|
|
81
112
|
|
|
82
113
|
self.sr = sr
|
|
83
|
-
self.f = f
|
|
84
|
-
self.
|
|
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)
|
|
85
120
|
|
|
86
121
|
# warmup numba
|
|
122
|
+
g = self.g
|
|
87
123
|
ba = self.ba
|
|
88
124
|
xy = self.xy
|
|
89
|
-
x
|
|
90
|
-
y
|
|
91
|
-
__df1__(ba, xy, x, y, 0)
|
|
125
|
+
x = numpy.zeros(1, float)
|
|
126
|
+
y = numpy.zeros(x.shape, x.dtype)
|
|
127
|
+
__df1__(g, ba, xy, x, y, 0)
|
|
92
128
|
|
|
93
|
-
def __call__(self, x, f=None, q=None):
|
|
129
|
+
def __call__(self, x, f=None, g=None, q=None):
|
|
94
130
|
"""
|
|
95
131
|
Process single or multiple contiguous signal values at once.
|
|
96
132
|
|
|
@@ -100,6 +136,8 @@ class biquad:
|
|
|
100
136
|
Filter input data.
|
|
101
137
|
f : scalar or array like, optional
|
|
102
138
|
Instantaneous filter frequency parameter in hertz.
|
|
139
|
+
g : scalar or array like, optional
|
|
140
|
+
Instantaneous filter gain parameter in decibel.
|
|
103
141
|
q : scalar or array like, optional
|
|
104
142
|
Instantaneous filter quality parameter.
|
|
105
143
|
|
|
@@ -117,19 +155,27 @@ class biquad:
|
|
|
117
155
|
x = numpy.atleast_1d(x)
|
|
118
156
|
y = numpy.zeros(x.shape, x.dtype)
|
|
119
157
|
|
|
120
|
-
self.
|
|
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]
|
|
121
167
|
|
|
122
168
|
return y[0] if scalar else y
|
|
123
169
|
|
|
124
170
|
@staticmethod
|
|
125
171
|
@numba.jit(nopython=True, fastmath=True)
|
|
126
|
-
def __filter__(ba, xy, x, y):
|
|
172
|
+
def __filter__(ba, xy, x, y, g):
|
|
127
173
|
|
|
128
174
|
for i in range(x.size):
|
|
129
175
|
|
|
130
|
-
__df1__(ba, xy, x, y, i)
|
|
176
|
+
__df1__(g[i], ba, xy, x, y, i)
|
|
131
177
|
|
|
132
|
-
def response(self, norm=False, log=False):
|
|
178
|
+
def response(self, *, norm=False, log=False):
|
|
133
179
|
"""
|
|
134
180
|
Returns frequency and phase response of the transfer function given by the ba coefficients.
|
|
135
181
|
|
|
@@ -154,6 +200,8 @@ class biquad:
|
|
|
154
200
|
|
|
155
201
|
(b, a), sr = self.ba, self.sr
|
|
156
202
|
|
|
203
|
+
b *= self.g
|
|
204
|
+
|
|
157
205
|
n = int(sr / 2)
|
|
158
206
|
|
|
159
207
|
# compute frequencies from 0 to pi or sr/2 but excluding the Nyquist frequency
|
|
@@ -175,12 +223,46 @@ class biquad:
|
|
|
175
223
|
|
|
176
224
|
return w, h
|
|
177
225
|
|
|
178
|
-
def plot(self):
|
|
226
|
+
def plot(self, *, log=False):
|
|
179
227
|
"""
|
|
180
|
-
|
|
181
|
-
|
|
228
|
+
Creates filter frequency and phase response plot.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
log : bool, optional
|
|
233
|
+
Option whether to express frequency values logarithmically.
|
|
182
234
|
"""
|
|
183
235
|
|
|
236
|
+
import matplotlib.pyplot as pyplot
|
|
237
|
+
|
|
184
238
|
w, h = self.response()
|
|
185
239
|
|
|
186
|
-
|
|
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
|
biquad/highpass.py
CHANGED
|
@@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT
|
|
|
6
6
|
Source: https://github.com/jurihock/biquad
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from .biquad import biquad, __df1__
|
|
9
|
+
from .biquad import biquad, __df1__, __gain__, __resize__
|
|
10
10
|
|
|
11
11
|
import numba
|
|
12
12
|
import numpy
|
|
@@ -17,7 +17,7 @@ class highpass(biquad):
|
|
|
17
17
|
Highpass filter (HPF).
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
def __init__(self, sr,
|
|
20
|
+
def __init__(self, sr, f=None, g=0, q=0.7071):
|
|
21
21
|
"""
|
|
22
22
|
Create a new filter instance.
|
|
23
23
|
|
|
@@ -27,15 +27,17 @@ class highpass(biquad):
|
|
|
27
27
|
Sample rate in hertz.
|
|
28
28
|
f : int or float, optional
|
|
29
29
|
Persistent filter frequency parameter in hertz.
|
|
30
|
+
g : int or float, optional
|
|
31
|
+
Persistent filter gain parameter in decibel.
|
|
30
32
|
q : int or float, optional
|
|
31
33
|
Persistent filter quality parameter.
|
|
32
34
|
"""
|
|
33
35
|
|
|
34
|
-
super().__init__(sr=sr, f=f, q=q)
|
|
36
|
+
super().__init__(sr=sr, f=f, g=g, q=q)
|
|
35
37
|
|
|
36
|
-
self.__call__(0
|
|
38
|
+
self.__call__(0) # warmup numba
|
|
37
39
|
|
|
38
|
-
def __call__(self, x, f=None, q=None):
|
|
40
|
+
def __call__(self, x, f=None, g=None, q=None):
|
|
39
41
|
"""
|
|
40
42
|
Process single or multiple contiguous signal values at once.
|
|
41
43
|
|
|
@@ -45,6 +47,8 @@ class highpass(biquad):
|
|
|
45
47
|
Filter input data.
|
|
46
48
|
f : scalar or array like, optional
|
|
47
49
|
Instantaneous filter frequency parameter in hertz.
|
|
50
|
+
g : scalar or array like, optional
|
|
51
|
+
Instantaneous filter gain parameter in decibel.
|
|
48
52
|
q : scalar or array like, optional
|
|
49
53
|
Instantaneous filter quality parameter.
|
|
50
54
|
|
|
@@ -62,21 +66,23 @@ class highpass(biquad):
|
|
|
62
66
|
x = numpy.atleast_1d(x)
|
|
63
67
|
y = numpy.zeros(x.shape, x.dtype)
|
|
64
68
|
|
|
65
|
-
f =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
f = numpy.resize(f, x.shape)
|
|
69
|
-
q = numpy.resize(q, x.shape)
|
|
69
|
+
f = __resize__(self.f if f is None else f, x.shape)
|
|
70
|
+
g = __resize__(self.g if g is None else __gain__(g), x.shape)
|
|
71
|
+
q = __resize__(self.q if q is None else q, x.shape)
|
|
70
72
|
|
|
71
73
|
sr = self.sr
|
|
72
74
|
|
|
73
|
-
self.__filter__(ba, xy, x, y, f, q, sr)
|
|
75
|
+
self.__filter__(ba, xy, x, y, f, g, q, sr)
|
|
76
|
+
|
|
77
|
+
self.f = f[-1]
|
|
78
|
+
self.g = g[-1]
|
|
79
|
+
self.q = q[-1]
|
|
74
80
|
|
|
75
81
|
return y[0] if scalar else y
|
|
76
82
|
|
|
77
83
|
@staticmethod
|
|
78
84
|
@numba.jit(nopython=True, fastmath=True)
|
|
79
|
-
def __filter__(ba, xy, x, y, f, q, sr):
|
|
85
|
+
def __filter__(ba, xy, x, y, f, g, q, sr):
|
|
80
86
|
|
|
81
87
|
rs = 2 * numpy.pi / sr
|
|
82
88
|
|
|
@@ -87,8 +93,8 @@ class highpass(biquad):
|
|
|
87
93
|
cosw = numpy.cos(w)
|
|
88
94
|
sinw = numpy.sin(w)
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
alpha = sinw / (+2 * q[i])
|
|
97
|
+
beta = cosw * (-2)
|
|
92
98
|
|
|
93
99
|
# update b
|
|
94
100
|
ba[0, 1] = -(1 + cosw)
|
|
@@ -96,9 +102,9 @@ class highpass(biquad):
|
|
|
96
102
|
ba[0, 0] = ba[0, 2]
|
|
97
103
|
|
|
98
104
|
# update a
|
|
99
|
-
ba[1, 0] = 1 +
|
|
100
|
-
ba[1, 1] =
|
|
101
|
-
ba[1, 2] = 1 -
|
|
105
|
+
ba[1, 0] = 1 + alpha
|
|
106
|
+
ba[1, 1] = beta
|
|
107
|
+
ba[1, 2] = 1 - alpha
|
|
102
108
|
|
|
103
109
|
# update y
|
|
104
|
-
__df1__(ba, xy, x, y, i)
|
|
110
|
+
__df1__(g[i], ba, xy, x, y, i)
|