Skip to main content

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 --plot
  • examples/dynamic_src.py: moving sources, fixed microphones.
    uv run python examples/dynamic_src.py --plot
  • examples/dynamic_mic.py: fixed sources, moving microphones.
    uv run python examples/dynamic_mic.py --plot
  • examples/cli.py: unified CLI for static/dynamic scenes with JSON/YAML configs.
    uv run python examples/cli.py --mode static --plot
  • examples/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 2
  • torchrir.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-dataset
  • examples/benchmark_device.py: CPU/GPU benchmark for RIR simulation.
    uv run python examples/benchmark_device.py --dynamic

Dataset Notices

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 speaker raises TypeError; an unsupported string raises ValueError.
    • 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 raises FileNotFoundError with download=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.
  • 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 subset raises ValueError.
    • 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 FileNotFoundError with download=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.
  • 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 raise NotImplementedError before 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-Length must 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_NOFOLLOW descriptor 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.
  • torchrir.datasets.build_dynamic_cmu_arctic(DynamicCmuArcticBuildConfig(...))

    • Builds oobss-compatible scene folders with mixture.wav, source_XX.wav, metadata.json, and source_info.json.
    • Returns DynamicDatasetBuildResult with 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 is max_order=6, nsample=4096, and dtype=torch.float32, with trajectory_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 range 0.3-0.8 m/s, and motion profile ratios 0-35%, 35-65%, 65-100%.
  • 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.DynamicConvolver with an explicit emission/observation time reference and a scene-carried or call-level FrameSchedule
  • 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
  • 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 under torchrir.sim.ism)
  • torchrir.signal: convolution utilities and dynamic convolver
  • torchrir.geometry: array geometries, sampling, trajectories
  • torchrir.viz: plotting and GIF/MP4 animation helpers
    • Default plot style follows SciencePlots Grid (science + grid).
  • torchrir.models: room/scene/result data models
  • torchrir.io: audio I/O and metadata serialization (*_wav for wav-only, *_audio for SoundFile-supported formats)
  • torchrir.util: shared math/tensor/device helpers
  • torchrir.logging: logging utilities
  • torchrir.config: simulation configuration objects

Design Notes

  • Scene typing is explicit: use StaticScene for fixed geometry and DynamicScene for trajectory-based simulation.
  • DynamicScene accepts tensor-like trajectories (e.g., lists) and normalizes them to tensors internally.
  • DynamicScene.schedule optionally stores the authoritative frame axis as exact CPU int64 sample starts. A schedule made with from_seconds remembers the sample rate used for conversion and is rejected if it conflicts with Room.fs.
  • FrameSchedule keeps immutable starts internally; its starts property returns a fresh CPU int64 snapshot.
  • DynamicConvolver requires time_reference="emission" for moving sources with fixed microphones and time_reference="observation" for fixed sources with moving microphones. Simultaneous motion is rejected because it requires a two-time retarded-propagation model.
  • SimulationConfig owns algorithm settings and must be complete when constructed. Room sampling rate and endpoint directivity live in the scene.
  • RIRResult.config is 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. RIRResult snapshots 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_distance apart (default: 1e-6 m).
  • 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.positions and mics.positions equal the first frame of their respective trajectories.
  • DynamicScene.schedule is optional. When present, it contains one exact integer start per trajectory frame, begins at zero, and increases strictly. from_seconds floors once and retains conversion-sample-rate provenance; a mismatch with Room.fs is 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-only progress argument to linear_trajectory. Frame i corresponds to schedule.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.directivity and MicrophoneArray.directivity default to "omni". Non-omnidirectional endpoints require an orientation on the same entity.
  • Supported canonical patterns are omni, halfomni, subcardioid, cardioid, hypercardioid, and bidir; common aliases are normalized at construction.
  • Entity orientation is constant over a dynamic trajectory. Time-varying orientation is not yet supported.

