Scene collections
A SceneCollection is a container for AudioScene objects built for large-scale dataset generation. Rather than building and rendering scenes one at a time, it lets you generate hundreds or thousands of varied room-and-source combinations, inspect and filter the collection before rendering — for example by predicted SNR or reverberation time — and then render the scenes that meet your criteria in batch. The collection also supports Parquet serialization so you can persist and reload it across sessions.
There are two ways to create a SceneCollection: assembling it manually from individually built scenes, or generating it automatically from a set of rules using SceneGenerator.
For information on rendering the resulting collection, see Scene collection rendering.
Basic examples
Manual assembly
Create an empty SceneCollection by passing a TSDK client and a data loader, then add scenes one at a time with add_scene(). The data loader must be created from the same IRCollection the scenes use, and controls where IR data is cached on disk.
from pathlib import Path
from treble_tsdk import treble
import treble_tsdk.scene as sc
from treble_tsdk.collections.scene_collection import SceneCollection
tsdk = treble.TSDK()
ir_collection = tsdk.datasets.purchase("Treble10")
# The data loader controls where IR files are cached on disk between sessions.
data_loader = ir_collection.get_data_loader(work_dir=Path("<work_dir>") / "cache")
scene_collection = SceneCollection(
client=tsdk,
data_loader=data_loader,
)
scene_collection.add_scene(scene_1)
See Audio scene for how to build individual AudioScene objects to add to the collection.
Automated generation with SceneGenerator
SceneGenerator automates collection creation at scale by combining a set of rules with an IRCollection to produce a SceneCollection of randomized scenes. Depending on the set of rules, each scene could have randomly assigned sources and receiver, track content, and listener orientation drawn from the ranges you define. The examples below assume you already have an IRCollection and at least one AudioDataset — see Audio scene for how to prepare these.
Source groups
SceneRules is the top-level container for all generation rules. Add SourceGroup entries to it, each describing one category of sources in the scene. A SourceGroup declares:
- a
TrackGeneratorto produce audio tracks - a
source_selectionPolars expression to filter which IRs are eligible for that group - source count constraints via
n_sourcesandmin_n_sources - a semantic tag via
group_tag - and an allocation priority via
priority_on_ir_collection
The following example defines a speech group and a noise group:
import polars as pl
scene_rules = sc.SceneRules(duration_s=20)
# Speech group — three foreground talkers drawn from non-elevated sources.
speech_tracks = sc.TrackGenerator(
audio_dataset=speech_dataset,
rules=sc.ConversationRules.from_preset(
sc.ConversationRulesPresets.sequential_talkers_increased_overlap,
in_track_level_range_db_spl=(69, 70),
),
talker_identifier="speaker_id",
)
scene_rules.add_source_group(
sc.SourceGroup(
name="conversation",
tracks=speech_tracks,
source_selection=(~pl.col("IS_NOISE_SOURCE")), # exclude elevated/ceiling sources
n_sources=2,
min_n_sources=2, # skip the scene if fewer than 2 eligible IRs are available
group_tag=sc.GroupTag.TARGET,
)
)
# Noise group — elevated sources only.
noise_tracks = sc.TrackGenerator(
audio_dataset=hvac_dataset,
rules=sc.NoiseSourceRules(
free_field_level_db_spl=(55, 56),
reuse_single_sample=True, # use the same clip for all sources (e.g. music to loudspeakers in a bar)
),
)
scene_rules.add_source_group(
sc.SourceGroup(
name="noise",
tracks=noise_tracks,
source_selection=pl.col("IS_NOISE_SOURCE"),
min_n_sources=1,
group_tag=sc.GroupTag.BACKGROUND,
)
)
Listener rules
ListenerRules describes how scenes are captured at the receiver. Pass it to SceneRules.set_listener(). It accepts a device, an orientation or orientation range, and optional device noise and filter specifications via DeviceSpecs.
device = tsdk.device_library.get_device_by_name("KEMAR051123_1")
device_specs = sc.DeviceSpecs(
noise_rules=sc.StaticNoiseRules.from_noise_type_and_level(
noise_type=sc.StaticNoiseType.mems_noise_profile, # generic spectral profile of MEMS mic
level_db_spl=(20, 25), # randomized per scene within this range
),
filter_definitions=[treble.ButterworthFilter(hp_order=2, hp_frequency=80)] # Emulate the microphone's frequency response
)
scene_rules.set_listener(
sc.ListenerRules(
device=device,
orientation=sc.OrientationRange( # Randomize orientation within a range
azimuth_range=(-180, 180),
elevation_range=(-10, 10),
),
device_specs=device_specs,
)
)
Scene collection generation
Pass the IRCollection and SceneRules to SceneGenerator, then call generate_scenes(). The generator iterates over receiver positions in the collection, assigns sources to IRs respecting priority and filter constraints, and randomizes track content for each scene. Use max_n_scenes to cap the number of scenes produced.
scene_generator = sc.SceneGenerator(
ir_collection=ir_collection,
scene_rules=scene_rules,
)
# Generate 100 scenes
scene_collection = scene_generator.generate_scenes(max_n_scenes=100)
The resulting SceneCollection supports indexing, slicing, iteration, and dataframe access:
scene_collection[0].plot()
scene_collection.dataframe.head(5)

