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/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..c56a2796 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,18 +9,51 @@ 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) +) + + +@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 -def test_add_source_mic(): - room = pra.ShoeBox(room_size).add_source(source_loc0).add_microphone(mic0) + 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) @@ -27,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) @@ -45,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) @@ -54,14 +105,27 @@ 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])) -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,15 +136,40 @@ 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]) + +@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] -def test_add_source_mic_ndarray(): - source0 = pra.SoundSource(source_loc0, signal=sig) - source1 = pra.SoundSource(source_loc1, signal=sig) + 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 @@ -90,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) - - -def test_add_source_mic_ndarray_2(): - source0 = pra.SoundSource(source_loc0, signal=sig) - source1 = pra.SoundSource(source_loc1, signal=sig) + # 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)) + + +@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) @@ -106,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__":