From de3e168e40c13a7c0aa6a5c7a3268debb36e7bae Mon Sep 17 00:00:00 2001 From: Robin Scheibler Date: Sat, 7 Dec 2024 20:10:45 +0900 Subject: [PATCH 1/2] Fixes issue #382: Room.add_microphone_array discards MicrophoneArray directivity. --- pyroomacoustics/room.py | 15 +++++++-- pyroomacoustics/tests/test_room_add.py | 43 +++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/pyroomacoustics/room.py b/pyroomacoustics/room.py index 329842d2..e9620347 100644 --- a/pyroomacoustics/room.py +++ b/pyroomacoustics/room.py @@ -1980,7 +1980,7 @@ def add(self, obj): ).format(self.dim, obj.dim) ) - if "mic_array" not in self.__dict__ or self.mic_array is None: + if not hasattr(self, "mic_array") or self.mic_array is None: self.mic_array = obj else: self.mic_array.append(obj) @@ -2046,6 +2046,12 @@ def add_microphone_array(self, mic_array, directivity=None): As an alternative, a :py:obj:`~pyroomacoustics.beamforming.MicrophoneArray` can be provided. + directivity: list of Directivity objects, optional + If ``mic_array`` is provided as a numpy array, an optional + :py:obj:`~pyroomacoustics.directivities.Directivity` object or + list thereof can be provided. + If ``mic_array`` is a MicrophoneArray object, passing an argument here + will result in an error. Returns ------- @@ -2064,7 +2070,12 @@ def add_microphone_array(self, mic_array, directivity=None): mic_array = MicrophoneArray(mic_array, self.fs, directivity) else: # if the type is microphone array - mic_array.set_directivity(directivity) + if directivity is not None: + raise ValueError( + "When providing a MicrophoneArray object, the directivities should " + "be provided in the object, not via the `directivity` parameter " + "of this method." + ) if self.simulator_state["rt_needed"] and mic_array.is_directive: raise NotImplementedError("Directivity not supported with ray tracing.") diff --git a/pyroomacoustics/tests/test_room_add.py b/pyroomacoustics/tests/test_room_add.py index 9826196c..98599cdf 100644 --- a/pyroomacoustics/tests/test_room_add.py +++ b/pyroomacoustics/tests/test_room_add.py @@ -1,4 +1,5 @@ import numpy as np +import pytest import pyroomacoustics as pra @@ -8,6 +9,18 @@ source_loc1 = [3.5, 7.7, 2.1] mic0 = [7, 8, 3.9] mic1 = [7.87, 3.6, 6.1] +mic_dir0 = pra.FigureEight( + orientation=pra.DirectionVector(azimuth=90, colatitude=15, degrees=True) +) +mic_dir1 = pra.FigureEight( + orientation=pra.DirectionVector(azimuth=180, colatitude=15, degrees=True) +) +src_dir0 = pra.FigureEight( + orientation=pra.DirectionVector(azimuth=270, colatitude=15, degrees=True) +) +src_dir1 = pra.FigureEight( + orientation=pra.DirectionVector(azimuth=0, colatitude=15, degrees=True) +) def test_add_source_mic(): @@ -56,12 +69,21 @@ def test_add_source_mic_obj(): assert room.mic_array.R.shape == (3, 2) -def test_add_source_mic_obj_2(): +@pytest.mark.parametrize("with_dir", ((True,), (False,))) +def test_add_source_mic_obj_2(with_dir): room = pra.ShoeBox(room_size) - source0 = pra.SoundSource(source_loc0, signal=sig) - source1 = pra.SoundSource(source_loc1, signal=sig) - mic_array = pra.MicrophoneArray(np.c_[mic0, mic1], fs=room.fs) + if with_dir: + sdir0 = src_dir0 + sdir1 = src_dir1 + mdir = [mic_dir0, mic_dir1] + else: + sdir0 = sdir1 = None + mdir = [None, None] + + source0 = pra.SoundSource(source_loc0, signal=sig, directivity=sdir0) + source1 = pra.SoundSource(source_loc1, signal=sig, directivity=sdir1) + mic_array = pra.MicrophoneArray(np.c_[mic0, mic1], fs=room.fs, directivity=mdir) room.add(source0).add(source1).add(mic_array) @@ -72,6 +94,19 @@ def test_add_source_mic_obj_2(): assert np.allclose(room.mic_array.R[:, 0], mic0) assert np.allclose(room.mic_array.R[:, 1], mic1) assert room.mic_array.R.shape == (3, 2) + # Test directivities. + assert room.sources[0].directivity is sdir0 + assert room.sources[1].directivity is sdir1 + assert all(d is md for d, md in zip(room.mic_array.directivity, mdir)) + + +def test_add_source_mic_obj_with_dir_error(): + room = pra.ShoeBox(room_size) + + mic_array = pra.MicrophoneArray(np.c_[mic0, mic1], fs=room.fs) + + with pytest.raises(ValueError): + room.add_microphone_array(mic_array, directivity=[mic_dir0, mic_dir1]) def test_add_source_mic_ndarray(): From 6a7397b2bccda09620e239a3c908c27f2168f20a Mon Sep 17 00:00:00 2001 From: Robin Scheibler Date: Sat, 7 Dec 2024 22:07:42 +0900 Subject: [PATCH 2/2] Tests and changelog --- CHANGELOG.rst | 7 +- pyroomacoustics/tests/test_room_add.py | 108 +++++++++++++++++++++---- 2 files changed, 98 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a2b8d305..a1063ca2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,7 +11,12 @@ adheres to `Semantic Versioning `_. `Unreleased`_ ------------- -Nothing yet +Bugfix +~~~~~~ + +- Fixes issue #382: When providing a ``MicrophoneArray`` object with + directivity to ``Room.add_microphone_array``, the directivity was dropped + from the object. `0.8.2`_ - 2024-11-06 --------------------- diff --git a/pyroomacoustics/tests/test_room_add.py b/pyroomacoustics/tests/test_room_add.py index 98599cdf..c56a2796 100644 --- a/pyroomacoustics/tests/test_room_add.py +++ b/pyroomacoustics/tests/test_room_add.py @@ -23,16 +23,37 @@ ) -def test_add_source_mic(): - room = pra.ShoeBox(room_size).add_source(source_loc0).add_microphone(mic0) +@pytest.mark.parametrize("with_dir", ((True,), (False,))) +def test_add_source_mic(with_dir): + room = pra.ShoeBox(room_size) + + if with_dir: + sdir0 = src_dir0 + sdir1 = src_dir1 + mdir0 = mic_dir0 + mdir1 = mic_dir1 + else: + sdir0 = sdir1 = None + mdir0 = mdir1 = None + + room = ( + pra.ShoeBox(room_size) + .add_source(source_loc0, directivity=sdir0) + .add_microphone(mic0, directivity=mdir0) + ) assert len(room.sources) == 1 assert np.allclose(room.sources[0].position, source_loc0) assert len(room.mic_array) == 1 assert room.mic_array.R.shape == (3, 1) assert np.allclose(room.mic_array.R[:, 0], mic0) + # Test directivities. + assert room.sources[0].directivity is sdir0 + assert all(d is md for d, md in zip(room.mic_array.directivity, [mdir0])) - room.add_microphone(mic1).add_source(source_loc1) + room.add_microphone(mic1, directivity=mdir1).add_source( + source_loc1, directivity=sdir1 + ) assert len(room.sources) == 2 assert np.allclose(room.sources[1].position, source_loc1) @@ -40,16 +61,30 @@ def test_add_source_mic(): assert np.allclose(room.mic_array.R[:, 0], mic0) assert np.allclose(room.mic_array.R[:, 1], mic1) assert room.mic_array.R.shape == (3, 2) + # Test directivities. + assert room.sources[0].directivity is sdir0 + assert room.sources[1].directivity is sdir1 + assert all(d is md for d, md in zip(room.mic_array.directivity, [mdir0, mdir1])) -def test_add_source_mic_obj(): +@pytest.mark.parametrize("with_dir", ((True,), (False,))) +def test_add_source_mic_obj(with_dir): room = pra.ShoeBox(room_size) - source0 = pra.SoundSource(source_loc0, signal=sig) - source1 = pra.SoundSource(source_loc1, signal=sig) + if with_dir: + sdir0 = src_dir0 + sdir1 = src_dir1 + mdir0 = mic_dir0 + mdir1 = mic_dir1 + else: + sdir0 = sdir1 = None + mdir0 = mdir1 = None + + source0 = pra.SoundSource(source_loc0, signal=sig, directivity=sdir0) + source1 = pra.SoundSource(source_loc1, signal=sig, directivity=sdir1) - mic_array0 = pra.MicrophoneArray(np.c_[mic0], fs=room.fs) - mic_array1 = pra.MicrophoneArray(np.c_[mic1], fs=room.fs) + mic_array0 = pra.MicrophoneArray(np.c_[mic0], fs=room.fs, directivity=mdir0) + mic_array1 = pra.MicrophoneArray(np.c_[mic1], fs=room.fs, directivity=mdir1) room.add(source0).add(mic_array0) @@ -58,6 +93,9 @@ def test_add_source_mic_obj(): assert len(room.mic_array) == 1 assert room.mic_array.R.shape == (3, 1) assert np.allclose(room.mic_array.R[:, 0], mic0) + # Test directivities. + assert room.sources[0].directivity is sdir0 + assert all(d is md for d, md in zip(room.mic_array.directivity, [mdir0])) room.add(mic_array1).add(source1) @@ -67,6 +105,10 @@ def test_add_source_mic_obj(): assert np.allclose(room.mic_array.R[:, 0], mic0) assert np.allclose(room.mic_array.R[:, 1], mic1) assert room.mic_array.R.shape == (3, 2) + # Test directivities. + assert room.sources[0].directivity is sdir0 + assert room.sources[1].directivity is sdir1 + assert all(d is md for d, md in zip(room.mic_array.directivity, [mdir0, mdir1])) @pytest.mark.parametrize("with_dir", ((True,), (False,))) @@ -109,13 +151,25 @@ def test_add_source_mic_obj_with_dir_error(): room.add_microphone_array(mic_array, directivity=[mic_dir0, mic_dir1]) -def test_add_source_mic_ndarray(): - source0 = pra.SoundSource(source_loc0, signal=sig) - source1 = pra.SoundSource(source_loc1, signal=sig) +@pytest.mark.parametrize("with_dir", ((True,), (False,))) +def test_add_source_mic_ndarray(with_dir): + if with_dir: + sdir0 = src_dir0 + sdir1 = src_dir1 + mdir = [mic_dir0, mic_dir1] + else: + sdir0 = sdir1 = None + mdir = [None, None] + + source0 = pra.SoundSource(source_loc0, signal=sig, directivity=sdir0) + source1 = pra.SoundSource(source_loc1, signal=sig, directivity=sdir1) mic_array = np.c_[mic0, mic1] room = ( - pra.ShoeBox(room_size).add(source0).add(source1).add_microphone_array(mic_array) + pra.ShoeBox(room_size) + .add(source0) + .add(source1) + .add_microphone_array(mic_array, directivity=mdir) ) assert len(room.sources) == 2 @@ -125,14 +179,32 @@ def test_add_source_mic_ndarray(): assert np.allclose(room.mic_array.R[:, 0], mic0) assert np.allclose(room.mic_array.R[:, 1], mic1) assert room.mic_array.R.shape == (3, 2) + # Test directivities. + assert room.sources[0].directivity is sdir0 + assert room.sources[1].directivity is sdir1 + assert all(d is md for d, md in zip(room.mic_array.directivity, mdir)) -def test_add_source_mic_ndarray_2(): - source0 = pra.SoundSource(source_loc0, signal=sig) - source1 = pra.SoundSource(source_loc1, signal=sig) +@pytest.mark.parametrize("with_dir", ((True,), (False,))) +def test_add_source_mic_ndarray_2(with_dir): + if with_dir: + sdir0 = src_dir0 + sdir1 = src_dir1 + mdir = [mic_dir0, mic_dir1] + else: + sdir0 = sdir1 = None + mdir = [None, None] + + source0 = pra.SoundSource(source_loc0, signal=sig, directivity=sdir0) + source1 = pra.SoundSource(source_loc1, signal=sig, directivity=sdir1) mic_array = np.c_[mic0, mic1] - room = pra.ShoeBox(room_size).add(source0).add(source1).add_microphone(mic_array) + room = ( + pra.ShoeBox(room_size) + .add(source0) + .add(source1) + .add_microphone(mic_array, directivity=mdir) + ) assert len(room.sources) == 2 assert np.allclose(room.sources[0].position, source_loc0) @@ -141,6 +213,10 @@ def test_add_source_mic_ndarray_2(): assert np.allclose(room.mic_array.R[:, 0], mic0) assert np.allclose(room.mic_array.R[:, 1], mic1) assert room.mic_array.R.shape == (3, 2) + # Test directivities. + assert room.sources[0].directivity is sdir0 + assert room.sources[1].directivity is sdir1 + assert all(d is md for d, md in zip(room.mic_array.directivity, mdir)) if __name__ == "__main__":