From 401ae1f666752dd0039d8dc3fc4ff0075ca8893d Mon Sep 17 00:00:00 2001 From: zorea Date: Tue, 4 Jun 2024 19:30:08 +0300 Subject: [PATCH 1/4] update .gitignore --- .gitignore | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 262b3ab..407761c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,14 +12,9 @@ *.ogg *.flac -/blind_rt60.egg-info/ -/build/ -/dist/ -/.idea/misc.xml -/.idea/modules.xml -/.idea/inspectionProfiles/profiles_settings.xml -/.idea/inspectionProfiles/Project_Default.xml -/.idea/pyBlindRT.iml -/.idea/vcs.xml +/blind_rt60.egg-info/* +/build/* +/dist/* +/.idea/* /local_history.patch -/notebooks/.ipynb_checkpoints/ +/notebooks/.ipynb_checkpoints/* From 81c6b40455e125f71942f03adfe8ecab85a57e01 Mon Sep 17 00:00:00 2001 From: zorea Date: Tue, 4 Jun 2024 20:23:03 +0300 Subject: [PATCH 2/4] add calculation of RTx --- blind_rt60/__init__.py | 1 + blind_rt60/estimation.py | 7 +++++++ blind_rt60/utils.py | 28 ++++++++++++++++++++++++++++ blind_rt60/version.py | 2 +- 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 blind_rt60/utils.py diff --git a/blind_rt60/__init__.py b/blind_rt60/__init__.py index 08cc7b8..ff69a32 100644 --- a/blind_rt60/__init__.py +++ b/blind_rt60/__init__.py @@ -24,4 +24,5 @@ """ from .estimation import BlindRT60 +from .utils import calculate_decay_time from .version import __version__ diff --git a/blind_rt60/estimation.py b/blind_rt60/estimation.py index 36fb6d1..83a2685 100644 --- a/blind_rt60/estimation.py +++ b/blind_rt60/estimation.py @@ -5,6 +5,8 @@ import scipy.signal as sig from matplotlib.figure import Figure +from .utils import calculate_decay_time + # Constants FRAME_LENGTH = 200e-3 # Frame length in seconds EPS = np.finfo('float').eps @@ -203,6 +205,11 @@ def visualize(self, x: np.ndarray, fs: int, ylim: Tuple = (0, 1)) -> Figure: fig.tight_layout() return fig + def calculate_rtx(self, decay_db): + if self.tau is None: + raise ValueError("tau has to be estimated first.") + return calculate_decay_time(decay_db, self.tau) + def estimate(self, x: np.ndarray, fs: int) -> float: """ Estimate the reverberation time (RT60) from the input signal. diff --git a/blind_rt60/utils.py b/blind_rt60/utils.py new file mode 100644 index 0000000..daaac38 --- /dev/null +++ b/blind_rt60/utils.py @@ -0,0 +1,28 @@ +import numpy as np + + +def calculate_decay_time(decay_db: float, tau: float) -> float: + """ + Calculates the decay time of a signal based on its decay in decibels (dB) and the time constant (tau). + decay_time = -decay_db / (20 * log10(e)) * tau + + Parameters: + - decay_db (float): The decay of the signal in decibels (dB). Positive values indicate + attenuation, while negative values indicate amplification. + - tau (float): The time constant of the system, which represents the time it takes for + the signal to decay to 1/e (approximately 36.8%) of its initial value. + - e (float, optional): The mathematical constant e (approximately 2.71828). Defaults + to `np.e` for efficiency (already imported with `numpy`). + + Returns: + float: The calculated decay time in the same units as `tau` (typically seconds, milliseconds, etc.). + + Raises: + ValueError: If `decay_db` is not a finite number (i.e., NaN or Inf). + """ + + if not np.isfinite(decay_db): + raise ValueError("decay_db must be a finite number (not NaN or Inf).") + + decay_time = -decay_db / (20 * np.log10(np.e ** -1)) * tau + return decay_time diff --git a/blind_rt60/version.py b/blind_rt60/version.py index cfe1bb5..4c56dce 100644 --- a/blind_rt60/version.py +++ b/blind_rt60/version.py @@ -1,2 +1,2 @@ # blind_rt60 version -__version__ = '0.1.0-a0' \ No newline at end of file +__version__ = '0.1.1' \ No newline at end of file From 7c6891e4504869249948a0d291bcda6cd866b34f Mon Sep 17 00:00:00 2001 From: zorea Date: Tue, 4 Jun 2024 20:23:11 +0300 Subject: [PATCH 3/4] add test for calculation of RTx --- tests/test_blind_rt60.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_blind_rt60.py b/tests/test_blind_rt60.py index fbaf15b..8d8a4d5 100644 --- a/tests/test_blind_rt60.py +++ b/tests/test_blind_rt60.py @@ -78,6 +78,21 @@ def test_sanity(self, decay_rate_smaller: float, decay_rate_larger: float, fs_si blind_rt60 = BlindRT60(fs=fs_estimator) self.assertGreater(blind_rt60(x1, fs_sig), blind_rt60(x2, fs_sig)) + def test_rtx_calculation(self, decay_rate: float = 1, fs_sig: int = 8000, fs_estimator: int = 8000): + """ + Test the basic functionality of BlindRT60. + + Args: + fs_sig (int): Signal sampling frequency. + fs_estimator (int): Estimator sampling frequency. + decay_rate (float): decay rate of the chirp signal (dB) + """ + x = decaying_chirp(fs_sig, decay_rate=decay_rate) + blind_rt60 = BlindRT60(fs=fs_estimator) + rt60 = blind_rt60(x, fs_sig) + rt_60_calc = blind_rt60.calculate_rtx(60) + self.assertEqual(rt60, rt_60_calc) + @parameterized.expand([ param(rt60_tgt=0.3, max_err=0.25), param(rt60_tgt=0.8, max_err=0.25), From 8848e5846e08179aa8de5b6005c49e64eb34b752 Mon Sep 17 00:00:00 2001 From: zorea Date: Tue, 4 Jun 2024 20:25:57 +0300 Subject: [PATCH 4/4] running pre-commit --- .pre-commit-config.yaml | 16 +++++ blind_rt60/estimation.py | 140 +++++++++++++++++++++++++------------ blind_rt60/utils.py | 2 +- blind_rt60/version.py | 2 +- notebooks/blind_rt60.ipynb | 2 +- setup.py | 20 +++--- tests/test_blind_rt60.py | 88 +++++++++++++++-------- 7 files changed, 185 insertions(+), 85 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a867c72 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v17.0.6 + hooks: + - id: clang-format +# Using this mirror lets us use mypyc-compiled black, which is about 2x faster +- repo: https://github.com/psf/black-pre-commit-mirror + rev: 23.12.1 + hooks: + - id: black +- repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: + - --profile=black \ No newline at end of file diff --git a/blind_rt60/estimation.py b/blind_rt60/estimation.py index 83a2685..bed8340 100644 --- a/blind_rt60/estimation.py +++ b/blind_rt60/estimation.py @@ -9,19 +9,30 @@ # Constants FRAME_LENGTH = 200e-3 # Frame length in seconds -EPS = np.finfo('float').eps +EPS = np.finfo("float").eps class UpdateMethod: - NEWTON = 'newton' - BISECTED = 'bisected' + NEWTON = "newton" + BISECTED = "bisected" class BlindRT60: - def __init__(self, fs: int = 8000, framelen: Optional[float] = None, hop: Optional[float] = None, - percentile: int = 50., a_init: int = 0.99, sigma2_init: int = 0.5, max_itr: int = 1000, - max_err: float = 1e-1, a_range: tuple = (0.99, 0.999999999), bisected_itr: int = 8, - sigma2_range: tuple = (0., np.inf), verbose: bool = False): + def __init__( + self, + fs: int = 8000, + framelen: Optional[float] = None, + hop: Optional[float] = None, + percentile: int = 50.0, + a_init: int = 0.99, + sigma2_init: int = 0.5, + max_itr: int = 1000, + max_err: float = 1e-1, + a_range: tuple = (0.99, 0.999999999), + bisected_itr: int = 8, + sigma2_range: tuple = (0.0, np.inf), + verbose: bool = False, + ): """ Estimate the reverberation time (RT60) from the input signal. @@ -40,7 +51,9 @@ def __init__(self, fs: int = 8000, framelen: Optional[float] = None, hop: Option - sigma2_range: Range of valid values for 'sigma2' """ self.fs = fs - self.framelen = int(self.fs * FRAME_LENGTH) if framelen is None else int(self.fs * framelen) + self.framelen = ( + int(self.fs * FRAME_LENGTH) if framelen is None else int(self.fs * framelen) + ) self.hop = int(self.framelen) // 4 if hop is None else int(self.fs * hop) self.percentile = percentile self.a_init = a_init @@ -85,14 +98,17 @@ def sanity_check(self): """ Check the validity of input parameters. """ - assert 0. <= self.percentile <= 100., 'gamma should be between 0 to 100' - assert self.framelen > 0, f'sigma2 should be larger than 0' - assert 0. < self.hop <= self.framelen, 'hop must be between 0 to framelen' - assert self.a_range[0] <= self.a_init < self.a_range[ - 1], f'a should be between {self.a_range[0]} to {self.a_range[1]}' - assert self.sigma2_init > 0., f'sigma2 should be larger than 0' - - def likelihood_derivative(self, a: np.ndarray, x_frames: np.ndarray) -> (np.ndarray, np.ndarray, np.ndarray): + assert 0.0 <= self.percentile <= 100.0, "gamma should be between 0 to 100" + assert self.framelen > 0, f"sigma2 should be larger than 0" + assert 0.0 < self.hop <= self.framelen, "hop must be between 0 to framelen" + assert ( + self.a_range[0] <= self.a_init < self.a_range[1] + ), f"a should be between {self.a_range[0]} to {self.a_range[1]}" + assert self.sigma2_init > 0.0, f"sigma2 should be larger than 0" + + def likelihood_derivative( + self, a: np.ndarray, x_frames: np.ndarray + ) -> (np.ndarray, np.ndarray, np.ndarray): """ Calculate the first and second derivatives of the log-likelihood function with respect to 'a'. @@ -105,13 +121,23 @@ def likelihood_derivative(self, a: np.ndarray, x_frames: np.ndarray) -> (np.ndar - d2l_da2: Second derivative of the log-likelihood with respect to 'a' - sigma2: Estimated variance of the signal """ - a_x_prod = a ** (-2 * self.n) * x_frames ** 2 - sigma2 = np.clip(np.mean(a_x_prod, axis=1, keepdims=True), a_min=self.sigma2_range[0], - a_max=self.sigma2_range[1]) - dl_da = 1 / (a + EPS) * ( - 1 / (sigma2 + EPS) * np.sum(self.n * a_x_prod, axis=1, keepdims=True) - self.framelen_fac) - d2l_da2 = self.framelen_fac / (a ** 2 + EPS) + 1 / (sigma2 + EPS) * np.sum( - (1 - 2 * self.n) * self.n * a_x_prod, axis=1, keepdims=True) + a_x_prod = a ** (-2 * self.n) * x_frames**2 + sigma2 = np.clip( + np.mean(a_x_prod, axis=1, keepdims=True), + a_min=self.sigma2_range[0], + a_max=self.sigma2_range[1], + ) + dl_da = ( + 1 + / (a + EPS) + * ( + 1 / (sigma2 + EPS) * np.sum(self.n * a_x_prod, axis=1, keepdims=True) + - self.framelen_fac + ) + ) + d2l_da2 = self.framelen_fac / (a**2 + EPS) + 1 / (sigma2 + EPS) * np.sum( + (1 - 2 * self.n) * self.n * a_x_prod, axis=1, keepdims=True + ) return dl_da, d2l_da2, sigma2 def step(self, x_frames: np.ndarray, method: str) -> np.ndarray: @@ -148,7 +174,9 @@ def step(self, x_frames: np.ndarray, method: str) -> np.ndarray: self.a_lower[changed_sign] = middle_a[changed_sign] self.a_upper[not_changed_sign] = middle_a[not_changed_sign] else: - raise ValueError(f'method {method} should be {UpdateMethod.NEWTON} or {UpdateMethod.BISECTED}') + raise ValueError( + f"method {method} should be {UpdateMethod.NEWTON} or {UpdateMethod.BISECTED}" + ) self.a = np.clip(self.a, a_min=self.a_range[0], a_max=self.a_range[1]) dl_da, d2l_da2, self.sigma2 = self.likelihood_derivative(self.a, x_frames) @@ -176,31 +204,40 @@ def visualize(self, x: np.ndarray, fs: int, ylim: Tuple = (0, 1)) -> Figure: fig, axs = plt.subplots(nrows=1, ncols=2, width_ratios=(3, 1), sharey=True) # Plot the input signal normalized to its maximum value - axs[0].plot(np.linspace(0.0, x_duration, len(x)), abs(x) / np.max(np.abs(x - np.mean(x))), label='Signal', - color='black') - axs[0].set_xlabel('Time [sec]') - axs[0].set_ylabel('Samples') - axs[0].tick_params(axis='y', labelcolor='black') + axs[0].plot( + np.linspace(0.0, x_duration, len(x)), + abs(x) / np.max(np.abs(x - np.mean(x))), + label="Signal", + color="black", + ) + axs[0].set_xlabel("Time [sec]") + axs[0].set_ylabel("Samples") + axs[0].tick_params(axis="y", labelcolor="black") # Plot the estimated reverberation times for each frame axs0 = axs[0].twinx() - axs0.plot(np.linspace(0.0, x_duration, len(self.taus)), self.taus, label='Tau [sec]', color='c') - axs0.tick_params(axis='y', labelcolor='black') - axs0.set_ylabel('Time Constant [sec]') + axs0.plot( + np.linspace(0.0, x_duration, len(self.taus)), + self.taus, + label="Tau [sec]", + color="c", + ) + axs0.tick_params(axis="y", labelcolor="black") + axs0.set_ylabel("Time Constant [sec]") # Create a histogram of taus bins = np.arange(ylim[0], ylim[1], 0.05) - axs[1].hist(self.taus, bins=bins, orientation='horizontal', color='c') - axs[1].axhline(self.tau, xmin=0.0, color='black') - axs[1].text(2, self.tau + 0.05, f'Tau {self.tau:.2f} sec', color='black') - axs[1].set_xlabel('Counts') + axs[1].hist(self.taus, bins=bins, orientation="horizontal", color="c") + axs[1].axhline(self.tau, xmin=0.0, color="black") + axs[1].text(2, self.tau + 0.05, f"Tau {self.tau:.2f} sec", color="black") + axs[1].set_xlabel("Counts") # Set y-axis limits for all subplots for ax in [axs[0], axs[1], axs0]: ax.set_ylim(ylim) # Add a title to the entire visualization - plt.suptitle(f'Blind RT60 Estimation | RT60 {self.rt60:.2f} sec') + plt.suptitle(f"Blind RT60 Estimation | RT60 {self.rt60:.2f} sec") fig.tight_layout() return fig @@ -224,13 +261,28 @@ def estimate(self, x: np.ndarray, fs: int) -> float: self.sanity_check() assert np.ndim(x) == 1 - x = sig.decimate(x, int(fs // self.fs)) if fs > self.fs else sig.resample(x, int(len(x) * self.fs / fs)) - x_frames = np.array([x[i:i + self.framelen] for i in range(0, len(x) - self.framelen + 1, self.hop)]) + x = ( + sig.decimate(x, int(fs // self.fs)) + if fs > self.fs + else sig.resample(x, int(len(x) * self.fs / fs)) + ) + x_frames = np.array( + [ + x[i : i + self.framelen] + for i in range(0, len(x) - self.framelen + 1, self.hop) + ] + ) self.init_states(x_frames.shape[0]) itr = 0 - while itr < self.bisected_itr or (itr < self.max_itr and np.any(np.bitwise_not(self.converged))): - method = UpdateMethod.BISECTED if itr < self.bisected_itr else UpdateMethod.NEWTON + while itr < self.bisected_itr or ( + itr < self.max_itr and np.any(np.bitwise_not(self.converged)) + ): + method = ( + UpdateMethod.BISECTED + if itr < self.bisected_itr + else UpdateMethod.NEWTON + ) dl_da = self.step(x_frames, method=method) self.converged = np.abs(dl_da) <= self.max_err itr += 1 @@ -238,10 +290,12 @@ def estimate(self, x: np.ndarray, fs: int) -> float: self.taus = -1 / np.log(self.a) / self.fs self.taus[np.bitwise_not(self.converged)] = np.nan self.tau = np.percentile(self.taus[self.converged], q=self.percentile) - self.rt60 = -3 * self.tau / np.log10(np.e ** -1) + self.rt60 = -3 * self.tau / np.log10(np.e**-1) if self.verbose: - print(f'Iteration {itr} / {self.max_itr}; rt60 {self.rt60:.2f} sec; tau {self.tau:.2f} sec') + print( + f"Iteration {itr} / {self.max_itr}; rt60 {self.rt60:.2f} sec; tau {self.tau:.2f} sec" + ) return self.rt60 diff --git a/blind_rt60/utils.py b/blind_rt60/utils.py index daaac38..c4323f3 100644 --- a/blind_rt60/utils.py +++ b/blind_rt60/utils.py @@ -24,5 +24,5 @@ def calculate_decay_time(decay_db: float, tau: float) -> float: if not np.isfinite(decay_db): raise ValueError("decay_db must be a finite number (not NaN or Inf).") - decay_time = -decay_db / (20 * np.log10(np.e ** -1)) * tau + decay_time = -decay_db / (20 * np.log10(np.e**-1)) * tau return decay_time diff --git a/blind_rt60/version.py b/blind_rt60/version.py index 4c56dce..d27a0c3 100644 --- a/blind_rt60/version.py +++ b/blind_rt60/version.py @@ -1,2 +1,2 @@ # blind_rt60 version -__version__ = '0.1.1' \ No newline at end of file +__version__ = "0.1.1" diff --git a/notebooks/blind_rt60.ipynb b/notebooks/blind_rt60.ipynb index c7481af..93e9ce9 100644 --- a/notebooks/blind_rt60.ipynb +++ b/notebooks/blind_rt60.ipynb @@ -1,5 +1,5 @@ { - "cells": [ + "cells": [ { "cell_type": "code", "execution_count": 1, diff --git a/setup.py b/setup.py index 58b9576..fa94077 100644 --- a/setup.py +++ b/setup.py @@ -5,29 +5,27 @@ setuptools.setup( name="blind_rt60", - version="0.1.0-a0", + version="0.1.1", author="Asaf Zorea", author_email="zoreasaf@gmail.com", description="The BlindRT60 algorithm is used to estimate the reverberation time (RT60) " - "of a room based on the recorded audio signals from microphones", + "of a room based on the recorded audio signals from microphones", long_description=long_description, long_description_content_type="text/markdown", - license='MIT', + license="MIT", url="https://github.com/nuniz/blind_rt60", - packages=setuptools.find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*", "tests.*"]), + packages=setuptools.find_packages( + exclude=["tests", "*.tests", "*.tests.*", "tests.*", "tests.*"] + ), include_package_data=True, classifiers=[ "Programming Language :: Python :: 3", - 'License :: OSI Approved :: MIT License', + "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], python_requires=">=3.6", - install_requires=[ - "scipy", - "numpy", - "matplotlib" - ], + install_requires=["scipy", "numpy", "matplotlib"], extras_require={ - "dev": ["pyroomacoustics", "parameterized"], + "dev": ["pyroomacoustics", "parameterized", "pre-commit"], }, ) diff --git a/tests/test_blind_rt60.py b/tests/test_blind_rt60.py index 8d8a4d5..e303020 100644 --- a/tests/test_blind_rt60.py +++ b/tests/test_blind_rt60.py @@ -4,14 +4,19 @@ import numpy as np import pyroomacoustics as pra -from parameterized import parameterized, param +from parameterized import param, parameterized from scipy.io import wavfile from blind_rt60 import BlindRT60 -def decaying_chirp(fs, duration: float = 5.0, frequency_start: float = 250.0, frequency_end: float = 1000.0, - decay_rate: float = 10) -> np.ndarray: +def decaying_chirp( + fs, + duration: float = 5.0, + frequency_start: float = 250.0, + frequency_end: float = 1000.0, + decay_rate: float = 10, +) -> np.ndarray: """ Generate a decaying chirp signal. @@ -28,7 +33,9 @@ def decaying_chirp(fs, duration: float = 5.0, frequency_start: float = 250.0, fr t = np.linspace(0, duration, int(duration * fs)) # Create a chirp signal - chirp_signal = np.sin(2 * np.pi * np.linspace(frequency_start, frequency_end, len(t)) * t) + chirp_signal = np.sin( + 2 * np.pi * np.linspace(frequency_start, frequency_end, len(t)) * t + ) # Apply decay envelope decaying_chirp_signal = chirp_signal * np.exp(-decay_rate * t) @@ -41,10 +48,12 @@ class TestRT60(unittest.TestCase): Test cases for the BlindRT60 class. """ - @parameterized.expand([ - param(fs_sig=16000, fs_estimator=8000), - param(fs_sig=4000, fs_estimator=8000), - ]) + @parameterized.expand( + [ + param(fs_sig=16000, fs_estimator=8000), + param(fs_sig=4000, fs_estimator=8000), + ] + ) def test_decimate(self, fs_sig: int = 16000, fs_estimator: int = 8000): """ Test the decimate or resample methods. @@ -59,13 +68,20 @@ def test_decimate(self, fs_sig: int = 16000, fs_estimator: int = 8000): self.assertTrue(isinstance(rt60, float)) self.assertLess(rt60, 1) - @parameterized.expand([ - param(decay_rate_smaller=1, decay_rate_larger=2), - param(decay_rate_smaller=5, decay_rate_larger=6), - param(decay_rate_smaller=10, decay_rate_larger=11), - ]) - def test_sanity(self, decay_rate_smaller: float, decay_rate_larger: float, fs_sig: int = 8000, - fs_estimator: int = 8000): + @parameterized.expand( + [ + param(decay_rate_smaller=1, decay_rate_larger=2), + param(decay_rate_smaller=5, decay_rate_larger=6), + param(decay_rate_smaller=10, decay_rate_larger=11), + ] + ) + def test_sanity( + self, + decay_rate_smaller: float, + decay_rate_larger: float, + fs_sig: int = 8000, + fs_estimator: int = 8000, + ): """ Test the basic functionality of BlindRT60. @@ -78,7 +94,9 @@ def test_sanity(self, decay_rate_smaller: float, decay_rate_larger: float, fs_si blind_rt60 = BlindRT60(fs=fs_estimator) self.assertGreater(blind_rt60(x1, fs_sig), blind_rt60(x2, fs_sig)) - def test_rtx_calculation(self, decay_rate: float = 1, fs_sig: int = 8000, fs_estimator: int = 8000): + def test_rtx_calculation( + self, decay_rate: float = 1, fs_sig: int = 8000, fs_estimator: int = 8000 + ): """ Test the basic functionality of BlindRT60. @@ -93,12 +111,19 @@ def test_rtx_calculation(self, decay_rate: float = 1, fs_sig: int = 8000, fs_est rt_60_calc = blind_rt60.calculate_rtx(60) self.assertEqual(rt60, rt_60_calc) - @parameterized.expand([ - param(rt60_tgt=0.3, max_err=0.25), - param(rt60_tgt=0.8, max_err=0.25), - param(rt60_tgt=1.2, max_err=0.25), - ]) - def test_functionality(self, rt60_tgt: float, max_err: float, path: str = r"supplementary_material/data/sp09.wav"): + @parameterized.expand( + [ + param(rt60_tgt=0.3, max_err=0.25), + param(rt60_tgt=0.8, max_err=0.25), + param(rt60_tgt=1.2, max_err=0.25), + ] + ) + def test_functionality( + self, + rt60_tgt: float, + max_err: float, + path: str = r"supplementary_material/data/sp09.wav", + ): """ Test the functionality of BlindRT60. Each parameter set represents a test case with different target RT60 values. @@ -118,13 +143,15 @@ def test_functionality(self, rt60_tgt: float, max_err: float, path: str = r"supp fs, audio = wavfile.read(path) except Exception as e: warnings.warn(str(e)) - fs, audio = wavfile.read(os.path.join('..', path)) + fs, audio = wavfile.read(os.path.join("..", path)) self.assertEqual(np.ndim(audio), 1) # Create the room room_dim = [10, 7.5, 3.5] # meters - e_absorption, max_order = pra.inverse_sabine(rt60_tgt, room_dim) # Invert Sabine's formula, ISM simulator + e_absorption, max_order = pra.inverse_sabine( + rt60_tgt, room_dim + ) # Invert Sabine's formula, ISM simulator room = pra.ShoeBox( room_dim, fs=fs, materials=pra.Material(e_absorption), max_order=max_order @@ -135,7 +162,8 @@ def test_functionality(self, rt60_tgt: float, max_err: float, path: str = r"supp # Define the locations of the microphones mic_locs = np.c_[ - [6.3, 4.87, 1.2], [6.3, 4.93, 1.2], # mic 1 # mic 2 + [6.3, 4.87, 1.2], + [6.3, 4.93, 1.2], # mic 1 # mic 2 ] room.add_microphone_array(mic_locs) @@ -147,13 +175,17 @@ def test_functionality(self, rt60_tgt: float, max_err: float, path: str = r"supp # Compute Blind RT60 blind_rt60 = BlindRT60() - rt60_estimations = np.array([blind_rt60(room.mic_array.signals[i, ...], fs) - for i in range(mic_locs.shape[-1])]) + rt60_estimations = np.array( + [ + blind_rt60(room.mic_array.signals[i, ...], fs) + for i in range(mic_locs.shape[-1]) + ] + ) # Calculate absolute error between BlindRT60 estimation and RT60 err = np.max(np.abs(rt60_estimations - rt60_schroeder)) self.assertLessEqual(err, max_err) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main()