-
Notifications
You must be signed in to change notification settings - Fork 435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
moving sound source #335
base: master
Are you sure you want to change the base?
moving sound source #335
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2080,6 +2080,115 @@ def add_microphone_array(self, mic_array, directivity=None): | |
|
||
return self.add(mic_array) | ||
|
||
def simulate_moving_sound( | ||
self, | ||
position, | ||
signal=None, | ||
delay=0, | ||
fs=None, | ||
stept=200, | ||
speed=5, | ||
x_direction=True, | ||
y_direction=True, | ||
): | ||
""" | ||
Adds a moving sound source given by its position in the room. | ||
Simulate all the locations the sound source is moving to | ||
determining the RIR. | ||
Perform time-varying convolution on the output signal. | ||
|
||
Parameters | ||
----------- | ||
position: ndarray, shape: (2,) or (3,) | ||
The starting location of the moving source in the room. | ||
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: float, optional | ||
The sampling frequency of the microphone, if different from that of the room. | ||
stept: float, (The default is 200). | ||
The step duration, measured in milliseconds. | ||
It is used to determine the duration of each simulation | ||
step in the virtual room. | ||
speed: float, (The default is 5). | ||
Speed of the moving sound source in the virtual room. | ||
It is measured in meters per second. | ||
x_direction, y_direction: bool, optional | ||
Flags indicating the direction of movement. True for forward, False for backward. | ||
|
||
Returns | ||
------- | ||
movemix: ndarray | ||
audio signal obtained from the simulation. | ||
filter_kernels: ndarray | ||
Room impulse responses at each step. | ||
""" | ||
|
||
# Validate parameters | ||
|
||
if signal is None: | ||
raise ValueError("Please provide a signal for the sound source.") | ||
|
||
if fs is None or fs <= 0: | ||
raise ValueError("Invalid sampling frequency (fs).") | ||
|
||
# Calculate the number of samples in each step | ||
stepn = int(stept * fs / 1000) | ||
# Calculate the distance traveled in a step | ||
stepd = speed * stept / 1000 | ||
# The number of simulation steps needed to process the entire audio signal. | ||
n = int(len(signal) / stepn) | ||
# Store RIR of the simulated audio | ||
# Initialize lists to store results | ||
movemix_list = [] | ||
movemix = np.array([]) | ||
filter_kernels = np.array([]) | ||
|
||
for i in range(n): | ||
# Reshape position to handle both (2,) and (2, 1) cases | ||
position = np.array(position).reshape(-1) | ||
|
||
# Update the position of the sound source based on movement direction | ||
if x_direction: | ||
x_position = [position[0] + stepd * i, position[1]] | ||
else: | ||
x_position = [position[0] - stepd * i, position[1]] | ||
|
||
# Update the position of the sound source based on movement direction | ||
if y_direction: | ||
y_position = [position[0], position[1] + stepd * i] | ||
else: | ||
y_position = [position[0], position[1] - stepd * i] | ||
|
||
# Create a numpy array for the source location | ||
source_location = np.array([x_position[0], y_position[1]]) | ||
self.sources = [] | ||
self.add_source( | ||
position=source_location, | ||
delay=delay, | ||
signal=signal[i * stepn : (i + 1) * stepn], | ||
) | ||
# Simulate the room and obtain the room impulse response | ||
self.simulate() | ||
|
||
# only use the duration of original, to avoid echo | ||
# set data type as 16bit, ref:https://docs.scipy.org/doc/scipy/reference/generated/scipy.io.wavfile.write.html | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please do not do this here. |
||
record_ = self.mic_array.signals[:, :stepn].astype("int16") | ||
|
||
if i == 0: | ||
movemix = record_ | ||
filter_kernels = self.rir.copy() | ||
else: | ||
movemix = np.concatenate((movemix, record_), axis=1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is very inefficient as a copy of the array occurs at every loop, and at each loop the array becomes longer. In the end this incurs a cost quadratic in the length of the array. |
||
filter_kernels = np.concatenate( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same |
||
(filter_kernels, self.rir.copy()), axis=1 | ||
) | ||
|
||
return movemix, filter_kernels | ||
|
||
|
||
def add_source(self, position, signal=None, delay=0, directivity=None): | ||
""" | ||
Adds a sound source given by its position in the room. Optionally | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import numpy as np | ||
|
||
import pyroomacoustics as pra | ||
|
||
|
||
def test_simulation_output_shape(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is good as a basic test, but it would be nice to have some tests for the correctness of the time-varying convolution too. |
||
# Set up input parameters for the test | ||
position = np.array([0, 0]) | ||
signal = np.random.rand(44100) # Replace with an actual signal | ||
fs = 44100 | ||
stept = 200 | ||
speed = 5 | ||
x_direction = True | ||
y_direction = True | ||
|
||
# Create an instance of the Room class | ||
room = pra.ShoeBox([100, 100]) # Adjust the room dimensions as needed | ||
|
||
# Microphone parameters | ||
# Two microphones at different locations | ||
mic_locations = np.array([[1, 1], [3, 1]]) | ||
room.add_microphone_array(pra.MicrophoneArray(mic_locations, room.fs)) | ||
|
||
# Set the source location as a 1D or 2D array with the correct shape | ||
position = np.array([[2, 3]]) # 2D array with shape (1, 2) | ||
|
||
# Call the simulate_moving_sound function | ||
movemix, filter_kernels = room.simulate_moving_sound( | ||
position=position, | ||
signal=signal, | ||
fs=fs, | ||
stept=stept, | ||
speed=speed, | ||
x_direction=x_direction, | ||
y_direction=y_direction, | ||
) | ||
|
||
print(movemix, filter_kernels) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be some assert at the end of the test that fails is something is wrong with the code. |
||
|
||
|
||
if __name__ == "__main__": | ||
test_simulation_output_shape() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the time varying convolution, the tail (i.e., the samples beyond the original length) need to be kept and added to the head of the following segment.
Kind of like overlap add for long filtering (except the filter changes everytime).
You can think about it this way: after the source has moved, you still hear echoes generated by the source at the previous position.