From ef22068f1bdfa22195df0eab6f3331bb7eaae335 Mon Sep 17 00:00:00 2001 From: Martmists Date: Tue, 11 Oct 2022 22:14:00 +0200 Subject: [PATCH 01/11] Add Equal Loudness Filter Signed-off-by: Martmists --- audio_filters/equal_loudness_filter.py | 135 +++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 136 insertions(+) create mode 100644 audio_filters/equal_loudness_filter.py diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py new file mode 100644 index 000000000000..a3f4c685853b --- /dev/null +++ b/audio_filters/equal_loudness_filter.py @@ -0,0 +1,135 @@ +import numpy as np +from yulewalker import yulewalk + +from audio_filters.butterworth_filter import make_highpass +from audio_filters.iir_filter import IIRFilter + + +class EqualLoudnessFilter: + r""" + An equal-loudness filter which compensates for the human ear's non-linear response + to sound. + This filter corrects this by cascading a yulewalk filter and a butterworth filter. + + Designed for use with samplerate of 44.1kHz and above. If you're using a lower + samplerate, use with caution. + + Code based on matlab implementation at + https://web.archive.org/web/20130119143147/\ + http://replaygain.hydrogenaudio.org/proposal/equal_loudness.html + (url trimmed for flake8) + + Target curve: https://i.imgur.com/3g2VfaM.png + Yulewalk response: https://i.imgur.com/J9LnJ4C.png + Butterworth and overall response: https://i.imgur.com/3g2VfaM.png + + Images and original matlab implementation by David Robinson, 2001 + """ + + def __init__(self, samplerate: int = 44100): + self.yulewalk_filter = IIRFilter(10) + self.butterworth_filter = make_highpass(150, samplerate) + + # The following is a representative average of the Equal Loudness Contours + # as measured by Robinson and Dadson, 1956 + curve_freqs = np.asarray( + [ + 0, + 20, + 30, + 40, + 50, + 60, + 70, + 80, + 90, + 100, + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900, + 1000, + 1500, + 2000, + 2500, + 3000, + 3700, + 4000, + 5000, + 6000, + 7000, + 8000, + 9000, + 10000, + 12000, + 15000, + 20000, + max(20000.0, samplerate / 2), # pad to nyquist + ] + ) + curve_gains = np.asarray( + [ + 120, + 113, + 103, + 97, + 93, + 91, + 89, + 87, + 86, + 85, + 78, + 76, + 76, + 76, + 76, + 77, + 78, + 79.5, + 80, + 79, + 77, + 74, + 71.5, + 70, + 70.5, + 74, + 79, + 84, + 86, + 86, + 85, + 95, + 110, + 125, + 140, + ] + ) + + # Convert to angular frequency + freqs_normalized = curve_freqs / samplerate * 2 + # Invert the curve and normalize to 0dB + gains_normalized = np.power(10, (np.min(curve_gains) - curve_gains) / 20) + + # Scipy's `yulewalk` function is a stub, so we're using the + # `yulewalker` library instead. + # This function computes the coefficients using a least-squares + # fit to the specified curve. + ya, yb = yulewalk(10, freqs_normalized, gains_normalized) + self.yulewalk_filter.set_coefficients(ya, yb) + + def process(self, sample: float) -> float: + """ + Process a single sample through both filters + + >>> filt = EqualLoudnessFilter() + >>> filt.process(0.0) + 0.0 + """ + tmp = self.yulewalk_filter.process(sample) + return self.butterworth_filter.process(tmp) diff --git a/requirements.txt b/requirements.txt index 294494acf41a..0fbc1cc4b45c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ tensorflow texttable tweepy xgboost +yulewalker From 412b4b68f903e06490ea7eb15d556138ee477d93 Mon Sep 17 00:00:00 2001 From: Martmists Date: Tue, 11 Oct 2022 22:17:24 +0200 Subject: [PATCH 02/11] NoneType return on __init__ Signed-off-by: Martmists --- audio_filters/equal_loudness_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index a3f4c685853b..b9d6cf37ef96 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -26,7 +26,7 @@ class EqualLoudnessFilter: Images and original matlab implementation by David Robinson, 2001 """ - def __init__(self, samplerate: int = 44100): + def __init__(self, samplerate: int = 44100) -> None: self.yulewalk_filter = IIRFilter(10) self.butterworth_filter = make_highpass(150, samplerate) From 3cd4e81561c8089bdb33b5c73b3208cbc17f07ec Mon Sep 17 00:00:00 2001 From: Martmists Date: Tue, 11 Oct 2022 23:04:03 +0200 Subject: [PATCH 03/11] Add data to JSON as requested by @CenTdemeern1 in a not very polite manner Signed-off-by: Martmists --- audio_filters/equal_loudness_filter.py | 96 ++++---------------------- audio_filters/loudness_curve.json | 76 ++++++++++++++++++++ 2 files changed, 90 insertions(+), 82 deletions(-) create mode 100644 audio_filters/loudness_curve.json diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index b9d6cf37ef96..f597897a9a56 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -1,3 +1,6 @@ +from json import load +from os.path import dirname, abspath + import numpy as np from yulewalker import yulewalk @@ -5,6 +8,10 @@ from audio_filters.iir_filter import IIRFilter +with open(abspath(dirname(__file__)) + "/loudness_curve.json", "r") as fp: + data = load(fp) + + class EqualLoudnessFilter: r""" An equal-loudness filter which compensates for the human ear's non-linear response @@ -14,10 +21,8 @@ class EqualLoudnessFilter: Designed for use with samplerate of 44.1kHz and above. If you're using a lower samplerate, use with caution. - Code based on matlab implementation at - https://web.archive.org/web/20130119143147/\ - http://replaygain.hydrogenaudio.org/proposal/equal_loudness.html - (url trimmed for flake8) + Code based on matlab implementation at https://bit.ly/3eqh2HU + (url shortened for flake8) Target curve: https://i.imgur.com/3g2VfaM.png Yulewalk response: https://i.imgur.com/J9LnJ4C.png @@ -30,85 +35,12 @@ def __init__(self, samplerate: int = 44100) -> None: self.yulewalk_filter = IIRFilter(10) self.butterworth_filter = make_highpass(150, samplerate) - # The following is a representative average of the Equal Loudness Contours - # as measured by Robinson and Dadson, 1956 - curve_freqs = np.asarray( - [ - 0, - 20, - 30, - 40, - 50, - 60, - 70, - 80, - 90, - 100, - 200, - 300, - 400, - 500, - 600, - 700, - 800, - 900, - 1000, - 1500, - 2000, - 2500, - 3000, - 3700, - 4000, - 5000, - 6000, - 7000, - 8000, - 9000, - 10000, - 12000, - 15000, - 20000, - max(20000.0, samplerate / 2), # pad to nyquist - ] + # pad the data to nyquist + curve_freqs = np.array( + data["frequencies"] + [max(20000.0, samplerate / 2)] ) - curve_gains = np.asarray( - [ - 120, - 113, - 103, - 97, - 93, - 91, - 89, - 87, - 86, - 85, - 78, - 76, - 76, - 76, - 76, - 77, - 78, - 79.5, - 80, - 79, - 77, - 74, - 71.5, - 70, - 70.5, - 74, - 79, - 84, - 86, - 86, - 85, - 95, - 110, - 125, - 140, - ] + curve_gains = np.array( + data["gains"] + [140] ) # Convert to angular frequency diff --git a/audio_filters/loudness_curve.json b/audio_filters/loudness_curve.json new file mode 100644 index 000000000000..3c4becd1a852 --- /dev/null +++ b/audio_filters/loudness_curve.json @@ -0,0 +1,76 @@ +{ + "_comment": "The following is a representative average of the Equal Loudness Contours as measured by Robinson and Dadson, 1956", + "_doi": "10.1088/0508-3443/7/5/302", + "frequencies": [ + 0, + 20, + 30, + 40, + 50, + 60, + 70, + 80, + 90, + 100, + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900, + 1000, + 1500, + 2000, + 2500, + 3000, + 3700, + 4000, + 5000, + 6000, + 7000, + 8000, + 9000, + 10000, + 12000, + 15000, + 20000 + ], + "gains": [ + 120, + 113, + 103, + 97, + 93, + 91, + 89, + 87, + 86, + 85, + 78, + 76, + 76, + 76, + 76, + 77, + 78, + 79.5, + 80, + 79, + 77, + 74, + 71.5, + 70, + 70.5, + 74, + 79, + 84, + 86, + 86, + 85, + 95, + 110, + 125 + ] +} \ No newline at end of file From 702b6a3a7443553e9ef9bf5c73e605562910de66 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 21:05:02 +0000 Subject: [PATCH 04/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- audio_filters/equal_loudness_filter.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index f597897a9a56..776efcd42609 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -1,5 +1,5 @@ from json import load -from os.path import dirname, abspath +from os.path import abspath, dirname import numpy as np from yulewalker import yulewalk @@ -7,8 +7,7 @@ from audio_filters.butterworth_filter import make_highpass from audio_filters.iir_filter import IIRFilter - -with open(abspath(dirname(__file__)) + "/loudness_curve.json", "r") as fp: +with open(abspath(dirname(__file__)) + "/loudness_curve.json") as fp: data = load(fp) @@ -36,12 +35,8 @@ def __init__(self, samplerate: int = 44100) -> None: self.butterworth_filter = make_highpass(150, samplerate) # pad the data to nyquist - curve_freqs = np.array( - data["frequencies"] + [max(20000.0, samplerate / 2)] - ) - curve_gains = np.array( - data["gains"] + [140] - ) + curve_freqs = np.array(data["frequencies"] + [max(20000.0, samplerate / 2)]) + curve_gains = np.array(data["gains"] + [140]) # Convert to angular frequency freqs_normalized = curve_freqs / samplerate * 2 From 7d366bd28333ce34d58d82aed733aadab180f612 Mon Sep 17 00:00:00 2001 From: Martmists Date: Wed, 12 Oct 2022 14:45:21 +0200 Subject: [PATCH 05/11] 'modernize' Signed-off-by: Martmists --- audio_filters/equal_loudness_filter.py | 6 +++--- audio_filters/loudness_curve.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index 776efcd42609..c1fa87ab4b53 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -1,5 +1,5 @@ from json import load -from os.path import abspath, dirname +from pathlib import Path import numpy as np from yulewalker import yulewalk @@ -7,8 +7,8 @@ from audio_filters.butterworth_filter import make_highpass from audio_filters.iir_filter import IIRFilter -with open(abspath(dirname(__file__)) + "/loudness_curve.json") as fp: - data = load(fp) + +data = load(Path("loudness_curve.json").read_text()) class EqualLoudnessFilter: diff --git a/audio_filters/loudness_curve.json b/audio_filters/loudness_curve.json index 3c4becd1a852..fc066a0810fc 100644 --- a/audio_filters/loudness_curve.json +++ b/audio_filters/loudness_curve.json @@ -73,4 +73,4 @@ 110, 125 ] -} \ No newline at end of file +} From e6c0fdedd524a343132ea31442f2101ec5851f20 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 12:47:46 +0000 Subject: [PATCH 06/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- audio_filters/equal_loudness_filter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index c1fa87ab4b53..91c7aa28b007 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -7,7 +7,6 @@ from audio_filters.butterworth_filter import make_highpass from audio_filters.iir_filter import IIRFilter - data = load(Path("loudness_curve.json").read_text()) From bc19d88b594920b8ad860fcadcab2a9f3119742f Mon Sep 17 00:00:00 2001 From: Martmists Date: Wed, 12 Oct 2022 14:57:33 +0200 Subject: [PATCH 07/11] Update audio_filters/equal_loudness_filter.py Co-authored-by: Christian Clauss --- audio_filters/equal_loudness_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index 91c7aa28b007..d0f1bc047612 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -7,7 +7,7 @@ from audio_filters.butterworth_filter import make_highpass from audio_filters.iir_filter import IIRFilter -data = load(Path("loudness_curve.json").read_text()) +data = load(Path(__file__).resolve().parent / "loudness_curve.json").read_text()) class EqualLoudnessFilter: From 63cfd221bc84608c97409cbeebf0f9bf617c2646 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 12 Oct 2022 15:09:10 +0200 Subject: [PATCH 08/11] Update equal_loudness_filter.py --- audio_filters/equal_loudness_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index d0f1bc047612..017431374db3 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -7,7 +7,7 @@ from audio_filters.butterworth_filter import make_highpass from audio_filters.iir_filter import IIRFilter -data = load(Path(__file__).resolve().parent / "loudness_curve.json").read_text()) +data = load(Path(__file__).resolve().parent / "loudness_curve.json".read_text()) class EqualLoudnessFilter: From 62441bd6b4af86ff37d41089ad918f22447943a7 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 12 Oct 2022 15:11:10 +0200 Subject: [PATCH 09/11] Update equal_loudness_filter.py --- audio_filters/equal_loudness_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index 017431374db3..5931942704df 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -7,7 +7,7 @@ from audio_filters.butterworth_filter import make_highpass from audio_filters.iir_filter import IIRFilter -data = load(Path(__file__).resolve().parent / "loudness_curve.json".read_text()) +data = load((Path(__file__).resolve().parent / "loudness_curve.json").read_text()) class EqualLoudnessFilter: From 243dac33ffe2ac8121f5898331f1dde5df979a70 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 12 Oct 2022 15:13:12 +0200 Subject: [PATCH 10/11] Finally!! --- audio_filters/equal_loudness_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index 5931942704df..124ae6fe2ccc 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -7,7 +7,7 @@ from audio_filters.butterworth_filter import make_highpass from audio_filters.iir_filter import IIRFilter -data = load((Path(__file__).resolve().parent / "loudness_curve.json").read_text()) +data = load(Path(__file__).resolve().parent / "loudness_curve.json") class EqualLoudnessFilter: From ae66c1970b1087b33588f834e99d6a25aa73e0b5 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 12 Oct 2022 15:15:18 +0200 Subject: [PATCH 11/11] Arrgghh --- audio_filters/equal_loudness_filter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/audio_filters/equal_loudness_filter.py b/audio_filters/equal_loudness_filter.py index 124ae6fe2ccc..b9a3c50e1c33 100644 --- a/audio_filters/equal_loudness_filter.py +++ b/audio_filters/equal_loudness_filter.py @@ -1,4 +1,4 @@ -from json import load +from json import loads from pathlib import Path import numpy as np @@ -7,7 +7,7 @@ from audio_filters.butterworth_filter import make_highpass from audio_filters.iir_filter import IIRFilter -data = load(Path(__file__).resolve().parent / "loudness_curve.json") +data = loads((Path(__file__).resolve().parent / "loudness_curve.json").read_text()) class EqualLoudnessFilter: