"""Module for room acoustics related functions.
Parametric room acoustics calculations using simple geometric considerations
such as Sabine's theory of sound in rooms.
"""
import numpy as np
from typing import Union
import pyfar as pf
[docs]
def energy_decay_curve(
times : np.ndarray,
reverberation_time : float | np.ndarray,
energy : float | np.ndarray = 1,
) -> pf.TimeData:
r"""Calculate the energy decay curve for the reverberation time and energy.
The energy decay curve is calculated as
.. math::
E(t) = E_0 e^{-\frac{6 \ln(10)}{T_{60}} t}
where :math:`E_0` is the initial energy, :math:`T_{60}` the reverberation
time, and :math:`t` the time [#]_.
Parameters
----------
times : numpy.ndarray[float]
The times at which the energy decay curve is evaluated.
reverberation_time : float | numpy.ndarray[float]
The reverberation time in seconds. If an array is passed, an energy
decay curve is calculated for each reverberation time.
energy : float | numpy.ndarray[float], optional
The initial energy of the sound field, by default 1. If
`reverberation_time` is an array, the shape of `energy` is required
to match the shape or be broadcastable to the shape of
`reverberation_time`.
Returns
-------
energy_decay_curve : pyfar.TimeData
The energy decay curve with a ``cshape`` equal to the shape of
the passed ``reverberation_time``.
Example
-------
Calculate and plot an energy decay curve with a reverberation time of
2 seconds.
.. plot::
>>> import numpy as np
>>> import pyrato
>>> import pyfar as pf
>>>
>>> times = np.linspace(0, 3, 50)
>>> T_60 = [2, 1]
>>> edc = pyrato.parametric.energy_decay_curve(times, T_60)
>>> pf.plot.time(edc, log_prefix=10, dB=True)
References
----------
.. [#] H. Kuttruff, Room acoustics, 4th Ed. Taylor & Francis, 2009.
"""
reverberation_time = np.asarray(reverberation_time)
energy = np.asarray(energy)
times = np.asarray(times)
if np.any(reverberation_time <= 0):
raise ValueError("Reverberation time must be greater than zero.")
if np.any(energy < 0):
raise ValueError("Energy must be greater than or equal to zero.")
if reverberation_time.shape != energy.shape:
try:
energy = np.broadcast_to(energy, reverberation_time.shape)
except ValueError as error:
raise ValueError(
"Reverberation time and energy must be broadcastable to the "
"same shape.",
) from error
matching_shape = reverberation_time.shape
reverberation_time = reverberation_time.flatten()
energy = energy.flatten()
reverberation_time = np.atleast_2d(reverberation_time)
energy = np.atleast_2d(energy)
damping_term = (3*np.log(10) / reverberation_time).T
edc = energy.T * np.exp(-2*damping_term*times)
return pf.TimeData(np.reshape(edc, (*matching_shape, times.size)), times)
[docs]
def critical_distance(
volume,
reverberation_time):
r"""Calculate the critical distance of a room with
given volume and reverberation time.
Assumes the source directivity is 1 (omnidirectional source).
See [#kra]_.
.. math::
d_c = 0.057 \sqrt{\frac{V}{T_{60}}}
Parameters
----------
volume : double
Volume of the room in cubic meters.
reverberation_time : double
Reverberation time of the room in seconds.
Returns
-------
critical_dist : double
The resulting critical distance in meters.
References
----------
.. [#kra] H. Kuttruff, Room acoustics, 4th Ed. Taylor & Francis, 2009.
"""
if reverberation_time <= 0:
raise ValueError("Reverberation time must be greater than zero.")
if volume <= 0:
raise ValueError("Volume must be greater than zero.")
critical_dist = 0.057 * np.sqrt(volume / reverberation_time)
return critical_dist
[docs]
def mean_free_path(
volume,
surface_area):
"""Calculate the mean free path. Source https://ccrma.stanford.edu/~jos/smith-nam/Mean_Free_Path.html.
Parameters
----------
volume : double
Room volume
surface_area : double
Total surface area
Returns
-------
mean free path : double
The calculated mean free path
"""
if volume < 0:
raise ValueError(f"Volume ({volume}) is smaller than 0.")
if surface_area < 0:
raise ValueError(f"Surface area ({surface_area}) is smaller than 0.")
return 4 * volume / surface_area
[docs]
def reverberation_time_eyring(
volume: float,
surface_area: float,
mean_absorption: Union[float, np.ndarray],
speed_of_sound: float = 343.4,
) -> np.ndarray:
r"""
Calculate the reverberation time in rooms as defined by Carl Eyring.
The reverberation time is calculated according to Ref. [#]_ as
.. math::
T_{60} = -\frac{24 \cdot \ln(10)}{c}
\cdot \frac{V}{S \ln(1 - \tilde{\alpha})}
where :math:`V` is the room volume, :math:`S` is the total surface area
of the room, :math:`\tilde{\alpha}` is the average absorption coefficient
of the room surfaces, and :math:`c` is the speed of sound.
Parameters
----------
volume : float
Room volume in :math:`\mathrm{m}^3`
surface_area : float
Total surface area of the room in :math:`\mathrm{m}^2`
mean_absorption : float, numpy.ndarray
Average absorption coefficient of room surfaces between 0 and 1. If
an array is passed, the reverberation time is calculated for each value
in the array.
speed_of_sound : float
Speed of sound in m/s. Default is 343.4 m/s, which corresponds to the
speed of sound in air at 20 °C.
Returns
-------
numpy.ndarray
Reverberation time in seconds. The shape matches the shape of the input
variable `mean_absorption`.
Examples
--------
>>> from pyrato.parametric import reverberation_time_eyring
>>> import numpy as np
>>> volume = 64
>>> surface_area = 96
>>> mean_absorption = [0.1, 0.3, 0.4]
>>> reverb_time = reverberation_time_eyring(
... volume, surface_area, mean_absorption)
>>> np.round(reverb_time, 2)
... array([1.02, 0.3 , 0.21])
References
----------
.. [#] Eyring, C.F., 1930. Reverberation time in "dead" rooms. The Journal
of the Acoustical Society of America, 1(2A_Supplement), pp.168-168.
"""
if speed_of_sound <= 0:
raise ValueError("Speed of sound should be larger than 0")
if volume <= 0:
raise ValueError("Volume should be larger than 0")
if surface_area <= 0:
raise ValueError("Surface area should be larger than 0")
mean_absorption = np.asarray(mean_absorption)
if np.any(mean_absorption < 0) or np.any(mean_absorption > 1):
raise ValueError("mean_absorption should be between 0 and 1")
factor = 24 * np.log(10) / speed_of_sound
with np.errstate(divide='ignore'):
reverberation_time = -factor * (
volume/(surface_area * np.log(1 - mean_absorption)))
reverberation_time = np.where(
np.isclose(mean_absorption, 0, atol=1e-10, rtol=1e-10),
np.inf,
reverberation_time)
return reverberation_time
[docs]
def reverberation_time_sabine(
volume: float,
surface_area: float,
mean_absorption: Union[float, np.ndarray],
speed_of_sound: float = 343.4,
) -> np.ndarray:
r"""
Calculate the reverberation time in rooms as defined by Wallace Sabine.
The reverberation time is calculated according to Ref. [#]_ as
.. math::
T_{60} = \frac{24 \cdot \ln(10)}{c}
\cdot \frac{V}{S\tilde{\alpha}}
where :math:`V` is the room volume, :math:`S` is the total surface area
of the room, :math:`\tilde{\alpha}` is the average absorption
coefficient of the room surfaces, and :math:`c` is the speed of sound.
Parameters
----------
surface_area : float
Total surface area of the room in :math:`\mathrm{m}^2`.
mean_absorption : float, numpy.ndarray
Average absorption coefficient of room surfaces between 0 and 1. If
an array is passed, the reverberation time is calculated for each value
in the array.
volume : float
Room volume in :math:`\mathrm{m}^3`.
speed_of_sound : float
Speed of sound in m/s. Default is 343.4 m/s, which corresponds to the
speed of sound in air at 20 °C.
Returns
-------
numpy.ndarray
Reverberation time in seconds.
References
----------
.. [#] H. Kuttruff, Room acoustics, 4th Ed. Taylor & Francis, 2009.
"""
if speed_of_sound <= 0:
raise ValueError("Speed of sound should be larger than 0")
if volume <= 0:
raise ValueError("Volume should be larger than 0")
if surface_area <= 0:
raise ValueError("Surface area should be larger than 0")
mean_absorption = np.asarray(mean_absorption)
if np.any(mean_absorption < 0) or np.any(mean_absorption > 1):
raise ValueError("mean_absorption should be between 0 and 1")
factor = 24 * np.log(10) / speed_of_sound
with np.errstate(divide='ignore'):
reverberation_time = factor * volume / (surface_area * mean_absorption)
return reverberation_time