Skip to content

Commit d5d11bf

Browse files
committed
parameters.clarity implemented
1 parent 0cdb24b commit d5d11bf

File tree

1 file changed

+67
-47
lines changed

1 file changed

+67
-47
lines changed

pyrato/parameters.py

Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
"""
55
import re
66
import numpy as np
7-
import pyfar.signals as pysi
8-
from . import dsp
7+
import pyfar as pf
98
import warnings
109

1110

@@ -110,85 +109,106 @@ def reverberation_time_linear_regression(
110109

111110

112111

113-
import numpy as np
114-
import pyfar as pf
115-
import pyfar.dsp as dsp
116112

117113
def clarity(RIR, early_time_limit=80):
118-
"""Calculate the clarity of a signal in a room.
119-
120-
The clarity parameter is calculated with the early-to-late index at 50 ms or 80 ms and describes how
121-
clearly someone can hear sound and music in a room
114+
"""
115+
Calculate the clarity of a room impulse response.
116+
117+
The clarity parameter (C50 or C80) is defined as the ratio of early-to-late
118+
arriving energy in an impulse response and describes how clearly speech or
119+
music can be perceived in a room. The early-to-late boundary is typically
120+
set at 50 ms (C50) or 80 ms (C80).
122121
123122
Parameters
124123
----------
125124
RIR : pyfar.Signal
126-
Room impulse response (or energy decay curve)
127-
early_time_limit : float [s]
128-
Early time limit to calculate the clarity as a scalar in seconds
129-
Typically 0.05 (C50) or 0.08 (C80).
125+
Room impulse response (time-domain signal).
126+
early_time_limit : float, optional
127+
Early time limit in milliseconds. Defaults to 80 (C80). Typical values
128+
are 50 ms (C50) or 80 ms (C80).
129+
frequencies : None or ndarray, optional
130+
Placeholder for octave/third-octave band center frequencies if the
131+
result should be returned as a frequency-domain representation. The
132+
filtering must be applied by the user prior to calling this function.
130133
131134
Returns
132135
-------
133-
clarity : ndarray [dB]
134-
Clarity index (early-to-late energy ratio) in decibel,
135-
shaped according to the channel structure of RIR.
136+
clarity : ndarray of float
137+
Clarity index (early-to-late energy ratio) in decibels, shaped according
138+
to the channel structure of ``RIR``.
139+
140+
References
141+
----------
142+
ISO 3382-1 : Annex A
143+
144+
Examples
145+
--------
146+
147+
Estimate the clarity from a real room impulse response and octave-band
148+
filtering:
149+
150+
>>> import numpy as np
151+
>>> import pyfar as pf
152+
>>> import pyrato as ra
153+
>>> RIR = pf.signals.files.room_impulse_response(sampling_rate=44100)
154+
>>> RIR = pf.dsp.filter.fractional_octave_bands(RIR, bands_per_octave=3)
155+
>>> C80 = ra.parameters.clarity(RIR, early_time_limit=80)
136156
137-
Reference
138-
---------
139-
ISO3382-1 : Annex A
140157
"""
158+
141159
if not hasattr(RIR, "cshape") or not hasattr(RIR, "sampling_rate"):
142-
raise AttributeError("clarity() requires a Signal object as input.")
160+
raise AttributeError("clarity() requires a signal object as input.")
143161

144162
# warnign for unusual early_time_limit
145163
if early_time_limit not in (50, 80):
146164
warnings.warn(
147-
f"early_time_limit={early_time_limit}s is unusual. "
148-
"Typically 50ms (C50) or 80ms (C80) are used.",
165+
f"early_time_limit={early_time_limit}ms is unusual. "
166+
"Typically 50ms (C50) or 80ms (C80) are chosen.",
149167
UserWarning
150168
)
169+
signal_length_ms = (RIR.signal_length) * 1000
170+
if early_time_limit > signal_length_ms:
171+
raise ValueError("early_time_limit cannot be larger than signal length.")
172+
if early_time_limit <= 0:
173+
raise ValueError("early_time_limit must be positive.")
174+
175+
if RIR.complex:
176+
warnings.warn(
177+
"Complex-valued input detected. Clarity is only defined for real "
178+
"signals and will be computed using |x(t)|^2.",
179+
UserWarning,
180+
)
181+
182+
# convert milliseconds to seconds for index lookup
183+
early_time_limit_sec = early_time_limit / 1000
151184

152-
# get channel shape & flatten audio object
153185
channel_shape = RIR.cshape
154186
RIR_flat = RIR.flatten()
155187

156188
clarity_vals = []
157189

158-
# iterate over flattended channels
159190
for rir in RIR_flat:
191+
start_index = pf.dsp.find_impulse_response_start(rir)[0]
192+
early_time_limit_index = int(rir.find_nearest_time(early_time_limit_sec))
160193

161-
# start-index
162-
start_index = dsp.find_impulse_response_start(rir)[0]
163-
164-
# early_time_limit-index
165-
early_time_limit_index = int(rir.find_nearest_time(early_time_limit/1000))
194+
# energy from squared amplitude
195+
energy_decay = np.abs(rir.time) ** 2
166196

167-
# calculate edc
168-
if rir.signal_type == "energy":
169-
energy_decay = rir.time
170-
else:
171-
energy_decay = rir.time**2
172-
173-
# late- and early energy
174-
energy_decay_early = np.sum(energy_decay[:,start_index:early_time_limit_index])
175-
energy_decay_late = np.sum(energy_decay[:,early_time_limit_index:])
197+
early_energy = np.sum(energy_decay[:, start_index:early_time_limit_index])
198+
late_energy = np.sum(energy_decay[:, early_time_limit_index:])
176199

177-
# clarity fraction incl. edge case handling
178-
if energy_decay_early == 0 and energy_decay_late == 0:
200+
if early_energy == 0 and late_energy == 0:
179201
val = np.nan
180-
elif energy_decay_early == 0:
202+
elif early_energy == 0:
181203
val = -np.inf
182-
elif energy_decay_late == 0:
204+
elif late_energy == 0:
183205
val = np.inf
184206
else:
185-
val = 10 * np.log10(energy_decay_early / energy_decay_late)
207+
val = 10 * np.log10(early_energy / late_energy)
186208

187209
clarity_vals.append(val)
188210

189-
# reshape array to channel_shape
190-
clarity_vals = np.array(clarity_vals).reshape(channel_shape)
191-
192-
return clarity_vals
211+
clarity = np.array(clarity_vals).reshape(channel_shape)
212+
return clarity
193213

194214

0 commit comments

Comments
 (0)