Custom speaker layouts¶
Built-in presets remain the recommended path for ordinary renders:
openjoc render-joc input.m4a --layout 7.1.4 --output render.wav
Advanced users can supply a versioned JSON layout without changing the renderer or creating a second DSP path:
openjoc render-joc input.m4a \
--layout-file fixtures/speaker-layouts/studio-irregular.json \
--output render.caf
--layout and --layout-file are mutually exclusive. A custom layout is
ordered exactly as its speakers array; that order is the interleaved PCM
order and the semantic label order reported by the Rust and C APIs.
Format and coordinates¶
The current format is JSON version: 1:
{
"version": 1,
"name": "My Studio",
"speakers": [
{"name": "FL", "azimuth": 35.0, "elevation": 0.0},
{"name": "FR", "azimuth": -35.0, "elevation": 0.0},
{"name": "Sub", "azimuth": 0.0, "elevation": -20.0, "role": "lfe"}
]
}
The renderer uses the existing OpenJOC normalized Cartesian convention. In
the spherical input form, azimuth is in degrees, positive toward the OpenJOC
left side, with 0 straight ahead; elevation is in degrees, positive above
the listener. Valid ranges are azimuth -180..=180 and elevation
-90..=90. Internally, front/rear is y=0..1, left/right is x=0..1, and
bottom/top is signed z=-QMAX..QMAX. There is no second coordinate system in
the projector.
role is full_range by default or explicitly lfe. LFE channels are
logical output channels, remain in the declared order, and are not spatially
panned. The decoded base LFE is copied to each declared logical LFE output,
matching the existing multiple-LFE preset behavior. This feature does not
add crossover, bass management, delay, gain calibration, or room correction.
The implementation admits up to 64 output channels and requires at least two full-range speakers. Names must be unique and non-empty. Coordinates must be finite and in range; duplicate or near-degenerate full-range directions, empty layouts, malformed JSON, unknown fields, and unknown versions are rejected before rendering. JSON numbers are never allowed to become NaN or Infinity PCM.
Projection coverage policy¶
The existing generic projector defines coverage for structurally valid custom
layouts; it does not add a second fallback renderer. For finite source
coordinates outside a layout's rectangular support, x is clamped to the
first/last anchor in the selected row and y is clamped to the first/last row.
For multi-layer layouts, z is clamped to the lowest/highest layer. Between
adjacent layers, target vectors are blended with the existing equal-power
cosine/sine law. The resulting dynamic target is normalized by the existing
projector, so supported finite sweeps remain deterministic, finite, and
bounded. Structurally unusable layouts are rejected during construction;
finite out-of-bound source positions are defined boundary projections rather
than undefined behavior.
API and container boundary¶
Rust callers can construct the same validated object directly:
use openjoc_api::{OpenJocConfig, OpenJocSession};
use openjoc_scene::{SpeakerGeometry, SpeakerLayout};
let layout = SpeakerLayout::custom(
"studio",
vec![
SpeakerGeometry::full_range("A", -40.0, 0.0),
SpeakerGeometry::full_range("B", 8.0, 6.0),
SpeakerGeometry::full_range("C", 48.0, 0.0),
],
)?;
let session = OpenJocSession::new(OpenJocConfig::default().with_speaker_layout(layout))?;
The C ABI 1.4 appends custom_speaker_layout to
openjoc_decoder_config. It points to an ordered array of
openjoc_custom_speaker records and is copied/validated during decoder
creation. Existing preset callers can leave it null. The ABI does not require
temporary JSON files.
For custom physical layouts, WAV is written as deterministic, truthful unmasked PCM in the declared channel order; a standard WAVEFORMATEXTENSIBLE speaker mask would falsely claim standard identities. CAF is recommended when downstream interchange must preserve geometry because OpenJOC writes coordinate channel descriptions there. Downstream players, FFmpeg channel-layout negotiation, GStreamer, DirectShow/LAV, and physical devices may still have narrower geometry contracts; renderer support does not imply host/device support.