Predicted scene SNR
enrich_with_predicted_snr() estimates the broadband SNR for each scene without rendering. It operates on per-band octave SPL values already attached to the source samples and IRs, so it runs fast and avoids the full convolution step. Use it to sanity-check your scene configuration, filter out degenerate setups, or select source–IR combinations likely to fall within a target SNR range.
Before calling it, enrich the IR collection with acoustic parameters and each audio dataset with SPL metadata. Then pass a source group name, track index, or list of track indices as target_selection:
# Add acoustic parameter metadata to the IR collection.
ir_collection.enrich_with_acoustic_parameters()
# SPL in octave bands must be attached to audio datasets before prediction.
speech_dataset.enrich_with_spl()
hvac_dataset.enrich_with_spl()
# Add a predicted SNR column to the collection dataframe.
scene_collection.enrich_with_predicted_snr(target_selection="conversation")
# Retrieve the predicted SNR for a specific scene index.
scene_collection.get_predicted_snr(scene_index=0, target_selection="conversation")
The prediction is estimated for a single channel represented by the Mono IR, not for the device's captured signal. It does not account for the device IR or device filters. Device noise is accounted for, but averaged over all device microphones into that single channel.
Call plot() on the SceneCollection to display a histogram of the predicted SNR across all scenes:
scene_collection.plot()

The predicted SNR has an expected error of −0.2 dB mean and 1.5 dB standard deviation relative to the SNR measured from rendered audio.
The main sources of deviation are:
- Speech RMS over full sample duration. If speech samples contain leading, trailing, or inter-sentence silence, the prediction underestimates speech level and produces a lower SNR than the rendered value. Bias direction: prediction underestimates SNR. Trimming leading and trailing silence reduces this error but does not eliminate it entirely.
- Non-stationary noise. The prediction uses the long-term average SPL of each noise source. If the noise level during speech-active periods differs from that average (e.g., music with loud and quiet passages), the estimate will be off. Bias direction: either direction.
- Partial temporal overlap. When a noise source is active for only part of the scene, its contribution to noise RMS is reduced in proportion to its overlap with the speech-active window. This compounds the stationarity issue described above and partial overlap might amplify the stationarity error. Bias direction: either direction.
- Reverberant tails. The rendered signal includes IR decay after a source stops; the prediction treats source activity as ending at block boundaries. This affects both the noise contribution and the P.56 speech-activity mask, which can extend into the reverberant tail. Bias direction: either direction.
- Octave-band spectral approximation. Source and IR spectra are represented as piecewise-constant over octave bands. Within-band variation (e.g., low-frequency room modes, spectrally shaped noise) introduces error. Bias direction: either direction.
- Multiple speech-type noise sources. When speech is used as noise (e.g., babble or a competing talker), all of the above effects compound. Bias direction: prediction is highly likely to underestimate SNR.
- Incoherent power summation. The prediction accumulates noise power additively, assuming all sources are uncorrelated. When sources are positioned close together, their IRs may be spatially correlated, or the room may add correlation through shared early reflections or room modes, increasing the actual noise level above the prediction. Bias direction: either direction.
- Band-count mismatch (GA-only IRs). For IRs produced by a GA-only simulation, the 63 Hz octave band is skipped in the SPL calculations, resulting in one fewer band than in hybrid simulations. The missing band affects both the noise and target level estimates. Bias direction: either direction.
enrich_with_spl() processes every audio file in the dataset and can be slow for large datasets. If you reuse the same dataset across multiple sessions, save the enriched dataset to Parquet after the first run and load it from there on subsequent runs:
# First run: load, enrich and save.
speech_set = sc.AudioDataset.from_huggingface(
repo_id="agkphysics/AudioSet",
config="full",
split="bal_train",
audio_loader_class=sc.AudioSetAudioLoader,
schema_mapping={"id": "video_id"},
)
speech_set = speech_set.enrich_with_spl()
parquet_path = "./speech_dataset.parquet"
speech_set.dataframe.write_parquet(parquet_path)
# Subsequent runs: load directly.
speech_set = sc.AudioDataset.from_parquet(
parquet_path,
audio_loader_class=sc.LibriSpeechAudioLoader(parquet_path),
)
Filter by predicted SNR
Once the collection is enriched with predicted SNR, call filter_by_predicted_snr() to keep only the scenes that fall within a target SNR range for a given source group. Pass the same target_selection used during enrichment:
scene_collection = scene_collection.filter_by_predicted_snr(
min_db=15,
max_db=25,
target_selection="conversation",
)