Skip to content

Commit 768bda8

Browse files
Merge branch 'TheAlgorithms:master' into master
2 parents f176b18 + 0fc24e8 commit 768bda8

File tree

101 files changed

+4318
-453
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+4318
-453
lines changed

.github/pull_request_template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
### **Describe your change:**
1+
### Describe your change:
22

33

44

55
* [ ] Add an algorithm?
66
* [ ] Fix a bug or typo in an existing algorithm?
77
* [ ] Documentation change?
88

9-
### **Checklist:**
9+
### Checklist:
1010
* [ ] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md).
1111
* [ ] This pull request is all my own work -- I have not plagiarized.
1212
* [ ] I know that pull requests will not be merged if they fail the automated tests.

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ repos:
2222
- id: isort
2323
args:
2424
- --profile=black
25+
- repo: https://github.com/asottile/pyupgrade
26+
rev: v2.29.0
27+
hooks:
28+
- id: pyupgrade
29+
args:
30+
- --py39-plus
2531
- repo: https://gitlab.com/pycqa/flake8
2632
rev: 3.9.1
2733
hooks:

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Befo
88

99
### Contributor
1010

11-
We are very happy that you consider implementing algorithms and data structure for others! This repository is referenced and used by learners from all over the globe. Being one of our contributors, you agree and confirm that:
11+
We are very happy that you consider implementing algorithms and data structures for others! This repository is referenced and used by learners from all over the globe. Being one of our contributors, you agree and confirm that:
1212

1313
- You did your work - no plagiarism allowed
1414
- Any plagiarized work will not be merged.
1515
- Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged
16-
- You submitted work fulfils or mostly fulfils our styles and standards
16+
- Your submitted work fulfils or mostly fulfils our styles and standards
1717

1818
__New implementation__ is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity but __identical implementation__ of an existing implementation is not allowed. Please check whether the solution is already implemented or not before submitting your pull request.
1919

@@ -25,7 +25,7 @@ We appreciate any contribution, from fixing a grammar mistake in a comment to im
2525

