From 99a29f21aea76c14581d0f7ca310d7eafac77dc5 Mon Sep 17 00:00:00 2001 From: Robin Scheibler Date: Sun, 8 Dec 2024 01:21:49 +0900 Subject: [PATCH 1/3] Fixes a bug where the single/multi-band materials is not properly handled when using from_corners together with extrude. (issue #381) --- pyroomacoustics/room.py | 58 ++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/pyroomacoustics/room.py b/pyroomacoustics/room.py index e9620347..d299d7a1 100644 --- a/pyroomacoustics/room.py +++ b/pyroomacoustics/room.py @@ -1395,9 +1395,11 @@ def extrude(self, height, v_vec=None, absorption=None, materials=None): if libroom.area_2d_polygon(floor_corners) <= 0: floor_corners = np.fliplr(floor_corners) - walls = [] + wall_corners = {} + wall_materials = {} for i in range(nw): - corners = np.array( + name = str(i) + wall_corners[name] = np.array( [ np.r_[floor_corners[:, i], 0], np.r_[floor_corners[:, (i + 1) % nw], 0], @@ -1405,14 +1407,33 @@ def extrude(self, height, v_vec=None, absorption=None, materials=None): np.r_[floor_corners[:, i], 0] + height * v_vec, ] ).T - walls.append( - wall_factory( - corners, - self.walls[i].absorption, - self.walls[i].scatter, - name=str(i), + + if len(self.walls[i].absorption) == 1: + # Single band + wall_materials[name] = Material( + energy_absorption=float(self.walls[i].absorption), + scattering=float(self.walls[i].scatter), + ) + elif len(self.walls[i].absorption) == self.octave_bands.n_bands: + # Multi-band + abs_dict = { + "coeffs": self.walls[i].absorption, + "center_freqs": self.octave_bands.centers, + "description": "", + } + sca_dict = { + "coeffs": self.walls[i].scatter, + "center_freqs": self.octave_bands.centers, + "description": "", + } + wall_materials[name] = Material( + energy_absorption=abs_dict, + scattering=sca_dict, + ) + else: + raise ValueError( + "Encountered a material with inconsistent number of bands." ) - ) ############################ # BEGIN COMPATIBILITY CODE # @@ -1477,12 +1498,23 @@ def extrude(self, height, v_vec=None, absorption=None, materials=None): # we need the floor corners to ordered clockwise (for the normal to point outward) new_corners["floor"] = np.fliplr(new_corners["floor"]) - for key in ["floor", "ceiling"]: + # Concatenate new walls param with old ones. + wall_corners.update(new_corners) + wall_materials.update(materials) + + # If some of the materials used are multi-band, we need to resample + # all of them to have the same number of values + if not Material.all_flat(wall_materials): + for name, mat in wall_materials.items(): + mat.resample(self.octave_bands) + + walls = [] + for key, corners in wall_corners.items(): walls.append( wall_factory( - new_corners[key], - materials[key].absorption_coeffs, - materials[key].scattering_coeffs, + corners, + wall_materials[key].absorption_coeffs, + wall_materials[key].scattering_coeffs, name=key, ) ) From 14d60716d05cb2091171830519c34f68268e648a Mon Sep 17 00:00:00 2001 From: Robin Scheibler Date: Sun, 8 Dec 2024 01:41:10 +0900 Subject: [PATCH 2/3] Adds a test for #381. --- .../tests/test_from_corners_extrude.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 pyroomacoustics/tests/test_from_corners_extrude.py diff --git a/pyroomacoustics/tests/test_from_corners_extrude.py b/pyroomacoustics/tests/test_from_corners_extrude.py new file mode 100644 index 00000000..a816f062 --- /dev/null +++ b/pyroomacoustics/tests/test_from_corners_extrude.py @@ -0,0 +1,68 @@ +import numpy as np +import pytest + +import pyroomacoustics as pra + +_FS = 16000 +_MAX_ORDER = 3 + + +def test_from_corner_extrude(): + + room_dim = [3, 4, 5] + # This test is sensitive to the selection of this value. + # If some symmetries are present, for some reasons differences between + # the two simulated methods happen. + src_loc = [1.001, 0.999, 1.002] + mic_loc = [2, 3, 4] + mat = pra.Material(energy_absorption=0.1) + + room_ref = pra.ShoeBox(room_dim, fs=_FS, max_order=_MAX_ORDER, materials=mat) + room_ref.add_source(src_loc).add_microphone(mic_loc) + room_ref.compute_rir() + + # Now construct the same room with the other set of primitives. + corners = np.array( + [[0, 0], [room_dim[0], 0], [room_dim[0], room_dim[1]], [0, room_dim[1]]] + ).T + room = pra.Room.from_corners(corners, fs=_FS, max_order=_MAX_ORDER, materials=mat) + room.extrude(height=room_dim[2], materials=mat) + room.add_source(src_loc).add_microphone(mic_loc) + room.compute_rir() + + assert np.allclose(room_ref.rir[0][0], room.rir[0][0], rtol=1e-4, atol=1e-4) + + +def test_from_corner_extrude_different_materials(): + + room_dim = [3, 4, 5] + # This test is sensitive to the selection of this value. + # If some symmetries are present, for some reasons differences between + # the two simulated methods happen. + src_loc = [1.001, 0.999, 1.002] + mic_loc = [2, 3, 4] + mat1 = "hard_surface" + mat2 = 0.1 + + materials = pra.make_materials( + east=mat1, west=mat1, south=mat1, north=mat1, floor=mat2, ceiling=mat2 + ) + room_ref = pra.ShoeBox(room_dim, fs=_FS, max_order=_MAX_ORDER, materials=materials) + room_ref.add_source(src_loc).add_microphone(mic_loc) + room_ref.compute_rir() + + # Now construct the same room with the other set of primitives. + corners = np.array( + [[0, 0], [room_dim[0], 0], [room_dim[0], room_dim[1]], [0, room_dim[1]]] + ).T + room = pra.Room.from_corners( + corners, + fs=_FS, + max_order=_MAX_ORDER, + materials=pra.Material(energy_absorption=mat1), + ) + room.extrude(height=room_dim[2], materials=pra.Material(energy_absorption=mat2)) + room.add_source(src_loc).add_microphone(mic_loc) + room.compute_rir() + + assert np.allclose(room_ref.rir[0][0], room.rir[0][0], rtol=1e-4, atol=1e-4) From 4d5f844f25976c24283f65c5cc862aa6ab8488e8 Mon Sep 17 00:00:00 2001 From: Robin Scheibler Date: Sun, 8 Dec 2024 01:44:32 +0900 Subject: [PATCH 3/3] CHANGELOG --- CHANGELOG.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f9793536..759cbe16 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,6 +18,12 @@ Bugfix directivity to ``Room.add_microphone_array``, the directivity was dropped from the object. +- Fixes issues #381: When creating a room with from_corners with multi-band + material, and then making it 3D with ``extrude`` with a single band material + for the floor and ceiling, an error would occur. + After the fix, the materials for floor and ceiling are automatically extended + to multi-band, as expected. + - Fixes issue #380: Caused by the attribute ``cartesian`` of ``GridSphere`` not being set properly when the grid is only initialized with a number of points. @@ -30,7 +36,7 @@ Bugfix Changed ~~~~~~~ -- Makes the ``pyroomacoustics.utilities.resample`` backend is made configurable +- Makes the ``pyroomacoustics.utilities.resample`` backend configurable to avoid ``soxr`` dependency. The resample backend is configurable to ``soxr``, ``samplerate``, if these packages are available, and otherwise falls back to ``scipy.signal.resample_poly``.