1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
119
120
121
122
123
124
125
126
127
128
129
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
|