A PyTorch-based room impulse response (RIR) simulation toolkit with a clean API and GPU support. This project has been developed with substantial assistance from Codex.
Warning
TorchRIR is under active development and may contain bugs or breaking changes. Please validate results for your use case. If you find bugs or have feature requests, please open an issue. Contributions are welcome.
TorchRIR supports Python 3.11.4 through 3.13. Python 3.11.4 is the minimum
because dataset extraction requires the standard-library
tarfile.data_filter security policy; Python 3.14 is not yet supported.
pip install torchrirInstall only the optional features you use:
pip install "torchrir[audio]" # SoundFile-backed audio I/O
pip install "torchrir[cli]" # YAML configuration for the example CLI
pip install "torchrir[hpf]" # optional RIR high-pass filtering
pip install "torchrir[viz]" # plots, GIFs, and MP4 rendering
pip install "torchrir[datasets]" # dataset loaders and builders
pip install "torchrir[oobss]" # oobss integration
pip install "torchrir[all]" # all optional features| Feature | torchrir |
gpuRIR |
pyroomacoustics |
rir-generator |
|---|---|---|---|---|
| 🎯 Dynamic Sources | ✅ Emission-time | 🟡 Single moving source | 🟡 Manual loop | ❌ |
| 🎤 Dynamic Microphones | ✅ Observation-time | ❌ | 🟡 Manual loop | ❌ |
| 🖥️ CPU | ✅ | ❌ | ✅ | ✅ |
| 🧮 CUDA | ✅ | ✅ | ❌ | ❌ |
| 🍎 MPS | ✅ | ❌ | ❌ | ❌ |
| 📊 Scene Plot | ✅ | ❌ | ✅ | ❌ |
| 🎞️ Dynamic Scene GIF | ✅ | ❌ | 🟡 Manual animation script | ❌ |
| 🗂️ Dataset Build | ✅ | ❌ | ✅ | ❌ |
| 🎛️ RIR Convolution | ✅ Static/dynamic | 🟡 Dynamic helper | ✅ | ❌ |
| 🧱 Non-shoebox Geometry | 🚧 Candidate | ❌ | ✅ | ❌ |
| 🌐 Geometric Acoustics | 🚧 Candidate | ❌ | ✅ | ❌ |
Legend: ✅ native support, 🟡 manual setup, 🚧 candidate (not yet implemented), ❌ unavailable
For detailed notes and equations, see Documentation: Library Comparisons.
- CUDA tests run in
.github/workflows/cuda-ci.ymlon a self-hosted runner with labels:self-hosted,linux,x64,cuda. - The workflow validates the lockfile-based installation via
uv sync --locked --group test, checkstorch.cuda.is_available(), runstests/test_device_parity.pywith-k cuda, and executes the dynamic emission-time convolution autograd regression on CUDA. - The extended job installs the pinned
gpuRIRreference revision. - The extended workflow requires
gpuRIRinstallation and runs direct-path static/dynamic RIR comparisons plus an isolatedsimulateTrajectorycomparison using identical synthetic RIRs. Known normalization, image-count, and fractional-delay conventions are handled explicitly; signals are never freely aligned. Installation failure or a skipped comparison fails that workflow.
examples/static.py: fixed sources and microphones with configurable mic count (default: binaural).
uv run python examples/static.py --plotexamples/dynamic_src.py: moving sources, fixed microphones.
uv run python examples/dynamic_src.py --plotexamples/dynamic_mic.py: fixed sources, moving microphones.
uv run python examples/dynamic_mic.py --plotexamples/cli.py: unified CLI for static/dynamic scenes with JSON/YAML configs.
uv run python examples/cli.py --mode static --plotexamples/build_dynamic_dataset.py: small dynamic dataset generation script (CMU ARCTIC / LibriSpeech; fixed room/mics, randomized source motion).
uv run python examples/build_dynamic_dataset.py --dataset cmu_arctic --num-scenes 4 --num-sources 2torchrir.datasets.dynamic_cmu_arctic: oobss-compatible dynamic CMU ARCTIC builder CLI.
python -m torchrir.datasets.dynamic_cmu_arctic --cmu-root datasets/cmu_arctic --n-scenes 2 --overwrite-datasetexamples/benchmark_device.py: CPU/GPU benchmark for RIR simulation.
uv run python examples/benchmark_device.py --dynamic
- For dataset attribution and redistribution notes, see THIRD_PARTY_DATASETS.md.
torchrir.datasets.CmuArcticDataset(root, speaker=..., download=...)- Accepted
speaker:aew,ahw,aup,awb,axb,bdl,clb,eey,fem,gka,jmk,ksp,ljm,lnh,rms,rxr,slp,slt - A non-string
speakerraisesTypeError; an unsupported string raisesValueError. - Every non-empty transcript entry must parse and contain a canonical
arctic_<letter><four-digits>ID, and at least one such ID must have a same-named regular WAV below the dataset tree. A missing or incomplete tree raisesFileNotFoundErrorwithdownload=False. - The speaker dataset root, every traversed parent directory, and final
transcript/audio entries must not be symlinks. Files are opened by walking
directory descriptors component by component with
O_NOFOLLOW; the same opened descriptor is consumed through parsing or SoundFile decoding. - Downloads use HTTPS, verify the pinned per-speaker SHA-256 table also
published by
torchaudio,
and extract only regular files/directories below the dataset root.
Extraction is validated in staging before publication, and an already
usable tree skips archive and network access even with
download=True. cmu_arctic_speakers()lists every supported speaker;cmu_arctic_speakers(root)lists only usable local speaker directories.
- Accepted
torchrir.datasets.LibriSpeechDataset(root, subset=..., speaker=..., download=...)- Accepted
subset:dev-clean,dev-other,test-clean,test-other,train-clean-100,train-clean-360,train-other-500 - Invalid
subsetraisesValueError. - A numeric speaker tree is ready only when, in every transcript discovered
directly below one of its numeric chapters, each non-empty entry uses a
canonical ID that agrees with its speaker and chapter directories. At least
one entry must have a same-named regular FLAC. Missing or incomplete
subset/speaker trees raise
FileNotFoundErrorwithdownload=False. - The subset root, numeric speaker/chapter directories, and final transcript/audio entries must not be symlinks. Final entries are opened with the same descriptor walk and consumed through that descriptor rather than reopening a validated pathname.
- Downloads verify the published subset MD5 and reject unsafe or special
archive members. Validated staged publication preserves the previous tree
if replacement fails; an already usable subset skips archive/network work.
With
speaker=..., the fast path checks that requested speaker rather than accepting an unrelated usable speaker in a partial subset.
- Accepted
- Shared download and publication contract:
- Secure dataset filesystem operations are supported on Linux and macOS only.
They require POSIX
dir_fd,O_DIRECTORY,O_NOFOLLOW, and the platform's atomic no-replace/exchange rename primitives; unsupported platforms or filesystems raiseNotImplementedErrorbefore an operation starts. - Network requests use a 60-second per-connect/per-read timeout and a six-hour
transfer deadline. The remaining total time is applied to every connection
and response-body read; this is a network-transfer deadline, not a hard
real-time bound on hashing or disk I/O. A response is capped at 64 GiB and
streamed into a fresh, uniquely named, TorchRIR-owned sibling workspace.
If supplied,
Content-Lengthmust be a valid non-negative value at most 64 GiB and must match the complete body exactly. The final archive path is changed only after the mandatory pinned digest has also been verified. - One retry is made only for HTTP 408/429/5xx, transport, or transfer-integrity
failures, including a malformed
Content-Length. Local filesystem, extraction, validation, publication, and 64 GiB safety-limit errors are propagated without retrying or re-downloading an already verified archive. - A cached archive may be absent, a regular file, or a symlink; other entry
types are rejected. An absent destination is published with atomic
no-replace. An existing entry is exchanged with the verified temporary file
only if its device, inode, and type are unchanged; a racing third-party entry
is restored and retained. A symlink target is never followed. Digest
verification and tar parsing share one
O_NOFOLLOWdescriptor and inode. - Archives are fully prevalidated before extraction. Only regular files and directories are accepted, with limits of 1,000,000 members, 8 GiB per file, and 512 GiB total declared file data.
- A per-speaker CMU or per-subset LibriSpeech writer lock covers stale owned workspace cleanup, the readiness check, cache handling, download, extraction, validation, and publication. Unsafe or unowned stale paths are retained with a warning; cleanup failures do not hide the primary result. A separate per-target publication lock covers only transaction recovery and the final target check/rename/rollback/cleanup sequence.
- Replacement transactions persist a fsynced manifest containing the staged and previous tree identities plus the publication phase. Recovery restores only an identity recorded by that manifest. Unknown transaction entries, unsafe symlinks, and an unrelated target stop recovery without deleting the retained backup.
- Dynamic builds for one target acquire a separate build lock before recovery, target checks, stale owned-build-workspace cleanup, or scene generation. They are therefore serialized for the complete expensive build. Publication still uses its narrower nested target lock for the final transaction.
- Secure dataset filesystem operations are supported on Linux and macOS only.
They require POSIX
torchrir.datasets.build_dynamic_cmu_arctic(DynamicCmuArcticBuildConfig(...))- Builds oobss-compatible scene folders with
mixture.wav,source_XX.wav,metadata.json, andsource_info.json. - Returns
DynamicDatasetBuildResultwith the resolved output root, sample rate, microphone count, and scene paths. - Static layout images (
room_layout_2d.png,room_layout_3d.png) and optional layout videos (room_layout_2d.mp4,room_layout_3d.mp4) are generated, with source-index annotations by default. simulation=SimulationConfig(...)owns every RIR algorithm setting. The default ismax_order=6,nsample=4096, anddtype=torch.float32, withtrajectory_steps=256.- The corpus root and output root must not overlap. Related stems and their mixture receive one common anti-clipping scale and are stored as floating-point WAV, preserving relative gain and additivity.
- Requested duration is converted with
ceil(duration_sec * sample_rate); metadata records both requested and effective sample-aligned durations. - Other defaults include
n_sources=3, moving speed range0.3-0.8 m/s, and motion profile ratios0-35%,35-65%,65-100%.
- Builds oobss-compatible scene folders with
- Local-only (no download) example:
from pathlib import Path from torchrir.datasets import CmuArcticDataset, LibriSpeechDataset cmu = CmuArcticDataset(Path("datasets/cmu_arctic"), speaker="bdl", download=False) libri = LibriSpeechDataset( Path("datasets/librispeech"), subset="train-clean-100", speaker="103", download=False, )
- Full dataset usage details, expected directory layout, and invalid-input handling: Documentation: Datasets
- Geometry:
Room,Source,MicrophoneArray - Scene models:
StaticScene,DynamicScene - Scene-oriented simulation:
torchrir.sim.simulate(scene, config) - Dynamic convolution:
torchrir.signal.DynamicConvolverwith an explicit emission/observation time reference and a scene-carried or call-levelFrameSchedule - Audio I/O:
- wav-specific:
torchrir.io.load_wav,torchrir.io.save_wav,torchrir.io.info_wav - SoundFile-supported formats:
torchrir.io.load_audio,torchrir.io.save_audio,torchrir.io.info_audio - channel/metadata-aware:
torchrir.io.AudioData,torchrir.io.load_audio_data,torchrir.io.save_audio_data
- wav-specific:
- Metadata export:
torchrir.io.build_result_metadata,torchrir.io.save_result_metadata
torchrir.sim: the scene-oriented simulation entry point (the private ISM implementation lives undertorchrir.sim.ism)torchrir.signal: convolution utilities and dynamic convolvertorchrir.geometry: array geometries, sampling, trajectoriestorchrir.viz: plotting and GIF/MP4 animation helpers- Default plot style follows SciencePlots Grid (
science+grid).
- Default plot style follows SciencePlots Grid (
torchrir.models: room/scene/result data modelstorchrir.io: audio I/O and metadata serialization (*_wavfor wav-only,*_audiofor SoundFile-supported formats)torchrir.util: shared math/tensor/device helperstorchrir.logging: logging utilitiestorchrir.config: simulation configuration objects
- Scene typing is explicit: use
StaticScenefor fixed geometry andDynamicScenefor trajectory-based simulation. DynamicSceneaccepts tensor-like trajectories (e.g., lists) and normalizes them to tensors internally.DynamicScene.scheduleoptionally stores the authoritative frame axis as exact CPUint64sample starts. A schedule made withfrom_secondsremembers the sample rate used for conversion and is rejected if it conflicts withRoom.fs.FrameSchedulekeeps immutable starts internally; itsstartsproperty returns a fresh CPUint64snapshot.DynamicConvolverrequirestime_reference="emission"for moving sources with fixed microphones andtime_reference="observation"for fixed sources with moving microphones. Simultaneous motion is rejected because it requires a two-time retarded-propagation model.SimulationConfigowns algorithm settings and must be complete when constructed. Room sampling rate and endpoint directivity live in the scene.RIRResult.configis the single resolved config used by the kernel and records the effective sample count, duration, device, dtype, LUT, and compile settings.- Model dataclasses are frozen but tensor payloads remain mutable.
RIRResultsnapshots every scene tensor and compares its identity and value at consumer boundaries, so mutation through PyTorch or a shared NumPy array is rejected. RIR finiteness is also rescanned on every validation. - A scene requires all geometry Tensors to share one device and dtype. Sequence trajectories inherit their endpoint layout; Tensor trajectories are preserved and mismatches are rejected instead of silently converted.
- Endpoint orientations are stored as unit vectors with shape
(entities, dimensions). Angles use radians. In 2D, use a scalar or(n, 1)for angles and(2,)or(n, 2)for vectors; a length-two value is one shared vector. In 3D, use(azimuth, elevation)or(n, 2)angles and(3,)or(n, 3)vectors. A single row is broadcast to all entities. - Audio I/O uses one SoundFile implementation; there is no process-global backend selector or format override.
import torch
from torchrir import DynamicScene, MicrophoneArray, Room, Source, StaticScene
from torchrir.config import SimulationConfig
from torchrir.sim import simulate
from torchrir.signal import DynamicConvolver, FrameSchedule
room = Room.shoebox(size=[6.0, 4.0, 3.0], fs=16000, beta=[0.9] * 6)
sources = Source.from_positions([[1.0, 2.0, 1.5]])
mics = MicrophoneArray.from_positions([[2.0, 2.0, 1.5]])
scene = StaticScene(room=room, sources=sources, mics=mics)
result = simulate(scene, SimulationConfig(max_order=6, tmax=0.3))
rir = result.rirs
# A scene-owned schedule is reused by the resulting RIRResult.
signal = torch.ones(1, 8000)
src_traj = torch.tensor([[[1.0, 2.0, 1.5]], [[1.5, 2.0, 1.5]]])
mic_traj = mics.positions.unsqueeze(0).expand(2, -1, -1).clone()
schedule = FrameSchedule.from_samples([0, 4000])
dynamic_scene = DynamicScene(
room=room,
sources=sources,
mics=mics,
src_traj=src_traj,
mic_traj=mic_traj,
schedule=schedule,
)
dynamic_result = simulate(
dynamic_scene, SimulationConfig(max_order=6, tmax=0.3)
)
y = DynamicConvolver(time_reference="emission").convolve(signal, dynamic_result)- Room, source, microphone, and geometry-factory coordinates are real floating-point tensors. Their integer inputs are promoted to PyTorch's default floating dtype. Sequence trajectories inherit the endpoint dtype; Tensor trajectories must already match it.
- Room coordinates are 2D or 3D. Source, microphone, and trajectory coordinates
must lie strictly inside the room:
0 < position < room.size. Wall positions are rejected because they create duplicate images and singular paths. - Every source--microphone pair must remain at least
SimulationConfig.min_source_mic_distanceapart (default:1e-6m). - Static positions have shape
(entities, dimensions). - Dynamic trajectories have shape
(frames, entities, dimensions). A 2D(frames, dimensions)trajectory is accepted for one entity. DynamicScene.sources.positionsandmics.positionsequal the first frame of their respective trajectories.DynamicScene.scheduleis optional. When present, it contains one exact integer start per trajectory frame, begins at zero, and increases strictly.from_secondsfloors once and retains conversion-sample-rate provenance; a mismatch withRoom.fsis rejected. Emission-time starts must precede the dry-signal endpoint; observation-time starts may extend into the convolution tail.- Continuous geometry must be sampled on that same frame axis. Use
progress = schedule.normalized_progress(stop_sample=..., dtype=..., device=...)and pass it as the keyword-onlyprogressargument tolinear_trajectory. Frameicorresponds toschedule.starts[i] / stop_sample; the division is evaluated in float64 before the requested dtype cast. If that dtype cannot distinguish the final ratio from one, it is capped at the greatest representable value below one; if two starts would collapse to the same cast value, conversion fails explicitly. The nominal endpoint is therefore never a frame start, and the last sampled frame remains active until the next boundary or timeline end. Source.directivityandMicrophoneArray.directivitydefault to"omni". Non-omnidirectional endpoints require an orientation on the same entity.- Supported canonical patterns are
omni,halfomni,subcardioid,cardioid,hypercardioid, andbidir; common aliases are normalized at construction. - Entity orientation is constant over a dynamic trajectory. Time-varying orientation is not yet supported.
SimulationConfigis keyword-only and validates a complete request at construction. Exactly one oftmax/nsampleand exactly one ofmax_order/nb_imgare required.nb_imgis normalized to an immutable tuple whose length must match the room dimension.- Image-source indices are counted with checked integer arithmetic and streamed
in
image_chunk_sizebatches; the simulation path does not materialize the complete L1/rectangular index grid at once. Room.betawall order is[x-low, x-high, y-low, y-high]in 2D and adds[z-low, z-high]in 3D.- With
tmax, the sample count isceil(tmax * fs). The resolved duration inRIRResult.config.tmaxisnsample / fs, while the final returned sample is located at(nsample - 1) / fs. - Returned RIRs always use the physical propagation-time sample axis. Fractional-delay FIR group delay is an internal implementation detail and late arrivals retain their available support at the requested horizon.
- Sabine conversion uses
12 ln(10) / cin 2D and24 ln(10) / cin 3D. Targets requiring an absorption coefficient above one are rejected. - A diffuse handoff requires
tdiff > 0. Its level is estimated from the preceding 10 ms RMS and joined with a 5 ms power-complementary crossfade. A zero-energy handoff window raises an actionable error; seeded tails are reproducible across output horizons and unrelated batch composition, useRoom.cfor decay estimation, and preserve infinite Sabine T60 as a non-decaying carrier. - RIR high-pass filtering is disabled by default, avoiding hidden CPU
post-processing in accelerator simulations. Enable it explicitly with
high_pass=RIRHighPassConfig(...). Its defaultphase="zero_phase"matches pyroomacoustics: each source/microphone RIR is forward-backward filtered only through its natural ISM horizonmin(nsample, ceil(max_arrival_sample) + (frac_delay_length - 1) // 2 + 2), then zero-filled tonsample. A diffuse tail instead uses the complete requested horizon. Explicitphase="causal"is prefix invariant and filters the complete requested RIR. Supported filter families arebessel,butter,cheby1,cheby2, andellip. This optional SciPy post-process detaches autograd and transfers accelerator results through CPU memory. Chebyshev-I and elliptic filters require positive passband ripple; Chebyshev-II and elliptic filters require positive stopband attenuation; elliptic attenuation must exceed its ripple. - Static RIR tensors have shape
(sources, microphones, samples). - Dynamic RIR tensors have shape
(frames, sources, microphones, samples). torchrir.sim.simulatereturnsRIRResult; its config is aResolvedSimulationConfigcontaining the effective settings.device=Nonefollows scene tensors.device="auto"explicitly chooses CUDA, then MPS, then CPU, even when the scene was constructed on CPU.- RIR simulation supports
torch.float32andtorch.float64; inherited or explicit lower-precision geometry is rejected because image positions, distances, delays, and gains are not numerically safe in float16/bfloat16. MPS rejectsfloat64; MPS resolvesuse_lut=False, and CPU resolvesuse_compile=False. These effective values are stored inRIRResult.config.
For RIR frame selector f, source s, microphone m, output sample n, and
delay sample k, emission-time convolution selects the RIR from the input
sample's emission time:
y_m[n] = sum_s sum_k h_{f_e(n-k),s,m}[k] x_s[n-k]
DynamicConvolver(time_reference="emission") implements this convention for
moving sources and fixed microphones. Schedule starts delimit input samples.
Observation-time convolution instead selects the RIR from the output sample's observation time:
y_m[n] = sum_s sum_k h_{f_o(n),s,m}[k] x_s[n-k]
DynamicConvolver(time_reference="observation") implements this convention for
fixed sources and moving microphones. Schedule starts delimit output samples,
may occur after the dry signal ends, and the final RIR frame remains active
through the convolution tail.
FrameSchedule is independent of the time reference and stores one strictly
increasing CPU int64 start sample per RIR frame. Construct it with
from_samples, from_seconds, uniform, or fixed_hop. Raw RIR tensors and
results whose DynamicScene.schedule is None require an explicit call-level
schedule. When the dynamic scene already owns a schedule, its RIRResult
supplies that schedule automatically and rejects a second explicit schedule to
prevent competing frame axes.
An RIRResult carries enough scene metadata to reject the wrong
motion/time-reference
combination. A raw RIR tensor does not; callers using raw tensors must ensure
that the selected convention matches the simulated motion. Simultaneous source
and microphone motion requires a two-time retarded propagation kernel and is
not implemented.
- Dry convolution signals use
(samples,),(1, samples)for source-broadcast, or(sources, samples). Static convolution accepts RIRs shaped(rir_samples,),(microphones, rir_samples), or(sources, microphones, rir_samples); dynamic RIRs add the leading frame dimension. - Static and dynamic convolution always return
(microphones, output_samples), including a single microphone. - Dynamic convolution uses a required emission-time or observation-time reference and a frame schedule with exactly as many starts as RIR frames.
- Signal and RIR tensors must share device and dtype.
- Audio, dataset, and convolution Tensor payloads accept only
float16,bfloat16,float32, orfloat64; float8 and non-floating payloads are rejected before backend kernels. Geometry constructors may promote integer coordinates as described above. RIR simulation itself accepts onlyfloat32andfloat64geometry. - Public finite-real parameters in simulation configuration, room/acoustics,
geometry construction/sampling, frame-time conversion, audio normalization,
and dataset utilities reject booleans, numeric strings, scalar Tensors, NaN,
and infinity instead of coercing them. Integers too large to represent as a
finite float raise
ValueErrorat the API boundary. - Public count, index, sample-rate, and seed parameters accept non-boolean
integer scalars, including NumPy integer scalars, and normalize them to Python
int. Fractional values and booleans raiseTypeError; operation-specific bounds are checked before tensor allocation or arithmetic. Audio and dataset sample rates are limited to1..2**31-1, simulation sample counts and frame starts fit positive/non-negativeint64, and random seeds fit non-negativeint64. - Source and microphone FFT convolution is batched.
float16andbfloat16inputs usefloat32FFT, source-sum, and overlap-add work buffers, then cast once on return; the implementation remains differentiable, including dynamic emission-time convolution on CUDA. - Audio-file tensors separately use
(samples,)or(channels, samples).AudioDatais keyword-only and validates non-empty, finite, floating-point audio plus a positive integer sample rate. It preserves all channels, sample rate, and subtype;formatis retained as load metadata, while the output path selects the saved format. Its save API does not normalize unlessnormalize=Trueis requested. Normalization computes the scale in float64 before narrowing to the destination storage dtype; an unrepresentable peak or unnormalized sample is rejected before a file is created.save_audio_datareusesAudioData.subtypeonly when the destination container matches the loaded format; otherwise a WAV with no subtype usesFLOAT, preserving values outside[-1, 1]. An integer or companded subtype rejects out-of-range audio before it can be clipped silently. Common suffix aliases such as.wave,.aif,.aifc,.oga, and.sndare resolved to their SoundFile container names. Every pathname argument must be apathlib.Path; string paths are rejected instead of being coerced. AudioInfonormalizes Python/NumPy integer metadata. Its sample rate is in1..2**31-1, frame count in non-negativeint64, and channel count in positiveint32; invalid types and out-of-range values fail separately.DatasetItemis keyword-only and requires finite, non-empty mono floating-point audio, a positive integer sample rate, and a non-empty utterance ID.collate_dataset_itemsrevalidates shallow-mutable items, rejects mixed sample rates/dtypes/devices, and preserves requested item metadata as immutable tuples inCollateBatch.BaseDatasetis abstract. Corpus sentence and attribution records are frozen, slotted, keyword-only values that validate canonical IDs and required fields when constructed.load_dataset_sourcesrequires an explicitspeakers=catalog and adataset_factorythat accepts one selected string ID. It never calls the factory with a hiddenNonesentinel; target length isceil(duration_s * sample_rate).- Tuple-based
load_audio/load_wavreturn channel 0 for multichannel files and emit a warning directing callers toload_audio_data. The format-genericload_audioandload_audio_dataaccept either aPathor a caller-owned open, seekable binary stream, read metadata and samples through one SoundFile handle, and leave that stream open. Other objects raiseTypeError; closed or non-seekable streams raiseValueErrorbefore SoundFile is opened. The WAV-only wrapper requires a.wav/.wavePath.
LoggingConfig is a frozen, slotted, keyword-only record whose level and
formatting fields are validated at construction. setup_logging(config)
configures only the torchrir namespace root, owns at most one marked handler,
and updates that handler's level and formatter on every call. Propagation to
Python's process-wide root logger is always disabled, preventing duplicate
records when the host application has its own logging configuration.
get_logger(name) qualifies child names below torchrir; only torchrir
itself and names beginning with torchrir. are already qualified.
- Metadata uses
{"name": "torchrir.scene", "version": 1}and canonical top-level fieldsschema,generator,room,sources,mics,trajectories,rir,doa,frame_schedule,signal,convolution, anddynamic.generatoridentifies the TorchRIR distribution and PyTorch versions that produced the file. mics.layoutrecordskind(singleorcustom),center, andminimum_pair_distance.rircompresses its physical sample axis intoorigin_sample,sample_count, andsample_raterather than serializing a completetime_axisarray.frame_schedule, when present, contains only exactstarts_samplesandsample_rate; seconds are not duplicated.signalrecords its sample count and rate, whileconvolutionrecords the time reference and output count.build_metadata,build_result_metadata,save_scene_metadata, andsave_result_metadataacceptscheduleandtime_reference; there is no timestamp argument. When convolution metadata is requested, the reference must match the source/microphone motion andsignal_lenis required.- Result-based metadata additionally includes the resolved
simulationobject.source_infois an optional application payload;extrais an optional mapping. Their recursive containers must be acyclic, mapping keys must be strings, numeric values must be real, and floating values must be finite. NumPy values are normalized to JSON values. Passing an explicit emptyextra={}preserves that field; omittingextraleaves it absent. - Scene-output helpers require cross-platform-safe single filenames, validate
optional logger objects before creating directories, and validate
attribution/modification text as non-empty single lines. Attribution notices
render the dataset's explicit
attribution_requiredflag instead of always claiming redistribution attribution is mandatory. - Tensor values in these payloads must be materializable dense-strided, non-nested, non-quantized, non-complex tensors on CPU, CUDA, or MPS. Floating tensors additionally require a supported dtype and finite values; sparse, nested, quantized, complex, and unsupported-device tensors are rejected.
- JSON writes are atomic, reject NaN and infinity, and preserve an existing destination file's permission mode.
- Image-source coordinates, reflection coefficients, path delays, path gains, and fractional-delay accumulation are checked against independent analytic or direct implementations.
- Finite
float64geometry near the dtype limits is evaluated with scaled affine image coordinates, overflow-resistant vector norms and direction normalization, and exponent-scaledfs / cdelay conversion. An image, attenuation, diffuse sample, or metadata value that is not representable fails explicitly; a delay beyond the usable sample domain is excluded before anyint64index cast instead of wrapping into the RIR. - Diffuse handoff RMS uses max-absolute-value scaling, and metadata uses stable microphone centers, pair distances, horizontal distances, and angles. These paths therefore preserve finite extreme and subnormal values without overflowing an avoidable square, sum, or coordinate difference.
- Reflected source directivity mirrors the source orientation component normal to every wall with an odd reflection count.
- Static and dynamic convolution are checked against direct NumPy convolution, including exact frame-schedule boundaries.
- pyroomacoustics comparisons cover 2D/3D rooms, orders 0/1/3, asymmetric wall coefficients, and multiple source/microphone pairs. Its documented fixed 40-sample fractional-delay offset is removed before comparison; cross-correlation is never used to estimate alignment.
- rir-generator 0.3.0 comparisons cover asymmetric walls, orders 0/1/3, and
analytic microphone directivity after only the predetermined
4*piamplitude conversion; both outputs use their physical time axes directly. - gpuRIR CUDA comparisons restrict generated-RIR parity to unambiguous direct
paths and compare
simulateTrajectorywith identical synthetic RIR tensors, so known gpuRIR issues are not promoted to correctness oracles. - Unexpected warnings fail the test suite. Reference, CUDA, MPS, numerical, and slow tests use explicit pytest markers.
- CI measures branch coverage and requires at least 75% overall coverage.
- TorchRIR is under active development. Public contracts are intentionally small, and obsolete compatibility wrappers are removed rather than retained beside canonical APIs.
- Breaking API and numerical changes are recorded in CHANGELOG.md.
- Cached RIRs must be regenerated when the changelog reports a timing, interpolation, geometry, directivity, diffuse-tail, or filtering change.
For detailed documentation: Documentation
Install exactly the committed dependency set, then run the same core checks as CI:
uv sync --locked --all-groups --all-extras
uv run ruff format --check .
uv run ruff check .
uv run ty check
uv run pytest
uv run zensical build --strict
uv buildCI also inspects the built artifacts: the sdist must contain CHANGELOG.md,
and the wheel must declare License-Expression: Apache-2.0.
The documentation site is configured in
zensical.toml
and built with Zensical.
uv sync --locked --only-group docs
uv run --locked --only-group docs zensical serveBefore submitting documentation changes, run the same strict build used by CI:
uv run --locked --only-group docs zensical build --clean --strictPushes to main are published automatically by Read the Docs using the custom
Zensical build in
.readthedocs.yaml.
GitHub Actions validates the documentation but does not publish a GitHub Pages
site.
- Advanced room geometry pipeline beyond shoebox rooms (e.g., irregular polygons/meshes and boundary handling).
Motivation: pyroomacoustics#393, pyroomacoustics#405 - General reflection/path capping controls (e.g., first-K, strongest-K, or energy-threshold-based path selection).
Motivation: pyroomacoustics#338 - Microphone hardware response modeling (frequency response, sensitivity, and self-noise).
Motivation: pyroomacoustics#394 - Near-field speech source modeling for more realistic close-talk scenarios.
Motivation: pyroomacoustics#417 - Integrated 3D spatial response visualization (e.g., array/directivity beam-pattern rendering).
Motivation: pyroomacoustics#397
The external datasets surveyed in
Related Dynamic Speech and Acoustic Datasets
are not currently exposed through torchrir.datasets. Future integration work
should keep corpus download and licensing decisions explicit and should avoid
redistributing third-party recordings.
Common integration work:
- Define a dynamic multichannel dataset manifest for mixtures, reverberant stems, optional dry or close-talk references, sample rates, microphone-array geometry, source and microphone trajectories, orientations, synchronization, provenance, and license metadata.
- Add manifest-backed multichannel dataset adapters that preserve channels and
expose validated timing and geometry records without forcing every corpus
into the current mono
DatasetItemcontract. - Add reusable dataset-building controls for resampling, source onset and offset, relative source level, SIR/SNR, background-noise mixing, arbitrary microphone arrays, and straight or piecewise-linear trajectories.
- Add small synthetic fixtures and schema-validation tests. Full external corpora must remain optional and must not be required by the normal test suite.
Dataset-specific work:
- SonicSet v2: parse its audio, stem, trajectory, and JSON layout and map its coordinate conventions into torchrir records. Exact regeneration also depends on irregular mesh geometry, material-dependent propagation, occlusion, and diffraction support.
- ASA_20k_4s_nspk2-4: add an archive/manifest adapter. Reproduction requires multi-corpus source loading, a four-channel tetrahedral array, source onset/offset scheduling, diffuse background noise, and SNR-controlled mixing.
- WSJ0-Demand-6ch-Move: implement the published generation recipe with straight moving-source trajectories, a six-channel circular array, relative speaker-level sampling, and DEMAND noise mixing. WSJ0 and DEMAND access must be supplied and authorized by the user.
- LOCATA: parse multichannel recordings, close-talk references, voice activity, and OptiTrack position/orientation streams. Task 6 simulation also requires simultaneous source and microphone motion plus time-varying orientation.
- EasyCom: synchronize the six-channel array, close-talk references, voice activity, speaker identity, and pose metadata. Faithful simulation further requires simultaneous motion, time-varying orientation, wearable-device occlusion, near-field speech, and hardware response modeling.
- RealMAN: add multichannel FLAC, source-position, direct-path target, and transcript adapters. Multi-talker separation use also needs an explicit mixture and reference-stem construction policy.
- trajectoRIR: map the supplied audio, RIR, coordinate, speed, and array configuration records into torchrir. Add evaluation utilities that compare measured moving-microphone signals with observation-time convolution.