diff --git a/audiomentations/augmentations/air_absorption.py b/audiomentations/augmentations/air_absorption.py index 4780650b..d3e367dd 100644 --- a/audiomentations/augmentations/air_absorption.py +++ b/audiomentations/augmentations/air_absorption.py @@ -13,6 +13,16 @@ def next_power_of_2(x: int) -> int: return 1 if x == 0 else 2 ** (x - 1).bit_length() +def get_temperature_humidity_key(temperature: float, humidity: float) -> str: + key = str(int(temperature)) + "C" + bounds = [30, 50, 70, 90] + for n in range(1, len(bounds)): + if bounds[n - 1] <= humidity <= bounds[n]: + key += f"_{bounds[n-1]}-{bounds[n]}%" + break + return key + + class AirAbsorption(BaseWaveformTransform): """ Apply a Lowpass-like filterbank with variable octave attenuation that simulates attenuation of @@ -28,7 +38,7 @@ class AirAbsorption(BaseWaveformTransform): is adapted from a lookup table by pyroomacoustics [1]. It can also be seen as a lowpass filter with variable octave attenuation. - Note: This only "simulates" the dampening of high frequencies, and does not + Note: This only "simulates" the damping of high frequencies, and does not attenuate according to the distance law. Gain augmentation needs to be done separately. [1] https://github.com/LCAV/pyroomacoustics @@ -122,23 +132,19 @@ def randomize_parameters(self, samples: NDArray[np.float32], sample_rate: int): self.min_distance, self.max_distance ) - def apply(self, samples: NDArray[np.float32], sample_rate: int) -> NDArray[np.float32]: + def apply( + self, samples: NDArray[np.float32], sample_rate: int + ) -> NDArray[np.float32]: assert samples.dtype == np.float32 - humidity = self.parameters["humidity"] - distance = self.parameters["distance"] - # Choose correct absorption coefficients - key = str(int(self.parameters["temperature"])) + "C" - bounds = [30, 50, 70, 90] - for n in range(1, len(bounds)): - if bounds[n - 1] <= humidity or humidity <= bounds[n]: - key += f"_{bounds[n-1]}-{bounds[n]}%" - break + key = get_temperature_humidity_key( + self.parameters["temperature"], self.parameters["humidity"] + ) # Convert to attenuations attenuation_values = np.exp( - -distance * np.array(self.air_absorption_table[key]) + -self.parameters["distance"] * np.array(self.air_absorption_table[key]) ) # Calculate n_fft so that the lowest band can be stored in a single diff --git a/docs/waveform_transforms/air_absorption.md b/docs/waveform_transforms/air_absorption.md index 5eee905b..9c955b10 100644 --- a/docs/waveform_transforms/air_absorption.md +++ b/docs/waveform_transforms/air_absorption.md @@ -18,7 +18,7 @@ It can also be seen as a lowpass filter with variable octave attenuation. Note that since this transform mostly affects high frequencies, it is only suitable for audio with sufficiently high sample rate, like 32 kHz and above. -Note also that this transform only "simulates" the dampening of high frequencies, and +Note also that this transform only "simulates" the damping of high frequencies, and does not attenuate according to the distance law. Gain augmentation needs to be done separately. diff --git a/tests/test_air_absorption.py b/tests/test_air_absorption.py index 2f368029..f071f5e1 100644 --- a/tests/test_air_absorption.py +++ b/tests/test_air_absorption.py @@ -1,3 +1,5 @@ +from audiomentations.augmentations.air_absorption import get_temperature_humidity_key + DEBUG = False from audiomentations import AirAbsorption @@ -14,32 +16,39 @@ def get_chirp_test(sample_rate, duration): return samples.astype(np.float32) -class TestAirAbsorptionTransform: - @pytest.mark.parametrize("temperature", [10, 20]) - @pytest.mark.parametrize("humidity", [30, 50, 70, 90]) - @pytest.mark.parametrize("distance", [5, 10, 20, 100]) - @pytest.mark.parametrize("sample_rate", [8000, 16000, 48000]) - def test_input_shapes(self, temperature, humidity, distance, sample_rate): - np.random.seed(1) - - samples = get_chirp_test(sample_rate, 10) - - augment = AirAbsorption( - min_temperature=temperature, - max_temperature=temperature, - min_humidity=humidity, - max_humidity=humidity, - min_distance=distance, - max_distance=distance, - ) - - # Test 1D case - processed_samples = augment(samples, sample_rate=sample_rate) - assert processed_samples.shape == samples.shape - assert processed_samples.dtype == np.float32 - - # Test 2D case - samples = np.tile(samples, (2, 1)) - processed_samples = augment(samples, sample_rate=sample_rate) - assert processed_samples.shape == samples.shape - assert processed_samples.dtype == np.float32 +@pytest.mark.parametrize("temperature", [10, 20]) +@pytest.mark.parametrize("humidity", [30, 50, 70, 90]) +@pytest.mark.parametrize("distance", [5, 10, 20, 100]) +@pytest.mark.parametrize("sample_rate", [8000, 16000, 48000]) +def test_input_shapes(temperature, humidity, distance, sample_rate): + np.random.seed(1) + + samples = get_chirp_test(sample_rate, 10) + + augment = AirAbsorption( + min_temperature=temperature, + max_temperature=temperature, + min_humidity=humidity, + max_humidity=humidity, + min_distance=distance, + max_distance=distance, + ) + + # Test 1D case + processed_samples = augment(samples, sample_rate=sample_rate) + assert processed_samples.shape == samples.shape + assert processed_samples.dtype == np.float32 + + # Test 2D case + samples = np.tile(samples, (2, 1)) + processed_samples = augment(samples, sample_rate=sample_rate) + assert processed_samples.shape == samples.shape + assert processed_samples.dtype == np.float32 + +def test_get_temperature_humidity_key(): + assert get_temperature_humidity_key(10.0, 30.0) == "10C_30-50%" + assert get_temperature_humidity_key(10.0, 45.0) == "10C_30-50%" + assert get_temperature_humidity_key(10.0, 50.0) == "10C_30-50%" + assert get_temperature_humidity_key(10.0, 51.0) == "10C_50-70%" + assert get_temperature_humidity_key(10.0, 65.0) == "10C_50-70%" + assert get_temperature_humidity_key(20.0, 90.0) == "20C_70-90%"