Simulation

  • SimulationConfig is keyword-only and validates a complete request at construction. Exactly one of tmax/nsample and exactly one of max_order/nb_img are required.
  • nb_img is 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_size batches; the simulation path does not materialize the complete L1/rectangular index grid at once.
  • Room.beta wall 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 is ceil(tmax * fs). The resolved duration in RIRResult.config.tmax is nsample / 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) / c in 2D and 24 ln(10) / c in 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, use Room.c for 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 default phase="zero_phase" matches pyroomacoustics: each source/microphone RIR is forward-backward filtered only through its natural ISM horizon min(nsample, ceil(max_arrival_sample) + (frac_delay_length - 1) // 2 + 2), then zero-filled to nsample. A diffuse tail instead uses the complete requested horizon. Explicit phase="causal" is prefix invariant and filters the complete requested RIR. Supported filter families are bessel, butter, cheby1, cheby2, and ellip. 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.simulate returns RIRResult; its config is a ResolvedSimulationConfig containing the effective settings.
  • device=None follows scene tensors. device="auto" explicitly chooses CUDA, then MPS, then CPU, even when the scene was constructed on CPU.
  • RIR simulation supports torch.float32 and torch.float64; inherited or explicit lower-precision geometry is rejected because image positions, distances, delays, and gains are not numerically safe in float16/bfloat16. MPS rejects float64; MPS resolves use_lut=False, and CPU resolves use_compile=False. These effective values are stored in RIRResult.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, or float64; float8 and non-floating payloads are rejected before backend kernels. Geometry constructors may promote integer coordinates as described above. RIR simulation itself accepts only float32 and float64 geometry.
  • 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 ValueError at 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 raise TypeError; operation-specific bounds are checked before tensor allocation or arithmetic. Audio and dataset sample rates are limited to 1..2**31-1, simulation sample counts and frame starts fit positive/non-negative int64, and random seeds fit non-negative int64.
  • Source and microphone FFT convolution is batched. float16 and bfloat16 inputs use float32 FFT, 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). AudioData is keyword-only and validates non-empty, finite, floating-point audio plus a positive integer sample rate. It preserves all channels, sample rate, and subtype; format is retained as load metadata, while the output path selects the saved format. Its save API does not normalize unless normalize=True is 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_data reuses AudioData.subtype only when the destination container matches the loaded format; otherwise a WAV with no subtype uses FLOAT, 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 .snd are resolved to their SoundFile container names. Every pathname argument must be a pathlib.Path; string paths are rejected instead of being coerced.
  • AudioInfo normalizes Python/NumPy integer metadata. Its sample rate is in 1..2**31-1, frame count in non-negative int64, and channel count in positive int32; invalid types and out-of-range values fail separately.
  • DatasetItem is keyword-only and requires finite, non-empty mono floating-point audio, a positive integer sample rate, and a non-empty utterance ID. collate_dataset_items revalidates shallow-mutable items, rejects mixed sample rates/dtypes/devices, and preserves requested item metadata as immutable tuples in CollateBatch.
  • BaseDataset is abstract. Corpus sentence and attribution records are frozen, slotted, keyword-only values that validate canonical IDs and required fields when constructed.
  • load_dataset_sources requires an explicit speakers= catalog and a dataset_factory that accepts one selected string ID. It never calls the factory with a hidden None sentinel; target length is ceil(duration_s * sample_rate).
  • Tuple-based load_audio/load_wav return channel 0 for multichannel files and emit a warning directing callers to load_audio_data. The format-generic load_audio and load_audio_data accept either a Path or a caller-owned open, seekable binary stream, read metadata and samples through one SoundFile handle, and leave that stream open. Other objects raise TypeError; closed or non-seekable streams raise ValueError before SoundFile is opened. The WAV-only wrapper requires a .wav/.wave Path.

Visualization

  • GIF/MP4 animation requires an explicit FrameSchedule, fs, and stop_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.
  • step retains every positive-integer-th trajectory record at its original start sample. With fps=None, the output has one frame per retained record. A positive requested FPS selects ceil(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_channels must contain two valid non-boolean integer indices. The intermediate WAV uses DOUBLE, 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=True with 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=True is 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 fields schema, generator, room, sources, mics, trajectories, rir, doa, frame_schedule, signal, convolution, and dynamic. generator identifies the TorchRIR distribution and PyTorch versions that produced the file.
  • mics.layout records kind (single or custom), center, and minimum_pair_distance. rir compresses its physical sample axis into origin_sample, sample_count, and sample_rate rather than serializing a complete time_axis array.
  • frame_schedule, when present, contains only exact starts_samples and sample_rate; seconds are not duplicated. signal records its sample count and rate, while convolution records the time reference and output count.
  • build_metadata, build_result_metadata, save_scene_metadata, and save_result_metadata accept schedule and time_reference; there is no timestamp argument. When convolution metadata is requested, the reference must match the source/microphone motion and signal_len is required.
  • Result-based metadata additionally includes the resolved simulation object. source_info is an optional application payload; extra is 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 empty extra={} preserves that field; omitting extra leaves 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_required flag 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 float64 geometry near the dtype limits is evaluated with scaled affine image coordinates, overflow-resistant vector norms and direction normalization, and exponent-scaled fs / c delay 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 any int64 index 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*pi amplitude conversion; both outputs use their physical time axes directly.
  • gpuRIR CUDA comparisons restrict generated-RIR parity to unambiguous direct paths and compare simulateTrajectory with 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=True now 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, and stop_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 DatasetItem contract.
  • 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.

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)

Source distribution for torchrir 3.0.1
File Size Uploaded
torchrir-3.0.1.tar.gz 258.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for torchrir 3.0.1
File Interpreter ABI Platform
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}

Release history Release notifications | RSS feed

This release

3.0.1 This release

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.2

1 release file

0.5.1

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page