Skip to content

Latest commit

 

History

History
184 lines (131 loc) · 8.53 KB

File metadata and controls

184 lines (131 loc) · 8.53 KB

Spatialization & Rendering — Internal Reference

Last Updated: May 12, 2026
Source: source/spatial_engine/spatialRender/SpatialRenderer.cpp, source/spatial_engine/spatialRender/OfflineOutputRouteMap.cpp, source/spatial_engine/src/JSONLoader.cpp

See PUBLIC_DOCS/API.md for the public input-contract summary and DEPENDENCIES.md for the maintainer-level LUSID and layout format spec.


Rendering System

RENDERING.md — Comprehensive reference for the offline spatial renderer (spatialroot_spatial_render).

Overview

Three spatializers supported:

Feature DBAP (default) LBAP
Coverage No gaps (works anywhere) No gaps
Layout Req Any layout Multi-ring layers
Localization Moderate Moderate
Speakers/Src Distance-weighted (many) Layer interpolation
Best For Unknown/irregular layouts AlloSphere, TransLAB
Params --dbap_focus (0.1–5.0) --lbap_dispersion (0–1.0)

Pipeline: Source WAVs + LUSID scene + Layout JSON -> compact internal bus -> layout-derived device-indexed N-channel WAV

Offline output routing now preserves layout channel assignments. The renderer spatializes into a compact internal bus (main speakers + subwoofers), then scatters those internal channels into the final WAV using OfflineOutputRouteMap. Final WAV width is max(channel) + 1; unmapped channels are included and silent. See REALTIME_ENGINE.md § Output Routing Architecture for the shared two-space routing model.

Current scope note: the offline CLI is maintained, but the GUI's Offline Render controls remain intentionally hidden until parity and workflow validation are complete. Treat source/spatial_engine/spatialRender/ as the offline-owned implementation surface.

CLI Usage

# Default render with DBAP
./build/source/spatial_engine/spatialRender/spatialroot_spatial_render \
  --layout source/speaker_layouts/allosphere_layout.json \
  --positions data/processedData/stageForRender/scene.lusid.json \
  --sources data/processedData/stageForRender/ \
  --out render.wav

# DBAP with tight focus
./build/source/spatial_engine/spatialRender/spatialroot_spatial_render \
  --spatializer dbap --dbap_focus 3.0 \
  --layout translab_layout.json \
  --positions scene.lusid.json \
  --sources ./stageForRender/ \
  --out render_tight.wav

# Debug single source
./build/source/spatial_engine/spatialRender/spatialroot_spatial_render \
  --solo_source "11.1" \
  --debug_dir ./debug_output/ \
  --layout allosphere_layout.json \
  --positions scene.lusid.json \
  --sources ./stageForRender/ \
  --out debug_source.wav

Required flags: --layout, --positions, --sources, --out
Spatializer: --spatializer dbap|lbap, --dbap_focus, --lbap_dispersion
General: --master_gain, --solo_source, --t0, --t1, --elevation_mode, --debug_dir

Duration Handling (Feb 16, 2026)

Fixed: C++ renderer now uses mSpatial.duration from LUSID scene instead of inferring from WAV file length. This prevents truncated renders when keyframes end before composition end (e.g., 9:26 ADM → correct 566s render, not truncated 167s).

RF64 Auto-Selection for Large Renders (v0.5.2)

Problem: Standard WAV 32-bit data-chunk size wraps at 4 GB. 56-channel × 566s × 48kHz × 4B = 5.67 GB caused header overflow, making readers report ~166s instead of 566s. Audio data on disk was correct — only the header was wrong.

Fix: WavUtils::writeMultichannelWav() auto-selects SF_FORMAT_RF64 when audio data exceeds 4 GB. RF64 (EBU Tech 3306) uses 64-bit size fields. Falls back to standard WAV for files under 4 GB.

Duration limits at 48 kHz 32-bit float: 56-channel layout → ~6.6 min before RF64 kicks in.

Elevation Compensation

Default: RescaleAtmosUp — maps Atmos-style elevations [0°, +90°] into the layout's actual elevation range. Prevents sources from becoming inaudible at zenith.

Mode CLI Description
RescaleAtmosUp --elevation_mode rescale_atmos_up Default. Maps [0°, +90°] → layout range
RescaleFullSphere --elevation_mode compress Maps full [-90°, +90°] range
Clamp --no-vertical-compensation Hard clip

