Simulating many rooms
Use this tutorial to build a room-acoustic dataset from 200 randomly generated room models. You'll generate room geometry definitions, add random furniture, place valid sources and receiver grids, assign materials within a target Sabine reverberation-time range, run hybrid simulations, and inspect the results.
Key steps:
- Set up the project
- Generate room models
- Add random furniture
- Place sources and receivers
- Assign materials
- Create and run simulations
- Download and inspect results
Set up the project
Start by importing the packages used throughout the tutorial and creating a Treble project:
from pathlib import Path
import random
import numpy as np
from tqdm import tqdm
from treble_tsdk import treble
# This tutorial requires SDK credentials configured in the default location.
tsdk = treble.TSDK()
project = tsdk.get_or_create_project("Many rooms dataset tutorial")
Generate room models
The Treble SDK includes methods for generating randomized room geometry definitions with different shapes. This tutorial creates 40 room geometry definitions for each of five shape families, producing 200 room models in total. For focused examples of random room generation, see the Create rooms how-to.
generated_room_defs = {}
n_rooms_per_shape = 40
generated_room_defs["Shoebox"] = treble.GeometryDefinitionGenerator.create_random_shoebox_rooms(
n_rooms=n_rooms_per_shape,
x_range=(2, 5),
y_range=(2, 5),
z_range=(2, 3),
join_wall_layers=False,
)
generated_room_defs["L"] = treble.GeometryDefinitionGenerator.create_random_L_shaped_rooms(
n_rooms=n_rooms_per_shape,
a_side_range=(2, 4),
b_side_range=(2, 4),
c_side_range=(2, 3),
d_side_range=(2, 3),
height_range=(2, 3),
join_wall_layers=False,
)
generated_room_defs["T"] = treble.GeometryDefinitionGenerator.create_random_T_shaped_rooms(
n_rooms=n_rooms_per_shape,
a_side_range=(1.5, 3),
b_side_range=(2, 4),
c_side_range=(1.5, 3),
d_side_range=(1.5, 3),
height_range=(2, 3),
join_wall_layers=False,
)
generated_room_defs["U"] = treble.GeometryDefinitionGenerator.create_random_U_shaped_rooms(
n_rooms=n_rooms_per_shape,
a_side_range=(1.5, 3),
b_side_range=(1.5, 3),
c_side_range=(1.5, 3),
d_side_range=(1.5, 3),
height_range=(2, 3),
join_wall_layers=False,
)
generated_room_defs["Angled"] = treble.GeometryDefinitionGenerator.create_random_angled_shoebox_rooms(
n_rooms=n_rooms_per_shape,
base_side_range=(2, 5),
depth_range=(2, 4),
left_angle_range=(75, 105),
right_angle_range=(75, 105),
height_range=(2, 3),
join_wall_layers=False,
)
Add random furniture
The Treble SDK includes methods for injecting geometry objects, such as furniture, into room models. For this dataset, define a shared set of placement rules and use populate_with_geometry_components() to add random furniture to each room geometry definition.
Define the furniture objects, allowed rotations, and minimum distances from other objects and room model surfaces:
# A ComponentAnglePool specifies which orientations a geometry component can use.
whole_angles = treble.ComponentAnglePool([0, 90, 180, 270])
half_angles = treble.ComponentAnglePool([0, 45, 90, 135, 180, 225, 270, 315])
# Define the objects and quantities to place in the room models.
placements = [
# Request two tables from the geometry component library.
treble.GeometryComponentPlacement(
components=tsdk.geometry_component_library.query(group="table"),
preferred_count=2,
rotation_settings=whole_angles,
min_dist_from_objects=0.25,
min_dist_from_walls=0.25,
),
# Request two chairs from the geometry component library.
treble.GeometryComponentPlacement(
components=tsdk.geometry_component_library.query(group="chair"),
preferred_count=2,
rotation_settings=half_angles,
min_dist_from_objects=0.25,
min_dist_from_walls=0.25,
),
# Request one sofa from the geometry component library.
treble.GeometryComponentPlacement(
components=tsdk.geometry_component_library.query(group="sofa"),
preferred_count=1,
rotation_settings=whole_angles,
min_dist_from_objects=0.25,
min_dist_from_walls=0.25,
),
# Request two generated boxes with a larger clearance.
treble.GeometryComponentPlacement(
components=treble.GeometryComponentGenerator.create_box(
treble.BoundingBox.from_points(-0.5, 0.5, -0.5, 0.5, 0, 1)
),
preferred_count=2,
min_dist_from_objects=0.5,
min_dist_from_walls=0.5,
),
# Request five smaller generated boxes.
treble.GeometryComponentPlacement(
components=treble.GeometryComponentGenerator.create_box(
treble.BoundingBox.from_points(-0.1, 0.1, -0.1, 0.1, 0, 0.2)
),
preferred_count=5,
min_dist_from_objects=0.02,
),
]
Populate each generated room geometry definition, then add the furnished room models to the project with project.add_models_bulk():
model_upload_infos = []
for room_type, room_defs in generated_room_defs.items():
for room_idx, room_def in enumerate(tqdm(room_defs, desc=room_type)):
room_def.populate_with_geometry_components(
components=placements, selection_algorithm=treble.ComponentSelectionAlgorithm.random
)
model_upload_infos.append(treble.ModelUploadInfo(f"{room_type}_{room_idx}", room_def))
generated_models = project.add_models_bulk(model_upload_infos=model_upload_infos)
generated_rooms = {model.id: model for model in generated_models}
Place sources and receivers
For large, programmatically generated datasets, source and receiver placement needs validity rules. Without those rules, generated points can fall inside furniture or too close to room model surfaces. The Treble SDK provides methods to Generate valid positions while enforcing distances from room model surfaces, furniture, sources, receivers, and other generated points.
Start by creating three directional speech sources in each room model with generate_valid_points(). The source rules in this tutorial are:
- Each source must be at least m from all room model surfaces, including furniture objects.
- Each source must be at least m from the other generated sources.
Generate the valid speech source positions:
pg = treble.PointsGenerator()
sources_ruleset = treble.PointRuleset(min_dist_from_surface=0.5, min_dist_from_other_points=0.5)
speech_sources_pos = {}
for room in tqdm(generated_rooms.values()):
speech_sources_pos[room.id] = pg.generate_valid_points(
model=room,
max_count=3,
ruleset=sources_ruleset,
z_range=(0.5, 1.5),
)
Draw a random azimuth for each speech source:
speech_source_azimuths = {
room_id: np.random.uniform(-180, 180, len(positions)) for room_id, positions in speech_sources_pos.items()
}
Create the directional source objects with speech directivity from the generated positions and azimuths:
speech_directivity = tsdk.source_directivity_library.query(name="Speech")[0]
speech_sources = {}
for room_id, positions in speech_sources_pos.items():
speech_sources[room_id] = [
treble.Source.make_directive(
position=position,
orientation=treble.Rotation(
azimuth=speech_source_azimuths[room_id][idx],
elevation=0,
),
label=f"Speech_Src{idx}",
source_directivity=speech_directivity,
)
for idx, position in enumerate(positions)
]
After creating the sources, place spatial receivers on valid grids throughout each room model. This tutorial uses second-order Ambisonics receivers, but the same point-generation workflow can support other receiver types.
Because the room models have different sizes, define the receiver grid density by point count instead of fixed spacing. Use n_grid_points_x, n_grid_points_y, and n_grid_points_z to request a grid that adapts to each room model's extents, then limit receiver heights to m through m.
The receiver rules in this tutorial are:
- Each receiver must be at least m from all room model surfaces, including furniture objects.
- Each receiver must be at least m from the generated sources.
Generate the valid receiver positions:
receiver_positions = {}
for room in tqdm(generated_rooms.values()):
receiver_positions[room.id] = pg.generate_valid_grid(
model=room,
n_grid_points_x=5,
n_grid_points_y=5,
n_grid_points_z=3,
z_range=(0.5, 1.5 + 1e-15),
existing_points=speech_sources_pos[room.id],
min_dist_surf=0.25,
min_dist_existing_points=0.5,
)
Create the receiver objects from the generated positions:
receivers = {}
for room_id, positions in receiver_positions.items():
receivers[room_id] = [
treble.Receiver.make_spatial(position=position, ambisonics_order=2, label=f"Grid_Rcv{idx}")
for idx, position in enumerate(positions)
]
Assign materials
Treble lets you assign individual materials to room model surfaces, including furniture surfaces. In this tutorial, randomly assign materials from the material library until each room model has an average Sabine estimate between and s. For more details on the SDK's acoustic materials and the corresponding library, see Materials.
sabine_range = (0.4, 1.0)
materials = tsdk.material_library.get()
material_assignments = {}
sabine_estimates = {}
for room in tqdm(generated_rooms.values()):
__ = room.wait_for_model_processing()
successful_assignment = False
while not successful_assignment:
material_assignment = [
treble.MaterialAssignment(layer, random.choice(materials)) for layer in room.layer_names
]
sabine_est = room.calculate_sabine_estimate(material_assignment)
average_sabine = np.mean(sabine_est)
if sabine_range[0] <= average_sabine <= sabine_range[1]:
successful_assignment = True
material_assignments[room.id] = material_assignment
sabine_estimates[room.id] = average_sabine
Create and run simulations
Combine each room model, source list, receiver list, and material assignment list into a simulation definition. This tutorial uses hybrid simulations with a crossover frequency of Hz. For focused simulation examples, see Starting simulations.
sim_type = treble.SimulationType.hybrid
crossover_frequency = 500
sim_defs = []
for room in generated_rooms.values():
sim_def = treble.SimulationDefinition(
name=room.name,
simulation_type=sim_type,
model=room,
crossover_frequency=crossover_frequency,
ir_length=sabine_estimates[room.id],
receiver_list=receivers[room.id],
source_list=speech_sources[room.id],
material_assignment=material_assignments[room.id],
simulation_settings=treble.SimulationSettings(ambisonics_order=2),
)
sim_defs.append(sim_def)
sims_generated = project.add_simulations(sim_defs)
Start all simulations in the project:
project.start_simulations()
The SDK starts as many simulations as your concurrency tier allows and queues the remaining simulations.
Download and inspect results
After the simulations complete, download the results with labels preserved in the exported file names:
data_dir = Path("data")
project.download_results(data_dir, rename_rule=treble.ResultRenameRule.by_label)
Create a results object for one generated simulation and plot it:
results_sim0 = sims_generated[0].get_results_object(data_dir / sims_generated[0].name)
results_sim0.plot()
See also
Use these How-Tos for focused examples related to this tutorial: