aboutsummaryrefslogtreecommitdiff
path: root/lib/tests/test_dsp.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-06 15:17:57 -0400
committerhistoria <historiavg@proton.me>2026-09-06 15:22:52 -0400
commitc2b0f7e4fb4738afcae1705db8f983dd90a669a4 (patch)
treeffcb4bfea9227b40646ab0836decfbac55b9371b /lib/tests/test_dsp.py
downloadproducer-c2b0f7e4fb4738afcae1705db8f983dd90a669a4.tar.gz
inital commit
Diffstat (limited to 'lib/tests/test_dsp.py')
-rw-r--r--lib/tests/test_dsp.py130
1 files changed, 130 insertions, 0 deletions
diff --git a/lib/tests/test_dsp.py b/lib/tests/test_dsp.py
new file mode 100644
index 0000000..b9508ab
--- /dev/null
+++ b/lib/tests/test_dsp.py
@@ -0,0 +1,130 @@
+import numpy as np
+from conftest import band_db, sine, speechish
+
+from producer import dsp, meters
+
+
+def test_hpf_removes_rumble(sr):
+ x = sine(40, 3.0, sr, -20.0) + sine(200, 3.0, sr, -20.0)
+ y = dsp.hpf(x, sr, 80.0)
+ assert band_db(x, sr, 35, 45) - band_db(y, sr, 35, 45) > 10.0
+ assert abs(band_db(x, sr, 190, 210) - band_db(y, sr, 190, 210)) < 0.5
+
+
+def test_peak_eq_mud_cut(sr):
+ x = sine(300, 3.0, sr, -20.0) + sine(1000, 3.0, sr, -20.0)
+ y = dsp.peak_eq(x, sr, 300.0, -3.0, 1.0)
+ d300 = band_db(x, sr, 280, 320) - band_db(y, sr, 280, 320)
+ d1k = band_db(x, sr, 950, 1050) - band_db(y, sr, 950, 1050)
+ assert 2.4 < d300 < 3.6
+ assert abs(d1k) < 0.3
+
+
+def test_shelf(sr):
+ x = sine(60, 3.0, sr, -20.0) + sine(3000, 3.0, sr, -20.0)
+ y = dsp.shelf(x, sr, 150.0, 1.5, low=True)
+ d60 = band_db(y, sr, 50, 70) - band_db(x, sr, 50, 70)
+ assert 1.1 < d60 < 1.9
+
+
+def test_compressor_steady_sine(sr):
+ x = sine(440, 4.0, sr, peak_dbfs=-10.0)
+ y = dsp.compressor(x, sr, -20.0, 3.0, 15.0, 150.0, 6.0)
+ in_rms = meters.rms_db(x)
+ out_rms = meters.rms_db(y)
+ expected_gr = (1.0 - 1.0 / 3.0) * (in_rms - (-20.0))
+ assert abs((in_rms - out_rms) - expected_gr) < 0.4
+
+
+def test_compressor_quiet_signal_unaffected(sr):
+ x = sine(440, 4.0, sr, peak_dbfs=-45.0)
+ y = dsp.compressor(x, sr, -20.0, 3.0, 15.0, 150.0, 6.0)
+ assert abs(meters.rms_db(x) - meters.rms_db(y)) < 0.1
+
+
+def test_deesser(sr):
+ x = sine(1000, 4.0, sr, -20.0) + sine(6500, 4.0, sr, -30.0)
+ y = dsp.deesser(x, sr, 5500.0, 8000.0, 8.0)
+ d_sib = band_db(x, sr, 5800, 7200) - band_db(y, sr, 5800, 7200)
+ d_mid = band_db(x, sr, 900, 1100) - band_db(y, sr, 900, 1100)
+ assert 1.0 < d_sib < 3.0
+ assert abs(d_mid) < 0.4
+
+
+def test_deesser_bypass(sr):
+ x = sine(1000, 2.0, sr, -20.0)
+ y = dsp.deesser(x, sr, 5500.0, 8000.0, 0.0)
+ assert np.allclose(x, y)
+
+
+def test_expander_attenuates_pauses(sr):
+ speech = speechish(10.0, sr, level_dbfs=-20.0)
+ rng = np.random.default_rng(3)
+ noise = rng.standard_normal(speech.size).astype(np.float64)
+ noise *= (10 ** (-52.0 / 20.0)) / np.sqrt(np.mean(np.square(noise)))
+ x = (speech + noise).astype(np.float32)
+ y = dsp.expander(x, sr, max_drop_db=2.1)
+ frame = int(0.02 * sr)
+ nf = x.size // frame
+ frms_x = np.sqrt(np.mean(np.square(x[: nf * frame].reshape(nf, frame)), axis=1))
+ frms_y = np.sqrt(np.mean(np.square(y[: nf * frame].reshape(nf, frame)), axis=1))
+ fdb_x = 20 * np.log10(frms_x + 1e-12)
+ loud = fdb_x > np.percentile(fdb_x, 75)
+ quiet = fdb_x < np.percentile(fdb_x, 15)
+ stable_loud = np.convolve(loud.astype(int), np.ones(5, dtype=int), mode="same") == 5
+ d_y = 20 * np.log10(frms_y + 1e-12)
+ assert np.mean(fdb_x[stable_loud] - d_y[stable_loud]) < 0.6
+ assert np.mean(fdb_x[quiet] - d_y[quiet]) > 1.5
+
+
+def test_limit_hits_ceiling(sr):
+ x = speechish(6.0, sr, level_dbfs=-3.0)
+ y = dsp.limit(x, sr, -3.0)
+ assert meters.true_peak_db(y, sr) <= -3.0 + 0.1
+ assert meters.rms_db(x) - meters.rms_db(y) < 1.0
+
+
+def test_limit_below_ceiling_transparent(sr):
+ x = speechish(6.0, sr, level_dbfs=-20.0)
+ y = dsp.limit(x, sr, -3.0)
+ assert np.max(np.abs(x - y)) < 1e-6
+
+
+def test_resample_roundtrip(sr):
+ x = speechish(4.0, sr)
+ y = dsp.resample(dsp.resample(x, sr, 48000), 48000, sr)
+ assert abs(y.size - x.size) <= 2
+ assert abs(meters.rms_db(x) - meters.rms_db(y)) < 0.2
+
+
+def test_tape_bypass(sr):
+ x = speechish(3.0, sr)
+ assert np.array_equal(x, dsp.tape(x, sr, 0.0))
+
+
+def test_tape_adds_even_harmonics(sr):
+ x = sine(220, 3.0, sr, -6.0)
+ y = dsp.tape(x, sr, 1.0)
+ assert np.all(np.isfinite(y))
+ d_h2 = band_db(y, sr, 430, 460) - band_db(x, sr, 430, 460)
+ d_h3 = band_db(y, sr, 655, 690) - band_db(x, sr, 655, 690)
+ assert d_h2 > 1.0
+ assert d_h3 > 1.0
+ assert abs(meters.rms_db(x) - meters.rms_db(y)) < 2.0
+
+
+def test_soothe_bypass(sr):
+ x = speechish(3.0, sr)
+ assert np.array_equal(x, dsp.soothe(x, sr, 0.0))
+
+
+def test_soothe_reduces_resonant_bands(sr):
+ x = sine(300, 4.0, sr, -6.0) + sine(3500, 4.0, sr, -6.0) + sine(1000, 4.0, sr, -30.0)
+ y = dsp.soothe(x, sr, 1.0)
+ d_low = band_db(x, sr, 260, 350) - band_db(y, sr, 260, 350)
+ d_harsh = band_db(x, sr, 3200, 3800) - band_db(y, sr, 3200, 3800)
+ assert 2.0 < d_low <= 3.6
+ assert 2.0 < d_harsh <= 5.2
+ # frequencies outside the bands stay untouched
+ d_mid = band_db(x, sr, 900, 1200) - band_db(y, sr, 900, 1200)
+ assert abs(d_mid) < 0.4