TorchRIR
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.
Installation
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 torchrir
Install 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
Library Comparison
| Feature | torchrir |
gpuRIR |
pyroomacoustics |
rir-generator |
dynamic-sound |
das-generator |
|---|---|---|---|---|---|---|
| 🎯 Dynamic Sources | ✅ Emission-time | 🟡 Single moving source | 🟡 Manual loop | ❌ | ✅ Retarded-time | ✅ Emission-time |
| 🎤 Dynamic Microphones | ✅ Observation-time | ❌ | 🟡 Manual loop | ❌ | ✅ Observation-time | ✅ Observation-time* |
| Source + Microphone Motion | ❌ Signal synthesis | ❌ | 🟡 Custom propagation | ❌ | ✅ Direct sound | ✅ Two-time kernel* |
| Shoebox ISM | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| 🖥️ CPU | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| 🧮 CUDA | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
| 🍎 MPS | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 📊 Scene Plot | ✅ | ❌ | ✅ | ❌ | ✅ Paths/arrays | ❌ |
| 🎞️ Dynamic Scene GIF | ✅ | ❌ | 🟡 Manual animation script | ❌ | 🟡 Manual animation script | ❌ |
| 🗂️ Dataset Build | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| 🎛️ RIR Convolution | ✅ Static/dynamic | 🟡 Dynamic helper | ✅ | ❌ | ❌ Signal time warping | ✅ Internal dynamic RIR |
| 🧱 Non-shoebox Geometry | 🚧 Candidate | ❌ | ✅ | ❌ | ❌ | ❌ |
| 🌐 Geometric Acoustics | 🚧 Candidate | ❌ | ✅ | ❌ | ❌ | ❌ |
Legend: ✅ native support, 🟡 manual setup, 🚧 candidate (not yet implemented), ❌ unavailable
Motion rows describe signal synthesis, not just RIR snapshots. dynamic-sound
models direct sound without automatic room reflections; a multiple-source
superposition error was reproduced in the inspected revision. das-generator
implements a two-time ISM kernel, but moving-receiver RIR reuse and startup
errors were reproduced (*). Feature availability does not establish numerical
correctness; pinned revisions, equations, and CPU checks are documented below.
For detailed notes and equations, see Documentation: Library Comparisons.
Continuous Integration
- The main CI workflow runs Linux CPU tests for Python 3.11.4, 3.12, and 3.13, numerical reference comparisons, formatting and type checks, documentation builds, and package checks.
- There is no dedicated CUDA or MPS workflow. Device parity and accelerator autograd tests remain available for manual execution on suitable hardware; passing CPU CI does not establish accelerator correctness.
- The current coverage, proposed CI jobs, missing tests, and implementation order are recorded in the test and CI plan.
Examples
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
Dataset Notices
- For dataset attribution and redistribution notes, see THIRD_PARTY_DATASETS.md.
Dataset API Quick Guide
-
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
Core API Overview
- 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
Module Layout (for contributors)
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
Design Notes
- 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)
Specification
Geometry and scenes
- 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.
Simulation
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.
Dynamic convolution time conventions
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.
Signal and audio shapes
- 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.
Visualization
- GIF/MP4 animation requires an explicit
FrameSchedule,fs, andstop_sample. The display covers[0, stop_sample / fs); trajectory records become active at their exact sample starts and the last record is held through the end, including any requested reverberation tail. Source and microphone trajectories must both match the schedule length. No endpoint is invented. stepretains every positive-integer-th trajectory record at its original start sample. Withfps=None, the output has one frame per retained record. A positive requested FPS selectsceil(stop_sample / fs * fps)output frames; the effective FPS is that count divided by the duration. Geometry is sampled by holding the latest retained record on this uniform output clock, and titles show output time. FPS and frame subsampling never change the requested duration.- MP4 keeps fractional FPS. GIF rounds cumulative frame boundaries to its 10 ms clock, limiting total-duration error to 5 ms; schedules requiring a GIF frame shorter than 10 ms are rejected. Muxed audio must have the same duration as the display timeline. The dynamic CMU builder renders the complete convolved mixture duration and holds the final geometry through its tail.
- MP4 audio muxing preserves the selected channels' gain and rejects empty,
non-finite, or out-of-range (
abs(sample) > 1) stereo samples. It does not normalize automatically. A mono input is duplicated to stereo before channel selection;audio_channelsmust contain two valid non-boolean integer indices. The intermediate WAV usesDOUBLE, avoiding PCM quantization before lossy AAC encoding. Callers must apply any desired common gain explicitly. - Requested visualization failures propagate to the caller, including missing
codecs/audio files and encoder errors. Set
mux_audio=Truewith a mixture path to request audio; silent video is the default. Each PNG/GIF/MP4 is written through a unique temporary directory and replaces its destination only after success. Errors preserve an existing destination and clean up temporary files and owned Figures. Earlier successful outputs in a multi-view call remain. - Source-index annotations follow source positions in both 2D and 3D GIF/MP4
animations.
annotate_sources=Trueis supported in either view; disabling annotations changes only the labels. A 3D label uses the source's full XYZ position.
Logging
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 schema
- 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.
Numerical verification
- 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.
- Automated CI does not require accelerator hardware. CUDA/MPS validation is manual and must record the actual device, runtime versions, and skipped tests; an unavailable device is not evidence of a successful accelerator check.
API stability
- TorchRIR is under active development and does not preserve backward compatibility or provide data migration. Code and repository callers follow the current specification, using the simplest coherent design (KISS).
- Replace obsolete APIs and formats directly; do not retain compatibility wrappers, migration utilities, or tests that only preserve historical behavior or enumerate removed APIs and fields.
- Breaking API and numerical changes are recorded in CHANGELOG.md.
- Regenerate generated data after relevant format or numerical changes. Cached RIRs must be regenerated after changes to timing, interpolation, geometry, directivity, diffuse tails, or filtering.
For detailed documentation: Documentation
Development Verification
Follow the documentation-first development cycle in AGENTS.md: update and reconcile the specification and related documents, write failing tests for the new behavior, implement the minimum necessary change, then remove obsolete code/tests and check documentation consistency. Documentation-only changes and obsolete-test cleanup do not require an artificial failing test. Tests cover current contracts; supported external integrations and recovery from failed filesystem operations remain part of those contracts.
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 build
CI also inspects the built artifacts: the sdist must contain CHANGELOG.md,
and the wheel must declare License-Expression: Apache-2.0.
CPU CI runs Linux Python 3.11.4/3.12/3.13 and macOS Python 3.13 with explicit
accelerator/reference exclusions, required codecs, and retained JUnit reports.
Only Linux 3.11.4 collects coverage (minimum 75%). Quality, pinned comparisons,
documentation, and distribution run separately; actionlint validates all workflows.
Every PR triggers validation. Obsolete CI runs may be cancelled; release runs may not.
CLI integration checks use synthetic local speech fixtures to execute static,
moving-source, and moving-microphone scenarios. JSON/YAML configurations must
round-trip, explicit CLI options override loaded values, and WAV outputs must
agree with metadata and per-source references.
Publication recovery checks kill writers at actual rename/manifest checkpoints
and recover in a fresh process. Recovery must retain a complete old or new tree
and release locks; these checks cover process death, not power-loss durability.
Installed-wheel checks run outside the checkout in two fresh environments.
Base dependencies must support physical direct-path simulation and all three
convolution modes against direct sums; audio/dataset/CLI extras must preserve
multichannel FLOAT WAV samples and expose the installed builder command.
The release workflow calls the same CPU validation at the exact event commit.
It checks tag/project/lock/wheel/sdist version agreement and downloads the
validated artifact for publication without rebuilding. Manual release dispatch
accepts an expected tag and runs validation only; publishing requires a tag push
and successful completion of every validation job.
Documentation Development
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 serve
Before submitting documentation changes, run the same strict build used by CI:
uv run --locked --only-group docs zensical build --clean --strict
Pushes 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.
TODO
Outstanding work identified in the 2026-10-05 audit of commit 9bea4ce.
P1 covers confirmed defects and required validation; P2 extends integration and
hardware coverage; P3 tracks future capabilities. Follow the documentation-first
cycle in Development Verification for each item.
The test and CI plan contains the detailed CI
acceptance criteria; unchecked items remain incomplete.
P1: Confirmed visualization defects
- Fix source annotations in 3D animations. The default
annotate_sources=Truenow uses XYZ text coordinates. Actual 2D/3D GIF and MP4 rendering tests cover annotations enabled and disabled, frame counts, decoding, and HD MP4 dimensions. See animation.py and rendering tests. - Align animation timestamps and playback duration with the exact sample
schedule. Animation callers now pass
schedule,fs, andstop_sample. Tests cover uniform/nonuniform schedules, retained sample starts under subsampling, fractional MP4 FPS, cumulative GIF timing, invalid timelines, and synchronized audio/video through the reverberation tail. See Visualization for the canonical timing contract. - Define and enforce the audio level policy for MP4 muxing. Muxing preserves gain in a DOUBLE intermediate WAV, validates selected stereo channels, and rejects empty/non-finite/out-of-range audio before encoding. Tests cover channel ordering, mono duplication, and sub-PCM-resolution amplitudes. See audio mux tests.
P1: Distribution and automated validation
- Add a release validation gate. The release workflow reuses validation at the event commit, checks tag/project/lock/distribution versions, and publishes the tested artifact. The checker passes a real local build and rejects inconsistent inputs; manual dispatch validates without publishing.
- Exercise the installed wheel outside the checkout in two clean environments. Base dependencies pass direct-path arrival/gain and direct-sum checks for static/emission/observation convolution. Audio/datasets/CLI extras pass FLOAT multichannel WAV round trips and the installed builder command. See wheel smoke checks.
- Implement the CPU CI plan: separate quality, comparisons, and distribution; Linux 3.11.4/3.12/3.13 plus macOS 3.13; actionlint; retained test/coverage reports; one 75% coverage gate; explicit device markers; minimal dependency groups; every-PR validation; complete workflow push filters; and cancellation limited to superseded CI runs. Local CPU validation passes with zero skips. See ci.yml.
P2: Integration and hardware coverage
- Run CLI/examples against small local synthetic speech fixtures. All three modes, JSON/YAML save/reload, explicit overrides, standalone reference sums, and a real builder subprocess are covered. WAV sample rates/counts/channels and time-reference metadata are checked without downloading a corpus. See CLI integration tests.
- Add encoded-media integration and failure handling. Pillow/ffmpeg/ffprobe tests verify frame counts, dimensions, duration, decoding, and requested stereo audio. Rendering/mux failures propagate; per-file atomic replacement preserves existing outputs and cleans temporary files and owned Figures. See failure tests.
- Extend publication recovery tests with actual process interruption. Writers are killed at seven initialization/rename/manifest checkpoints; fresh processes verify complete old/new trees, transaction cleanup, and lock reacquisition. Existing exception/race/synthetic-state tests remain in publication recovery tests.
- Extend manual accelerator validation on actual hardware. Add static and observation-time convolution output/gradient parity, multiple sources and microphones, chunk boundaries, and CUDA eager/compiled accumulation with LUT enabled/disabled. Assert the actual output/config device and record runtime versions and skips. Existing tests cover basic static/dynamic RIR parity and emission-time gradients. Follow the manual accelerator checks; unavailable hardware does not close this item, and a replacement GPU workflow is not required. Deferred for this development cycle because a GPU environment is unavailable.
P3: Acoustic models and spatial visualization
- Extend room geometry beyond shoebox rooms with irregular polygons/meshes and boundary handling. Motivation: pyroomacoustics#393, pyroomacoustics#405.
- Add reflection/path selection controls such as first-K, strongest-K, and energy-threshold-based selection. Motivation: pyroomacoustics#338.
- Model microphone hardware frequency response, sensitivity, and self-noise. Motivation: pyroomacoustics#394.
- Add near-field speech source modeling for close-talk scenarios. Motivation: pyroomacoustics#417.
- Integrate 3D spatial response visualization, including array and directivity beam patterns. Motivation: pyroomacoustics#397.
- Support simultaneous source/microphone motion and time-varying orientation for dataset reproduction scenarios that require them. Specify propagation and time-reference semantics before implementing these extensions.
- Reconcile the ray-tracing/FDTD roadmap wording in the documentation overview with the current acoustic-model TODOs. Decide their intended scope before implementation; the retired placeholder classes do not need to be restored.
P3: Dynamic dataset foundation
The external datasets surveyed in
Related Dynamic Speech and Acoustic Datasets
are not currently exposed through torchrir.datasets. Their integration roadmap
was added in the documentation-only commit 7304a8a. Keep corpus download and
licensing decisions explicit, preserve provenance, and avoid redistributing
third-party recordings.
- 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.
P3: Dynamic dataset integrations
- 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.
The old TODO.md removed in 79946ba had all entries checked. Removed APIs and
compatibility wrappers are not outstanding work. Retain the current numerical,
external-reference, and filesystem-recovery tests; the HPF changes in 4515a27
already include regression tests.
Related Libraries
Metadata
Release files for torchrir 3.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| torchrir-3.0.1.tar.gz | 258.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| torchrir-3.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 411.7 kB
Release files / torchrir-3.0.1.tar.gz
| Download URL | torchrir-3.0.1.tar.gz |
|---|---|
| Size | 258.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c3631aee2f0210a8c0af18940a591b40b86c9f3215eeb43c9c0f6cd2fb16ae1d
|
|
BLAKE2b-256 checksum How to use checksums |
1ede938daec51401996c42e787217e250677af88ec17d81538c2e54575eb1dc9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / torchrir-3.0.1-py3-none-any.whl
| Download URL | torchrir-3.0.1-py3-none-any.whl |
|---|---|
| Size | 153.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c96b272d44fa60673fc94c0861dc9853eaf6c8f90d222c401a65f63fdf4e6868
|
|
BLAKE2b-256 checksum How to use checksums |
794078bf72121b2ab33fcf13caab81fed5970f47f86b16af57269605bc813fcd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|