Use case : Automotive
The treble SDK is capable of simulating the acoustics of a car cabin. In the sdk the user can virtually prototype the acoustics and the infotainment system together, estimite STI in the car and validate acoustic designs virtually.
Tutorial
In this tutorial, we will guide you through the process of setting up an acoustic simulation using the Treble SDK. You'll learn how to define and manipulate geometric models, inject source geometries like loudspeakers, and configure the simulation environment to accurately simulate sound propagation in a 3D space.
Initialize Project and Import Libraries
In this section, we will import the necessary modules required for the project and initialize a Treble SDK project. These imports bring in key components from the Treble SDK that are essential for creating, manipulating, and simulating acoustic models. You’ll use them for:
- Displaying data structures (
display_data
). - Accessing core SDK utilities and classes (
tsdk_namespace
). - Working with meshes and applying transformations to define the geometry of the simulation (
MeshCollection
andMeshTransform
). - Managing the overall simulation project and configuration through the
TSDK
class.
import os
from treble_tsdk import display_data as dd
from treble_tsdk import tsdk_namespace as treble
from treble_tsdk.tsdk import TSDK
# Initialize the TSDK instance
tsdk = TSDK()
# Define the project name and a brief description of the project.
project_name = "Automotive"
description = "Use case example demonstrating how to simulate a car cabin"
# Create or retrieve an existing project using the name and description
p = tsdk.get_or_create_project(
name=project_name,
description=description,
)
%env TSDK_DASH_PLOTTING=1
env: TSDK_DASH_PLOTTING=1
== SDK package is up to date ==
Define Control Parameters and Paths
Here we will define key parameters that control various aspects of our simulation, including physical dimensions (for the loudspeakers), solver settings (such as crossover frequency), and file paths for the geometry and model.
PLOT_INTERMEDIATE = True # Flag for plotting intermediate results
REPLACE_EXISTING_MODEL = False # Flag for replacing existing model with the same name
# Define the physical dimensions of a loudspeaker and its components
SOURCE_POSITION = (0.3, -0.2, 0.5) # (x, y, z) in meters
SOURCE_ROTATION = (-90.0, 0.0, 0.0) # (pitch, yaw, roll)
ENCLOSURE_DIMENSIONS = (160e-3, 100e-3, 100e-3) # (width, depth, hight) in meters
MEMBRANE_DIMENSIONS = (73e-3 / 2, 50e-3) # (radius, height) in meters
# Define solver parameters
CROSSOVER_FREQUENCY = 2880
SIMULATION_NAME = "sim1"
IR_LENGTH = 0.15
# Define file paths for models and geometry data
MODEL_PATH = "data/"
FILENAME = "car_cabin.3dm"
Next, let's define some helper functions to improve code clarity.
def remove_existing_model(name):
"""Helper function to remove an existing model from the project.
This is useful when you want to replace an existing model with a new one.
"""
if model := p.get_model_by_name(name):
p.delete_model(model)
def material(layer: str, treble_material: str, scattering: float = None):
"""Helper function for material assignment"""
return treble.MaterialAssignment(
layer, tsdk.material_library.get_by_name(treble_material), scattering
)
Geometry
In this section, we import the geometry of a car and add it to our simulation project. We then programmatically generate a loudspeaker geometry which we inject into the model.
Import 3D model
First, let's import the geometry of a car and add it to our simulation project.
model_name = project_name + "-" + SIMULATION_NAME
# Optionally, delete an existing model with the same name
if REPLACE_EXISTING_MODEL:
remove_existing_model(model_name)
# Add the new model to the project
car_model = p.add_model(
model_name=model_name, model_file_path=os.path.join(MODEL_PATH, FILENAME)
)
car_model.as_live_model_status() # View the status of the model as it is being processed
# Optionally plot the model to visually inspect it
car_model.plot()
Materials
Here we map specific materials to various parts of the car model and the loudspeaker. For example, materials like glass for the windshield, or hard materials for the speaker membrane, will be assigned to the corresponding layers in the model. As this tutorial uses a fully wave-based approach, all scattering values are set to None
, as this value is only relevant for geometrical simulations.
import random
material_assignment = []
for layer in car_model.layer_names:
if layer == "glass":
mat_list = tsdk.material_library.search("window")
else:
mat_list = tsdk.material_library.search("0% Abs")
material_assignment.append(treble.MaterialAssignment(layer,random.choice(mat_list)))
dd.as_table(material_assignment)
Sources and Receivers
In this section, we define the locations and properties of the sound sources and receivers in the simulation. We will model our injected loudspeaker as a boundary velocity source, which will approximate the directivity pattern of the loudspeaker with the specified geometry.
# Define the locations and labels for the sound sources
sources = [
{"point": SOURCE_POSITION, "label": "Omni-SRC"},
]
# Define the locations and labels for the receivers
receivers = [
{"point": (0.3, -0.9, 0.5), "label": "REC1"},
{"point": (-0.3, -0.9, 0.5), "label": "REC2"},
{"point": (-0.3, 0.2, 0.5), "label": "REC3"},
{"point": (0.3, 1.0, 0.5), "label": "REC4"},
{"point": (-0.3, 0.9, 0.5), "label": "REC5"},
]
# Assign boundary velocity source on "membrane" layer
source_properties = treble.SourceProperties(
azimuth_angle=-90,
elevation_angle=0,
source_directivity = tsdk.source_directivity_library.query(name="Speech")[0]
)
# Create a list of Source objects based on the source locations and properties
source_list = [
treble.Source(
*source["point"],
source_type=treble.SourceType.omni,
label=source["label"],
source_properties=source_properties,
)
for source in sources
]
# Create a list of Receiver objects based on the receiver locations
receiver_list = [
treble.Receiver(
*receiver["point"],
receiver_type=treble.ReceiverType.mono,
label=receiver["label"],
)
for receiver in receivers
]
Simulation
Add Simulation to Project
We will define a simulatino and add it to the project and wait for a cost estimate from the simulation engine. The estimate helps us understand the resource requirements before starting the full simulation run. In this case we opt for a fully wave-based simulation via the dg
simulation type, which we fetch from the treble
namespace. In this case, the crossover_frequency
parameter sets the upper limit of our simulation.
Alternatively we could opt for a hybrid of the geometrical and wave-based solvers via treble.SimulationType.hybrid
.
sim_def = treble.SimulationDefinition(
name=SIMULATION_NAME,
simulation_type=treble.SimulationType.dg,
model=car_model, # Geometry to use.
crossover_frequency=CROSSOVER_FREQUENCY,
ir_length=IR_LENGTH, # Impulse length
receiver_list=receiver_list,
source_list=source_list,
material_assignment=material_assignment,
)
# Let's plot our simulation definition
sim_def.plot()
Cost estimate
Yield an estimate of the computational resources required to complete the simulation.
# Add the simulation to the project and wait for an estimate of the computational resources required
simulation = p.add_simulation(definition=sim_def)
simulation.wait_for_estimate()
# Display the cost estimate for the simulation
dd.as_tree(simulation.estimate())
Inspect Mesh
Before running the simulation, we can inspect the mesh that represents the simulation environment. This step allows us to ensure that the geometry and materials are correctly represented.
from treble_tsdk.geometry import plot
try:
simulation.get_mesh_info().as_live_mesher_progress()
mesh = simulation.get_mesh_info()
mesh.source_mesh_infos_by_label[source_list[0].get("label")].plot()
except Exception as e:
print(f"Failed to plot mesh: {e}")
Run the Simulation
Finally, we can run the simulation based on the user's input. Once the user confirms, the simulation will be started, and the progress will be tracked in real-time.
if input("Run simulation? (y/n): ") == "y":
if input("Are you sure? (y/n): ") == "y":
simulation.start()
simulation.as_live_progress()