LFE Handling

  • Sources named "LFE" or node type LFE bypass spatialization.
  • Routed directly to compact internal subwoofer channels, then scattered to the layout-defined subwoofer deviceChannel outputs.
  • Energy divided by number of subs.
  • Gain compensation: dbap_sub_compensation = 0.95 (global — TODO: make configurable).
  • Output buffer auto-sized to max(deviceChannel) + 1 across both speakers and subwoofers.
{
  "speakers": [...],
  "subwoofers": [{ "channel": 16 }, { "channel": 17 }]
}

Robustness Features

Zero-Block Detection & Fallback:

  • Detects when spatializer produces silence despite input energy
  • Fallback: retarget direction 90% toward nearest speaker
  • Threshold: kPannerZeroThreshold = 1e-6

Fast-Mover Sub-Stepping:

  • Detects sources moving >14° (~0.25 rad) within a 64-sample block
  • Subdivides block into 16-sample chunks with per-chunk direction
  • Prevents "blinking" artifacts from rapid trajectory changes

Direction Validation:

  • NaN/Inf check on all directions before use
  • Zero-length vectors → front [0, 1, 0]
  • Warnings rate-limited (once per source, not per block)

DBAP Coordinate Quirk

AlloLib DBAP applies internal transform (x,y,z) → (x,-z,y). directionToDBAPPosition() in SpatialRenderer.cpp compensates automatically — no action needed from callers.

Render Statistics

End-of-render diagnostics (--debug_dir writes render_stats.json, block_stats.log):

  • Overall peak, near-silent channels, clipping channels, NaN channels
  • Direction sanitization summary (clamped/rescaled/invalid counts)
  • Panner robustness summary (zero-blocks, retargets, sub-stepped blocks)

Key Source Files

  • source/spatial_engine/spatialRender/SpatialRenderer.cpp/.hpp — core renderer
  • source/spatial_engine/spatialRender/OfflineOutputRouteMap.cpp/.hpp — offline compact-bus to sparse-output routing
  • source/spatial_engine/src/JSONLoader.cpp/.hpp — LUSID scene parser
  • source/spatial_engine/src/LayoutLoader.cpp/.hpp — speaker layout parser
  • source/spatial_engine/src/WavUtils.cpp/.hpp — WAV/RF64 I/O

Ownership boundary:

  • source/spatial_engine/realtimeEngine/ owns the live playback engine
  • source/spatial_engine/spatialRender/ owns offline rendering
  • source/spatial_engine/src/ owns shared loaders/utilities used by both

Algorithm Details

Interpolation: Block-center SLERP for direction between LUSID keyframes.

Safe Fallback: Last-good direction → nearest keyframe → front (0,1,0).

LUSID Scene Parser (JSONLoader.cpp):

  • JSONLoader::loadLusidScene(path)SpatialData struct
  • Extracts audio_object, direct_speaker, LFE nodes
  • Converts timestamps using timeUnit + sampleRate
  • Source keys use node ID format ("1.1", "11.1")
  • Ignores spectral_features, agent_state nodes

DBAP Field Testing Notes

DBAP-Testing.md — Field testing at Translab (Feb 3–10, 2026) with "Swale", 8.8.2 speaker config (2 subs).

Focus Level Results

Focus Observation
1.0 Best level balance, not very localized
1.5 Sweet spot — localized but slightly dispersed. Preferred listening target from field tests.
2.0 Strong localization but level adjustment needed
2.5 Mix becomes muddy

Takeaways

  • Realtime EngineSession default focus is 1.5 via RuntimeParams::defaults()
  • Offline renderer CLI default --dbap_focus is currently 1.0 in code
  • Use an explicit --dbap_focus value when parity between tools matters
  • Conduct further testing for range 1.1–1.5
  • Subs need energy distribution based on number of subs — currently routed equally to both
  • Sub level at focus 2.0 is 30–40% too loud (DBAP focus increases energy concentration → mains drop, but LFE bypass means subs don't compensate)
  • Possible future: scale sub level based on DBAP focus parameter if listening tests show a persistent main/sub balance shift. This is separate from DBAP normalization; the old focusAutoCompensation path has been removed.

Master Gain Default

Offline renderer master gain defaults to 0.0 dB unity gain (RenderConfig::masterGainDb = 0.0f in spatialRender/SpatialRenderer.hpp; --master_gain help in spatialRender/main.cpp). Realtime RuntimeParams::masterGainDb also defaults to 0.0f.