Use case : Synthetic data generation for machine learning
The Treble SDK is a powerful tool designed to simplify the creation of high-quality acoustic training data. It enables the simulation of realistic acoustic scenes, incorporating complex materials, geometries, furnishings, directional sound sources, and microphone arrays. By leveraging these capabilities, developers can enhance audio machine learning (ML) algorithms for tasks such as speech enhancement, source localization, blind room estimation, echo cancellation, room adaptation, and generative AI audio. Independent research has validated the benefits of wave-based synthetic acoustic data, demonstrating its ability to significantly improve ML performance. See independent research here
In this tutorial we show how to setup a batch simulation in treble using geometries from our geometry database.
from treble_tsdk.tsdk import TSDK
from treble_tsdk import tsdk_namespace as treble
from treble_tsdk import display_data as dd
from treble_tsdk.geometry.mesh_collection import MeshTransform
from datetime import datetime
import numpy as np
from pathlib import Path
import scipy.io as sio
import scipy.signal as sig
import matplotlib.pyplot as plt
import json
timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M")
base_dir = Path.home() / "tmp" / "tsdk"
base_dir.mkdir(parents=True, exist_ok=True)
from treble_tsdk.tsdk import TSDK, TSDKCredentials
tsdk = TSDK()
p = tsdk.get_or_create_project(name="Bedroom_example")
%env TSDK_DASH_PLOTTING=1
Geometries
Let's view the available geometry datasets in Treble SDK
dd.as_table(tsdk.geometry_library.list_datasets_with_count())
bedrooms = tsdk.geometry_library.get_dataset("Bedrooms")
Lets view information of the first 20 bedroom geometries as a table
bedrooms_20 = bedrooms[0:20]
dd.as_table(bedrooms_20)
# plot the first bedroom
bedrooms[0].plot()
Get the layer names printed out - to be used for the automated material assignment.
bedrooms[0].layer_names
# initializing sabine plot function
# Function to plot sabine estimate
import matplotlib.pyplot as plt
def plot_sabine_estimate(sabine_estimate):
frequencies = [63, 125, 250, 500, 1000, 2000, 4000, 8000]
# Create the plot
plt.figure(figsize=(8, 6))
plt.plot(range(len(frequencies)), sabine_estimate, marker="o", linestyle="-")
# Set labels and title
plt.xlabel("Frequencies")
plt.ylabel("RT")
plt.title("Sabine estimate")
# Show x-axis labels at specified frequencies
plt.xticks(range(len(frequencies)), frequencies)
# Display the plot
plt.grid(True)
plt.show()
Materials
Let's access materials in the inbuilt database and assign them to layers
# Showing available material categories as a table
categories_with_count = tsdk.material_library.get_categories_with_count()
dd.as_table(categories_with_count)
import random
# Load material categories.
porous_materials = tsdk.material_library.get("Porous")
rigid_materials = tsdk.material_library.get("Rigid")
gypsum_materials = tsdk.material_library.get("Gypsum")
carpet_materials = tsdk.material_library.get("Carpets")
window_materials = tsdk.material_library.get("Windows")
wood_materials = tsdk.material_library.get("Wood")
furnishing_materials = tsdk.material_library.get("Furnishing")
panel_materials = tsdk.material_library.get("Perforated panels")
reasonable_materials = {
"Floor": rigid_materials,
"Furniture/Carpet": carpet_materials,
"InteriorDoorPosts": wood_materials,
"InteriorDoors": wood_materials,
"ExteriorWall": rigid_materials,
"ApartmentWall": rigid_materials,
"Furniture/Bed table": wood_materials,
"InteriorWall": gypsum_materials,
"Furniture/Wardrobes": wood_materials,
"WindowFrames": wood_materials,
"Windows": window_materials,
"Ceiling": gypsum_materials,
"Furniture/Shelfs": furnishing_materials,
"Furniture/Bed": furnishing_materials,
"Furniture/Paintings": panel_materials,
"Furniture/Ceiling Lamps": furnishing_materials,
}
def avg(lst):
return sum(lst) / len(lst)
def get_random_material_assignment(room):
material_assignment = []
for layer in room.layer_names:
material = random.choice(reasonable_materials[layer])
material_assignment.append(treble.MaterialAssignment(layer, material))
return material_assignment
Batch simulations in a loop
Here we setup the batch simulation, one simulation per bedroom in the previous list of 20 bedrooms. Materials are assigned to fit the criteara of sabine T60 between 0.3 and 0.6 seconds. Receivers are created on a grid and Sources also added to the simulation. Simulation definition includes all the informations needed for a simulation, when the simualtion definition has been populated it will be added to the project. Batch simulation is then ready to be started on project level.
from treble_tsdk.geometry.generator import GeometryGenerator
from treble_tsdk.geometry.validation import PointRuleset
from treble_tsdk.geometry.generator import PointsGenerator
import numpy as np
min_dist_from_surface = 0.25
min_dist_from_other_points = 0.5
min_dist_from_sources = 1
min_dist_from_receivers = 0.5
sources_ruleset = PointRuleset(
min_dist_from_surface, min_dist_from_other_points, min_dist_from_sources, min_dist_from_receivers
)
simulation_definitions = []
all_materials = tsdk.material_library.get()
min_rt = 0.3 # Minimum average reverberation time
max_rt = 0.8 # Maximum average reverberation time
for bedroom_idx, bedroom in enumerate(bedrooms_20):
print(f"Simulation_{bedroom_idx}")
sabine_estimate_rt = 0
while sabine_estimate_rt < min_rt or sabine_estimate_rt > max_rt:
# Refit room
if sabine_estimate_rt > 0:
print(f"Rejected material assignment with sabine estimate of {sabine_estimate_rt}sec")
new_material_assignment = get_random_material_assignment(bedroom)
sabine_estimate_rt = round(avg(bedroom.calculate_sabine_estimate(new_material_assignment)[1:-1]), 3)
material_assignment = new_material_assignment
print(f"Accepted material assignment with sabine estimate of {sabine_estimate_rt}sec")
Receivers = PointsGenerator.generate_valid_grid(
model=bedroom,
x_res=0.5,
y_res=0.5,
z_res=1.0,
)
receivers = [
treble.Receiver(
x=receiver[0],
y=receiver[1],
z=receiver[2],
receiver_type=treble.ReceiverType.spatial,
label=f"Receiver_{index}",
)
for index, receiver in enumerate(Receivers)
]
Sources = PointsGenerator.generate_valid_points(
model=bedroom,
max_count=3,
ruleset=sources_ruleset,
existing_receiver_points=Receivers,
existing_source_points=None,
)
sources = [
treble.Source(
x=source[0], y=source[1], z=source[2], source_type=treble.SourceType.omni, label=f"Source_{index}"
)
for index, source in enumerate(Sources)
]
simulation_definitions.append(
treble.SimulationDefinition(
name=f"Simulation_{bedroom_idx}",
simulation_type=treble.SimulationType.hybrid,
model=bedroom,
crossover_frequency=256, # cross over frequency should be set higher for real dataset - this is just an example to show the workflow
receiver_list=receivers,
source_list=sources,
material_assignment=material_assignment,
ir_length=0.2,
)
)
Let's visualize simulation in one of the bedrooms.
simulation_definitions[5].plot()
upload simulation definitions to the cloud
_ = p.add_simulations(simulation_definitions)
Get a token estimate for each of the simulations and the total cost
dd.as_table(p.estimate())
Start simulations
res = p.start_simulations()
View the progress
dd.as_table(p.get_progress())
# Cancel the simulations (if you regret starting them)
_ = p.cancel_simulations()
# Some code that downloads the results
_ = p.download_results(f"{base_dir}/results", rename_rule=treble.ResultRenameRule.by_label)