Skip to content

Commit 0cdb24b

Browse files
committed
parameters.clarity tests inital
1 parent 9f25c3f commit 0cdb24b

File tree

1 file changed

+43
-89
lines changed

1 file changed

+43
-89
lines changed

tests/test_parameters_clarity.py

Lines changed: 43 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -3,119 +3,73 @@
33
import pyfar as pf
44
from pyrato.parameters import clarity
55

6-
# Input types, Output type
7-
# -------------------
8-
def test_accepts_signal_object():
9-
sig = pf.signals.impulse(
6+
7+
def test_clarity_accepts_signal_object_and_returns_correct_type():
8+
impulse_signal = pf.signals.impulse(
109
n_samples=128, delay=0, amplitude=1, sampling_rate=44100
1110
)
12-
result = clarity(sig, early_time_limit=80)
11+
result = clarity(impulse_signal, early_time_limit=1)
1312

1413
assert isinstance(result, (float, np.ndarray))
15-
assert result.shape == sig.cshape
16-
17-
def test_rejects_non_signal_input():
18-
with pytest.raises(AttributeError):
19-
clarity(np.array([1,2,3]))
14+
assert result.shape == impulse_signal.cshape
2015

2116

17+
def test_clarity_rejects_non_signal_input():
18+
with pytest.raises(AttributeError):
19+
clarity(np.array([1, 2, 3]))
2220

23-
# Multichannel shape
24-
# -------------------
25-
def test_multichannel_shape():
26-
brir = pf.signals.files.binaural_room_impulse_response(diffuse_field_compensation=False, sampling_rate=48000)
2721

22+
def test_clarity_preserves_multichannel_shape():
23+
brir = pf.signals.files.binaural_room_impulse_response(
24+
diffuse_field_compensation=False,
25+
sampling_rate=48000
26+
)
2827
output = clarity(brir, early_time_limit=80)
2928

3029
assert brir.cshape == output.shape
3130

3231

33-
34-
35-
# Edge cases
36-
# -------------------
37-
def test_empty_signal_returns_nan():
38-
sig = pf.Signal(np.zeros(16), 44100)
39-
result = clarity(sig, early_time_limit=80)
32+
def test_clarity_returns_nan_for_zero_signal():
33+
silent_signal = pf.Signal(np.zeros(4096), 44100)
34+
result = clarity(silent_signal)
4035
assert np.isnan(result) or result == -np.inf
4136

4237

43-
def test_unusual_time_limit_warns():
44-
sig = pf.signals.impulse( n_samples=128, delay=0, amplitude=1, sampling_rate=44100)
38+
def test_clarity_warns_for_unusually_short_time_limit():
39+
impulse_signal = pf.signals.impulse(
40+
n_samples=128, delay=0, amplitude=1, sampling_rate=44100
41+
)
4542
with pytest.warns(UserWarning):
46-
clarity(sig, early_time_limit=0.05)
47-
48-
49-
50-
51-
# Energie- vs. Amplitudensignal
52-
# -------------------
53-
# def test_energy_signal_vs_amplitude_signal():
54-
# # gleicher Inhalt, einmal als "energy" markiert
55-
# sig_amp = pf.signals.impulse( n_samples=128, delay=0, amplitude=1, sampling_rate=44100)
56-
# sig_energy = pf.Signal(sig_amp.time**2, 44100)
57-
58-
# # label "energy" automatically assigned?
59-
# result_amp = clarity(sig_amp, early_time_limit=80)
60-
# result_energy = clarity(sig_energy, early_time_limit=80)
43+
clarity(impulse_signal, early_time_limit=0.05)
6144

62-
# # same values
63-
# np.testing.assert_allclose(result_amp, result_energy)
6445

65-
66-
# Reference cases
67-
# -------------------
68-
def test_known_reference_case():
69-
# Artificial signal: 2 Samples early = 2 energy, 2 Samples late = 2 energy
70-
data = np.array([1, 1, 1, 1] + [0]*124)
71-
sig = pf.Signal(data, sampling_rate=1000)
72-
result = clarity(sig, early_time_limit=2) # 2ms limit -> even early/late division
73-
# Early = Late => log10(1) = 0 dB
74-
assert np.isclose(result, 0.0, atol=1e-6)
75-
76-
def load_c80_from_rew(file_path):
77-
c80_values = []
78-
79-
with open(file_path, "r") as f:
80-
for line in f:
81-
line = line.strip()
82-
if not line or not line[0].isdigit(): # nur Zeilen, die mit Zahl anfangen
83-
continue
84-
parts = [p.strip() for p in line.split(",")]
85-
if len(parts) < 16:
86-
continue
87-
try:
88-
c80 = float(parts[15])
89-
c80_values.append(c80)
90-
except ValueError:
91-
continue
92-
93-
return np.array(c80_values, dtype=np.float32)
94-
95-
96-
# test with reference impulse response C80
97-
def test_reference_impulse_response_filtered():
98-
rir = pf.signals.files.room_impulse_response(sampling_rate=48000)
99-
100-
# filter into octave bands
101-
rir_octave_filtered = pf.dsp.filter.fractional_octave_bands(rir, num_fractions=1)
102-
# rir_third_octave_filtered = pf.dsp.filter.fractional_octave_bands(rir, num_fractions=3)
103-
104-
c80_rir_octave_bands = clarity(rir_octave_filtered, early_time_limit=80)
105-
106-
# tolerance?
107-
REW_c80_rir_octave_band = load_c80_from_rew("tests/test_data/example_rir48kHz_REWdata.txt")
46+
def test_clarity_calculates_known_reference_value():
47+
# 2 samples early energy = 2, 2 samples late energy = 2 → ratio = 1 → 0 dB
48+
signal_data = np.concatenate(([1, 1, 1, 1], np.zeros(124)))
49+
test_signal = pf.Signal(signal_data, sampling_rate=1000)
10850

51+
result = clarity(test_signal, early_time_limit=2)
10952

110-
assert np.allclose(REW_c80_rir_octave_band, c80_rir_octave_bands, atol=0.01)
111-
53+
assert np.isclose(result, 0.0, atol=1e-6)
11254

11355

56+
def test_clarity_matches_analytical_geometric_decay_solution():
57+
sampling_rate = 1000
58+
decay_factor = 0.9
59+
total_samples = 200
60+
early_cutoff = 80 # ms
11461

115-
# test with reference impulse response C50
62+
time_axis = np.arange(total_samples)
63+
decaying_signal = pf.Signal(
64+
decay_factor ** time_axis,
65+
sampling_rate=sampling_rate
66+
)
11667

68+
squared_factor = decay_factor ** 2
69+
early_energy = (1 - squared_factor ** early_cutoff) / (1 - squared_factor)
70+
late_energy = (squared_factor ** early_cutoff - squared_factor ** total_samples) / (1 - squared_factor)
71+
expected_db = 10 * np.log10(early_energy / late_energy)
11772

118-
# test with reference impulse response C1000
119-
# what happens if early_time limit is longer than IR?
120-
# what happens if early_time_limit is out of logical bounds?
73+
result = clarity(decaying_signal, early_time_limit=early_cutoff)
12174

75+
assert np.isclose(result, expected_db, atol=1e-6)

0 commit comments

Comments
 (0)