2626
Your contribution will be tested by our [automated testing on Travis CI](https://travis-ci.org/TheAlgorithms/Python/pull_requests) to save time and mental energy. After you have submitted your pull request, you should see the Travis tests start to run at the bottom of your submission page. If those tests fail, then click on the ___details___ button try to read through the Travis output to understand the failure. If you do not understand, please leave a comment on your submission page and a community member will try to help.
2727

28-
Please help us keep our issue list small by adding fixes: #{$ISSUE_NO} to the commit message of pull requests that resolve open issues. GitHub will use this tag to auto close the issue when the PR is merged.
28+
Please help us keep our issue list small by adding fixes: #{$ISSUE_NO} to the commit message of pull requests that resolve open issues. GitHub will use this tag to auto-close the issue when the PR is merged.
2929

3030
#### What is an Algorithm?
3131

DIRECTORY.md

Lines changed: 39 additions & 3 deletions
Large diffs are not rendered by default.

audio_filters/__init__.py

Whitespace-only changes.

audio_filters/butterworth_filter.py

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
from math import cos, sin, sqrt, tau
2+
3+
from audio_filters.iir_filter import IIRFilter
4+
5+
"""
6+
Create 2nd-order IIR filters with Butterworth design.
7+
8+
Code based on https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html
9+
Alternatively you can use scipy.signal.butter, which should yield the same results.
10+
"""
11+
12+
13+
def make_lowpass(
14+
frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2)
15+
) -> IIRFilter:
16+
"""
17+
Creates a low-pass filter
18+
19+
>>> filter = make_lowpass(1000, 48000)
20+
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
21+
[1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.004277569313094809,
22+
0.008555138626189618, 0.004277569313094809]
23+
"""
24+
w0 = tau * frequency / samplerate
25+
_sin = sin(w0)
26+
_cos = cos(w0)
27+
alpha = _sin / (2 * q_factor)
28+
29+
b0 = (1 - _cos) / 2
30+
b1 = 1 - _cos
31+
32+
a0 = 1 + alpha
33+
a1 = -2 * _cos
34+
a2 = 1 - alpha
35+
36+
filt = IIRFilter(2)
37+
filt.set_coefficients([a0, a1, a2], [b0, b1, b0])
38+
return filt
39+
40+
41+
def make_highpass(
42+
frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2)
43+
) -> IIRFilter:
44+
"""
45+
Creates a high-pass filter
46+
47+
>>> filter = make_highpass(1000, 48000)
48+
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
49+
[1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.9957224306869052,
50+
-1.9914448613738105, 0.9957224306869052]
51+
"""
52+
w0 = tau * frequency / samplerate
53+
_sin = sin(w0)
54+
_cos = cos(w0)
55+
alpha = _sin / (2 * q_factor)
56+
57+
b0 = (1 + _cos) / 2
58+
b1 = -1 - _cos
59+
60+
a0 = 1 + alpha
61+
a1 = -2 * _cos
62+
a2 = 1 - alpha
63+
64+
filt = IIRFilter(2)
65+
filt.set_coefficients([a0, a1, a2], [b0, b1, b0])
66+
return filt
67+
68+
69+
def make_bandpass(
70+
frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2)
71+
) -> IIRFilter:
72+
"""
73+
Creates a band-pass filter
74+
75+
>>> filter = make_bandpass(1000, 48000)
76+
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
77+
[1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.06526309611002579,
78+
0, -0.06526309611002579]
79+
"""
80+
w0 = tau * frequency / samplerate
81+
_sin = sin(w0)
82+
_cos = cos(w0)
83+
alpha = _sin / (2 * q_factor)
84+
85+
b0 = _sin / 2
86+
b1 = 0
87+
b2 = -b0
88+
89+
a0 = 1 + alpha
90+
a1 = -2 * _cos
91+
a2 = 1 - alpha
92+
93+
filt = IIRFilter(2)
94+
filt.set_coefficients([a0, a1, a2], [b0, b1, b2])
95+
return filt
96+
97+
98+
def make_allpass(
99+
frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2)
100+
) -> IIRFilter:
101+
"""
102+
Creates an all-pass filter
103+
104+
>>> filter = make_allpass(1000, 48000)
105+
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
106+
[1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.9077040443587427,
107+
-1.9828897227476208, 1.0922959556412573]
108+
"""
109+
w0 = tau * frequency / samplerate
110+
_sin = sin(w0)
111+
_cos = cos(w0)
112+
alpha = _sin / (2 * q_factor)
113+
114+
b0 = 1 - alpha
115+
b1 = -2 * _cos
116+
b2 = 1 + alpha
117+
118+
filt = IIRFilter(2)
119+
filt.set_coefficients([b2, b1, b0], [b0, b1, b2])
120+
return filt
121+
122+
123+
def make_peak(
124+
frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2)
125+
) -> IIRFilter:
126+
"""
127+
Creates a peak filter
128+
129+
>>> filter = make_peak(1000, 48000, 6)
130+
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
131+
[1.0653405327119334, -1.9828897227476208, 0.9346594672880666, 1.1303715025601122,
132+
-1.9828897227476208, 0.8696284974398878]
133+
"""
134+
w0 = tau * frequency / samplerate
135+
_sin = sin(w0)
136+
_cos = cos(w0)
137+
alpha = _sin / (2 * q_factor)
138+
big_a = 10 ** (gain_db / 40)
139+
140+
b0 = 1 + alpha * big_a
141+
b1 = -2 * _cos
142+
b2 = 1 - alpha * big_a
143+
a0 = 1 + alpha / big_a
144+
a1 = -2 * _cos
145+
a2 = 1 - alpha / big_a
146+
147+
filt = IIRFilter(2)
148+
filt.set_coefficients([a0, a1, a2], [b0, b1, b2])
149+
return filt
150+
151+
152+
def make_lowshelf(
153+
frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2)
154+
) -> IIRFilter:
155+
"""
156+
Creates a low-shelf filter
157+
158+
>>> filter = make_lowshelf(1000, 48000, 6)
159+
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
160+
[3.0409336710888786, -5.608870992220748, 2.602157875636628, 3.139954022810743,
161+
-5.591841778072785, 2.5201667380627257]
162+
"""
163+
w0 = tau * frequency / samplerate
164+
_sin = sin(w0)
165+
_cos = cos(w0)
166+
alpha = _sin / (2 * q_factor)
167+
big_a = 10 ** (gain_db / 40)
168+
pmc = (big_a + 1) - (big_a - 1) * _cos
169+
ppmc = (big_a + 1) + (big_a - 1) * _cos
170+
mpc = (big_a - 1) - (big_a + 1) * _cos
171+
pmpc = (big_a - 1) + (big_a + 1) * _cos
172+
aa2 = 2 * sqrt(big_a) * alpha
173+
174+
b0 = big_a * (pmc + aa2)
175+
b1 = 2 * big_a * mpc
176+
b2 = big_a * (pmc - aa2)
177+
a0 = ppmc + aa2
178+
a1 = -2 * pmpc
179+
a2 = ppmc - aa2
180+
181+
filt = IIRFilter(2)
182+
filt.set_coefficients([a0, a1, a2], [b0, b1, b2])
183+
return filt
184+
185+
186+
def make_highshelf(
187+
frequency: int, samplerate: int, gain_db: float, q_factor: float = 1 / sqrt(2)
188+
) -> IIRFilter:
189+
"""
190+
Creates a high-shelf filter
191+
192+
>>> filter = make_highshelf(1000, 48000, 6)
193+
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
194+
[2.2229172136088806, -3.9587208137297303, 1.7841414181566304, 4.295432981120543,
195+
-7.922740859457287, 3.6756456963725253]
196+
"""
197+
w0 = tau * frequency / samplerate
198+
_sin = sin(w0)
199+
_cos = cos(w0)
200+
alpha = _sin / (2 * q_factor)
201+
big_a = 10 ** (gain_db / 40)
202+
pmc = (big_a + 1) - (big_a - 1) * _cos
203+
ppmc = (big_a + 1) + (big_a - 1) * _cos
204+
mpc = (big_a - 1) - (big_a + 1) * _cos
205+
pmpc = (big_a - 1) + (big_a + 1) * _cos
206+
aa2 = 2 * sqrt(big_a) * alpha
207+
208+
b0 = big_a * (ppmc + aa2)
209+
b1 = -2 * big_a * pmpc
210+
b2 = big_a * (ppmc - aa2)
211+
a0 = pmc + aa2
212+
a1 = 2 * mpc
213+
a2 = pmc - aa2
214+
215+
filt = IIRFilter(2)
216+
filt.set_coefficients([a0, a1, a2], [b0, b1, b2])
217+
return filt

