Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ adheres to `Semantic Versioning <http://semver.org/spec/v2.0.0.html>`_.
`Unreleased`_
-------------

Nothing yet
During the testing PR moving sound source #335, I found some issues, which I tried to improve with this PR:

- It was not possible to simulate rooms of standard dimensions, but only rooms with significantly unrealistic measurements (e.g., 2D room 100 * 100 m, which was in the test_moving_sound.py file).
- Step size is always the same for the x and y dimensions ‒ movement is possible only in four directions.
- Parameters for the x and y directions are Boolean variables that indicate the direction of a movement (True for forward movement and False for backward movement).
- The sound between the simulation steps is not smoothed, in which case you can hear the transitions between the steps.

Method simulate_moving_source was implemented.

`0.8.4`_ - 2025-05-19
---------------------
Expand Down
68 changes: 68 additions & 0 deletions pyroomacoustics/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -2186,6 +2186,74 @@ def add_soundsource(self, sndsrc, directivity=None):
if directivity is not None:
sndsrc.set_directivity(directivity)
return self.add(sndsrc)

def simulate_moving_source(
self,
start_position,
end_position,
signal=None,
delay=0,
fs=8000,
):
"""
Simulates a moving source in the room by given trajectory.

Parameters
----------
start_position: array_like or ndarray
The start position of the source. The position needs to be within the room dimension.
end_position: array_like or ndarray
The end position of the source. The position needs to be within the room dimension.
signal: ndarray, shape: (n_samples,), optional
The signal played by the source
delay: float, optional
A time delay until the source signal starts in the simulation
fs: int
The sampling frequency in Hz. Default is 8000.

Returns
-------
recorded_signal: ndarray
The recorded signals at the microphone array with the moving source, shape (n_channels, n_samples).
"""

start_position = np.array(start_position)
end_position = np.array(end_position)

num_steps = len(signal) // fs # Calculate the number of simulation steps based on the signal length and sampling frequency
trajectory = np.linspace(start_position, end_position,
num_steps + 1) # Compute linear trajectory coordinates between the start and end position, including the end position as the last simulation step

first = True
recorded_signal = None

for step in range(num_steps + 1):
source_location = trajectory[step]
distance = np.linalg.norm(source_location - self.mic_array.R[:, 0]) # Use the position of the first microphone for distance calculation; currently, only one microphone is supported

attenuation = 1 / max(distance, 0.1) # Attenuation is calculated according to the inverted distance value; the minimum distance is limited to 10 cm

self.sources = []
self.add_source(
position=source_location,
delay=delay,
signal=signal[step * fs: (step + 1) * fs] * attenuation, # Scale the signal based on the calculated attenuation
)

self.simulate()
new_signal = self.mic_array.signals[:, :fs]

if first:
recorded_signal = new_signal # Initialization of recorded_signal
first = False
else:
recorded_signal = np.concatenate((recorded_signal, new_signal), axis=1)
window_size = 5 # Window size for smoothing (constant 5 was chosen based on a psychoacoustic point of view; in the future, it is possible to make it a parameter)
window = np.hanning(window_size) / np.sum(np.hanning(window_size)) # Create and normalize the Hanning window of the specified size
for channel in range(recorded_signal.shape[0]): # Apply the smoothing window to each channel
recorded_signal[channel, :] = np.convolve(recorded_signal[channel, :], window, mode='same') # The convolution smooths the recorded signal by averaging nearby values using the created window

return recorded_signal

def image_source_model(self):
if not self.simulator_state["ism_needed"]:
Expand Down
29 changes: 29 additions & 0 deletions pyroomacoustics/tests/test_moving_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import pyroomacoustics as pra
import numpy as np

def test_simulate_moving_source():
fs = 16000
signal = np.arange(10)

rt60 = 0.5
room_dim = [6, 3.5, 2.5]
e_absorption, max_order = pra.inverse_sabine(rt60, room_dim)

room = pra.ShoeBox(room_dim, fs=fs, materials=pra.Material(e_absorption), max_order=max_order)

room.add_microphone(np.array([3, 1, 0.95]), room.fs)

move = room.simulate_moving_source(
start_position=[1, 1, 1.47],
end_position=[5, 1, 1.47],
signal=signal,
delay=0,
fs=fs,
)

move = move / np.max(np.abs(move)) # normalize recording
print(move)


if __name__ == "__main__":
test_simulate_moving_source()
Loading