Skip to content
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

simple implementation of the Room class as in proposed API #61

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
125 changes: 124 additions & 1 deletion spatialscaper/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
import scipy
import numpy as np
import warnings
from tqdm import tqdm

import pyroomacoustics as pra
from .room_sim import get_tetra_mics, center_mic_coords


# Local application/library specific imports
from .utils import (
Expand All @@ -26,7 +31,7 @@
save_output,
sort_matrix_by_columns,
)
from .sofa_utils import load_rir_pos, load_pos
from .sofa_utils import load_rir_pos, load_pos, create_srir_sofa


# Sound event classes for DCASE Challenge
Expand Down Expand Up @@ -762,3 +767,121 @@ def generate(self, audiopath, labelpath):

# save output
save_output(audiopath, labelpath, out_audio, self.sr, labels)

class Room:
def __init__(
self,
dims,
sr=24000,
src_locs=None,
mic_loc=None,
mic_type="tetra",
max_order=15,
scattering=0.9,
wall_abs=0.5,
flor_abs=0.1,
ceil_abs=0.1
):
"""
Initializes a Room. Should inherit most properties from the pyroomacoustics room class. Stores output to a .sofa file which can be read by the Scape class for generating soundscapes

Args:
dims (list): Three-element list defining length, width, and height of the defined room (in meters).
sr (int): Sample rate of room simulation
src_locs (np.ndarray, 3 X N): Element of 3D coordinates defining locations for all sources. If None, generate 9 rings sampled at 1degree increments evenly spaced within the height of the room
mic_loc (list): Three-element list defining the centerpoint of the microphone array
max_order (int): max order of reflections computed
mic_type (string): string defining type of microphone array, or list of coordinates to define custom microphone array options are tetra right now
TODO: add em32 coordinates
scattering: (float) scattering coefficient
wall_abs: (float) wall absorption coefficient
flor_abs: (float) floor absorption coefficient
ceil_abs: (float) ceiling absorption coefficient


"""


absorption_arr = [wall_abs] * 4 + [flor_abs, ceil_abs]
materials = [pra.Material(a, scattering) for a in absorption_arr]

if mic_type == 'tetra':
mic_coords, mic_dirs = get_tetra_mics()
else:
print("Unsupported mic type")

if mic_loc is None:
mic_loc = np.array([dims[0]/2, dims[1]/2, dims[2]/2])

centered_mics = center_mic_coords(mic_coords, mic_loc)

self.dims = dims
self.sr = sr
self.materials = materials
self.max_order = max_order
self.mic_type = mic_type
self.mic_loc = mic_loc
self.mics = list(centered_mics)
self.mic_dirs = mic_dirs
self.src_locs = src_locs


def compute_rirs(self, sofa_path, rir_len=7200, flip=True, db_name="Sim RIR", room_name="Sim Room", n_angles=360):
if self.src_locs is None:

path_stack = np.empty((0, 3))
rir_stack = np.empty((0, len(self.mics), rir_len))

heights = np.linspace(0,self.dims[2],11)[1:10] #generate 9 evenly spaced heights
rad = 0.4*np.minimum(self.dims[0], self.dims[1])
deg = np.linspace(0, 2*np.pi, n_angles)

for j, height in enumerate(tqdm(heights)):

path = [[rad*np.cos(deg[i]),rad*np.sin(deg[i]), height]for i in range(n_angles)]
path_rirs = np.empty((len(self.mics), len(path), rir_len))

room = pra.ShoeBox(
self.dims,
fs=self.sr,
materials=self.materials[0], #todo fix list import
max_order=self.max_order,
)

room.add_microphone_array(np.array(self.mics).T, directivity=self.mic_dirs)
for source in path:
try:
room.add_source(np.maximum(source, 0))

except ValueError:
print("Source at {} is not inside room of dimensions {}"\
.format(source, room_dim)
)
room.compute_rir()
for k in range(len(self.mics)):
for l in range(len(path)):
path_rirs[k, l] = room.rir[k][l][:rir_len]

if flip:
if j % 2 == 1:
# flip every other height, as in DCASE
path_rirs = path_rirs[:, ::-1]
path = path[::-1]

path_rirs = np.moveaxis(path_rirs, [0, 1, 2], [1, 0, 2])

rir_stack = np.concatenate((rir_stack, path_rirs), axis=0)
path_stack = np.concatenate((path_stack, path), axis=0)

create_srir_sofa(
sofa_path,
rir_stack,
path_stack,
self.mic_loc,
db_name=db_name,
room_name=room_name,
listener_name=self.mic_type,
)
else:
print("Unsupported src location")

17 changes: 14 additions & 3 deletions spatialscaper/room_sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import pyroomacoustics as pra
from pyroomacoustics import directivities as dr
from room_scaper import sofa_utils, tau_loading


def deg2rad(deg):
Expand Down Expand Up @@ -69,7 +68,19 @@ def unitvec_to_cartesian(path_unitvec, height, dist):
if type(dist) == np.ndarray:
z_offset = height
rad = np.sqrt(dist[0] ** 2 + (dist[2] + z_offset) ** 2)
scaled_path = tau_loading.map_to_cylinder(path_unitvec, rad, axis=1)
scaled_path = map_to_cylinder(path_unitvec, rad, axis=1)
else:
scaled_path = tau_loading.map_to_cylinder(path_unitvec, dist, axis=2)
scaled_path = map_to_cylinder(path_unitvec, dist, axis=2)
return scaled_path

def map_to_cylinder(path, rad, axis=2):
# maps points (unit vecs) to cylinder of known radius along axis (default z/2)
scaled_path = np.empty(path.shape)
rad_axes = [0, 1, 2]
rad_axes.remove(axis)
for i in range(path.shape[0]):
vec = path[i]
scale_rad = np.sqrt(np.sum([vec[j] ** 2 for j in rad_axes]))
scale = rad / scale_rad
scaled_path[i] = vec * scale
return scaled_path