audio_filters/iir_filter.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from __future__ import annotations
2+
3+
4+
class IIRFilter:
5+
r"""
6+
N-Order IIR filter
7+
Assumes working with float samples normalized on [-1, 1]
8+
9+
---
10+
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.
15+
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)
20+
"""
21+
22+
def __init__(self, order: int) -> None:
23+
self.order = order
24+
25+
# a_{0} ... a_{k}
26+
self.a_coeffs = [1.0] + [0.0] * order
27+
# b_{0} ... b_{k}
28+
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
34+
35+
def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None:
36+
"""
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
51+
52+
if len(a_coeffs) != self.order + 1:
53+
raise ValueError(
54+
f"Expected a_coeffs to have {self.order + 1} elements for {self.order}"
55+
f"-order filter, got {len(a_coeffs)}"
56+
)
57+
58+
if len(b_coeffs) != self.order + 1:
59+
raise ValueError(
60+
f"Expected b_coeffs to have {self.order + 1} elements for {self.order}"
61+
f"-order filter, got {len(a_coeffs)}"
62+
)
63+
64+
self.a_coeffs = a_coeffs
65+
self.b_coeffs = b_coeffs
66+
67+
def process(self, sample: float) -> float:
68+
"""
69+
Calculate y[n]
70+
71+
>>> filt = IIRFilter(2)
72+
>>> filt.process(0)
73+
0.0
74+
"""
75+
result = 0.0
76+
77+
# Start at index 1 and do index 0 at the end.
78+
for i in range(1, self.order + 1):
79+
result += (
80+
self.b_coeffs[i] * self.input_history[i - 1]
81+
- self.a_coeffs[i] * self.output_history[i - 1]
82+
)
83+
84+
result = (result + self.b_coeffs[0] * sample) / self.a_coeffs[0]
85+
86+
self.input_history[1:] = self.input_history[:-1]
87+
self.output_history[1:] = self.output_history[:-1]
88+
89+
self.input_history[0] = sample
90+
self.output_history[0] = result
91+
92+
return result

0 commit comments

Comments
 (0)