OpenJOC production architecture¶
This is the canonical description of the current production data flow. It describes implemented boundaries, not a promise that every historical design goal is complete.
OpenJOC implements its spatial rendering DSP directly rather than delegating object rendering to platform-specific spatial audio engines. Operating-system audio APIs may be used for integration and I/O, but they do not define OpenJOC's spatial rendering result. One renderer. Same spatial semantics across platforms.
The CLI, GStreamer plugin, and external FFmpeg bridge are transport frontends
over the same OpenJocSession; they do not contain separate render pipelines.
GStreamer owns its buffer/caps/segment lifecycle. The FFmpeg bridge uses
libavformat for demux and public libavutil AVFrame allocation. In both cases
OpenJOC retains E-AC-3/JOC decode, scene construction, DRC/dialnorm, speaker
rendering, binaural HRTF rendering, latency, and drain state.
Complete access-unit classification¶
openjoc-api::admission owns only stateless inspection of one complete AU:
indexing, grouping, audio-frame syntax, JOC carrier parsing and decoder topology.
The adapters select an explicit metadata policy:
- FFmpeg uses
StrictOrObservedVendor, trying ETSI strict validation before the existing observed-vendor compatibility profile - GStreamer uses
FirstJocPayload, preserving its direct first-payload check without introducing EMDF profile validation into its classifier
These policies intentionally remain different. For example, a syntactically
complete carrier with mismatched payload group IDs or only a JOC payload is
rejected by FFmpeg's profile classifier but can be classified as JOC by
GStreamer's payload classifier. Classification is not permission to bypass
the subsequent OpenJocSession admission rules. Profile rejection remains
separate from parser errors, and the FFmpeg wrapper retains original error
text and frame-count facts.
parse_access_unit_bounds continues to own AU boundaries in openjoc-eac3.
FFmpeg staging/EOF, GStreamer parsing/caps, browser chunks/CMAF and LAV
negotiation remain adapter-owned. The shared classifier does not manage any
of their state. The
149-case policy contract
freezes both adapters' complete pre-extraction classification transcripts.
Data flow¶
The explicit render-scene workflow is implemented in openjoc-render-scene.
It depends on openjoc-render, openjoc-sofa, and openjoc-wave; it is
deliberately separate from openjoc-scene, so decoder metadata and
ReconstructionBasis rows cannot enter the caller-bound source contract.
raw EC-3 / seekable ISO BMFF
│
▼
input/container ownership and access-unit delivery
│
├── E-AC-3 base decode ──► channel-labelled PCM / RcLfe
│
└── EMDF payloads
├── OAMD ──► metadata objects and timed state
└── JOC ──► reconstruction-basis rows
├──► codec-domain JOC bridge
│ (T(t) remains unresolved)
│
└──► exact clean binding gate
│
▼
bound decoded JOC Objects
│
▼
reconstructed dynamic ADM
The binding branch is deliberately narrower than the decoder and renderer branches. OAMD supplies decoded dynamic metadata; JOC supplies decoded object audio rows. Only the exact admitted carrier profile pairs them. Other scene consumers still receive metadata and decoder-coordinate rows without an automatic authored-object interpretation.
The reconstructed ADM branch establishes decoded-scene and ADM structural correctness within that profile. It does not recover the authored Atmos master or prove perceptual equivalence with a native JOC final renderer; native JOC playback remains the reference for renderer-identical localization.
The render-joc workflow adds an explicit experimental speaker branch after
the decoded component boundary:
raw EC-3 / seekable ISO BMFF
↓
bounded AU delivery → E-AC-3 Base + RcLfe + JOC/OAMD decode
↓
decoded Base/RB codec-coordinate bundle + automatic bridge-control assembly
↓
persistent JocSpatialBridge → active N-channel speaker planes
├── Base LFE/RcLfe → LFE plane only
└── active planes + LFE → shared final linked speaker gain
↓
incremental semantic WAV/CAF output
The CLI presets are data-only registrations over the generic SpatialLayout
and JocSpatialBridge projection path. The public library can consume a
caller-defined channel registry, geometry, and output order without a
preset-specific projection algorithm; the CLI accepts the same versioned
geometry through --layout-file.
The CLI keeps its speaker/binaural rendering and F64 intermediate processing
in joc_render, while its private joc_output module owns output staging,
container selection, channel descriptions and incremental WAV/CAF writers.
The existing publication order, overwrite rules, failure cleanup and error
text are covered by the
complete-file output contract.
The paired ADM publication transaction remains separately owned by main.rs.
This file-output boundary does not alter session precision, queues or FIR/LFE
tail handling.
The 22.2 preset is ITU-R BS.2051-3 Sound System H (9+10+3): a four-layer
bottom/middle/upper/top topology with 22 spatial speakers and two semantic
LFE destinations. It uses the same N-layer point projector and semantic
operation boundaries as the established layouts; LFE channels are never
projection vertices.
Automatic assembly derives codec-coordinate control from validated OAMD and
decoded Base/RB state. A complete sidecar is optional and takes precedence as
an explicit override/test source; automatic and explicit sources are not
implicitly merged. Topology/count, coordinate dimensions, metadata updates,
and Base topology changes are validated at the integration boundary; no
guessed row/object renderer is constructed. The public semantic PCM order is
selected by the explicit preset; exact speaker WAV masks remain backend
specific. 5.1 remains FL, FR, FC, LFE, Ls, Rs.
The parser reads what is present in the carrier. Validation then applies an explicit profile. The decoder consumes an accepted representation; it does not hide vendor compatibility decisions.
Layer boundaries¶
Input and container¶
Raw E-AC-3 uses the in-process incremental reader. Ordinary seekable ISO BMFF uses a sample cursor with container ownership kept separate from the AU consumer. Non-seekable and fragmented MP4 are outside the current contract.
E-AC-3 base¶
The base decoder keeps frame, audio-block, coupling, SPX, AHT, rematrix,
substream and TDAC state explicit. Channel labels and RcLfe are retained as
base-carried information; RcLfe is not a dynamic reconstruction row.
OAMD and profiles¶
OAMD metadata is parsed into typed state and timed updates. ETSI_STRICT
enforces the published validation rules. OBSERVED_VENDOR_COMPAT is explicit and
partial: it preserves original metadata and records deviations, but does not
assign meaning to unresolved vendor continuation.
User-facing decode and decode-payload commands expose a separate AUTO
selection policy. It parses once, evaluates strict validation first, and uses
the existing compatibility policy only when every blocking deviation is
already whitelisted. Malformed, unsafe, unknown, and non-whitelisted failures
remain failures. Selection diagnostics include the requested and selected
profiles, strict status, deviation set, and reason. AUTO is not a parser or
renderer profile; explicit ETSI_STRICT never falls back, while normative
inspection remains strict by purpose.
The observed OAMD warp_mode value raw=3 remains reserved under ETSI strict
parsing. The exact observed compatibility profile preserves raw3 as opaque and
may use the decoded OAMD spatial metadata directly for the tested scene scope;
no production alias, transform, offset, or trim guess is present.
Scene and binding¶
ObjectScene retains metadata objects, timed positions, and decoder-coordinate
rows as separate domains. For the exact admitted decoded-JOC/OAMD profile,
SemanticBindingState::ResolvedWithinCarrier records a carrier-local pairing
for the reconstructed ADM path. For all other profiles it remains
Unresolved; there is no implicit row == authored object, slot identity, or
dominant-row fallback.
JOC ReconstructionBasis¶
JOC reconstruction produces rows with structural indices and deterministic
numerical behavior. The rows remain decoder coordinates and are not authored-
object PCM. In the exact admitted carrier profile, a row also has a scoped
decoded JOC Object interpretation for the dynamic ADM path; that does not
turn it into an authored stem. Diagnostic WAV export uses
diagnostics/reconstruction_rows/row_NNN.wav.
The stable component boundary uses ReconstructionBasisRowIndex as a local
decoder-coordinate identity. DecodedComponentLayout and the CLI
diagnostics/components.json manifest distinguish Base full-band channels,
separate Base LFE, indexed RB rows, and SemanticBindingState::Unresolved
or its admitted carrier-local counterpart without retaining another PCM copy.
Operations requiring authored-object audio
identity fail explicitly even when decoded-object binding is admitted;
component-domain decode and streaming remain available.
JOC spatial reconstruction bridge¶
openjoc-scene exposes JocSpatialFrameBridge and the versioned
openjoc.joc-spatial-reconstruction.v1 codec-domain contract. A borrowed
CodecBasisBlock carries explicitly labelled Base full-band PCM, indexed
ReconstructionBasis rows, and separate RcLfe; JocSpatialMetadataFrame carries
the current OAMD payload and structural programme dimensions; and
SampleRange gives each committed decoder frame an absolute half-open sample
interval. The bridge is streaming and retains no duration-proportional PCM.
The semantic operation is modelled as o(t) = T(t)c(t) followed by the
independent renderer operator. T(t) is not known: JocSpatialOperatorState
therefore remains Unresolved, and require_resolved_operator() is a hard
gate. There is no automatic conversion from decoded components to
ExplicitSpatialScene. The scoped decoded-object binding resolves only the
carrier-local association between decoded JOC row j and decoded OAMD dynamic
ordinal j for reconstructed ADM; it does not resolve T(t), a
speaker-rendering operator, or an authored source identity. There is no fixed
RB-row/authored-object mapping and no implicit matrix or permutation. The
readiness census remains a repository artifact and is available as
docs/joc_reconstruction_readiness.json.
The explicitly activated JocSpatialBridge is a downstream spatial function
with experimental maturity. It consumes a losslessly retained topology/
coordinate snapshot, projects into a caller-supplied public layout, applies the
Q32 gain scheduler, and accumulates linearly into caller-owned buffers. It does
not change profile validation, assign authored-object identity, or resolve
SemanticBindingState for authored or renderer semantics; its raw warp-3 field
is retained as opaque data and excluded from projection arithmetic. The
supported ordinary domain and activation surface remain an experimental
implementation boundary. The E-AC-3 JOC overview shows
how this branch relates to reconstructed ADM export.
After Base and ReconstructionBasis contributions have been accumulated into the final semantic speaker planes, the shared renderer applies a causal, common FinalLinkedGain stage for the admitted 48-kHz 32-sample adapter blocks. It includes active LFE in the linked channel set, adds one block of speaker output history, and is reset with the stream/timeline lifecycle. The stage is not applied to the SOFA binaural path; Base downmix overload protection and pre-gain contribution linearity remain separate contracts.
Base full-band scene-composition gate¶
Decoded non-LFE Base/full-band channels are known JOC reconstruction inputs.
That fact is separate from whether an independent Base contribution survives
into the final JOC delivery scene. The experimental bridge may accumulate Base
coordinates and ReconstructionBasis rows together, but that codec-basis
composition is not proof of the final-scene equation while T(t) remains
unresolved. In particular, strong energy in decoded Base C is not evidence that
Base C should be exported a second time, and the original authored Bed is not
used to explain or authorize that export. Base-plus-Objects must pass an
explicit double-counting proof before ADM scene composition changes.
Explicit spatial renderer¶
The openjoc-render crate is a separate Layer-A/Layer-B foundation. It accepts
only caller-supplied ExplicitSpatialSource blocks with an opaque source ID,
mono PCM, explicit Cartesian position, and explicit linear gain. Its initial
renderer maps the front horizontal hemisphere to FL, FR with a public
equal-power law and mixes borrowed blocks into caller-owned floating-point
buffers. It has no dependency on openjoc-scene, DecodedJocComponents, or
ReconstructionBasis, so unresolved decoder rows cannot become authored
spatial sources through an implicit conversion.
The initial StereoRenderer rejects rear-hemisphere and undefined horizontal
directions, ignores elevation for stereo, performs no distance/room/occlusion
processing, and does not clip by default. SpeakerLayout2d and
LayoutRenderer2d add arbitrary validated horizontal layouts with deterministic
adjacent-pair, checked 2x2 VBAP-style gains. The caller's speaker order is the
public planar output order; unsupported angular gaps fail explicitly. The 2D
renderer ignores elevation and has no LFE/bass-management path. The separate
binaural renderer and experimental JOC spatial bridge do not change this 2D
contract or provide automatic JOC semantic binding.
SpatialState2d, TrajectorySegment2d, and SourceTrajectory2d add an
explicit, piecewise-linear automation contract. Segment endpoints are inclusive
absolute sample indices; azimuth follows an explicit shortest/increasing/
decreasing path policy and source gain is interpolated linearly in the linear
domain. StereoRenderer::render_trajectory_block and
LayoutRenderer2d::render_trajectory_block evaluate that state per sample, so
one block, irregular blocks, and one-sample blocks have the same result for the
same absolute timeline. Trajectory blocks borrow PCM and caller-owned output
planes, perform bounded preflight validation, and allocate neither per sample
nor for the full timeline. The trajectory is directional only: radius, z,
distance, Doppler, room effects, elevation, and HRTF are not rendered.
Speaker3d, SpeakerTriplet3d, and SpeakerLayout3d add an explicit
three-dimensional topology contract. The caller supplies the public speaker
order and every admissible VBAP triplet; LayoutRenderer3d never performs
Delaunay, hull, coverage, or “best triplet” inference. Each declared triplet
is solved as the public 3×3 system S g = p, with finite/non-singular checks,
non-negative gain checks, and unit-energy normalization. Exact speaker hits
are deterministic one-hot gains. If multiple declared triplets cover a
direction, their complete public-order gain vectors must agree or rendering
fails with an ambiguity error. Partial layouts fail explicitly for unsupported
directions, and LFE/bass management remains outside this renderer contract.
The 3D renderer accepts only explicit sources and caller-owned planar f64
outputs. SpatialState3d, TrajectorySegment3d, SourceTrajectory3d, and
TrajectorySourceBlock3d add an additive sample-accurate dynamic path over
that same immutable topology. Each segment uses shortest great-circle SLERP
between canonical unit directions, a stable small-angle branch, linear gain
interpolation, and explicit rejection of antipodal ambiguity. Callers supply
intermediate keyframes for longer routes; no path inference is performed.
LayoutRenderer3d::render_trajectory_block evaluates absolute sample indices
and preserves static output equivalence, endpoint/keyframe continuity, and
byte-identical block-partition invariance. It preflights every sample before
clearing caller-owned planar f64 outputs and performs no per-sample heap
allocation. Distance, Doppler, listener orientation, room effects, LFE,
HRTF/binaural rendering, JOC, ObjectScene, and authored-object bridges remain
outside the contract.
HrirPair, HrirEntry, and HrirBank provide a compact caller-supplied
exact-direction HRIR contract, with optional explicit per-ear delay metadata
retained for construction-time interpolation. StaticBinauralSource binds a
fixed explicit source ID, canonical direction, linear gain, and HRIR entry; it
does not infer authored-object identity. BinauralRenderer uses the fixed
listener convention (+Y forward, +X right, +Z up), emits LEFT_EAR then
RIGHT_EAR, and performs direct causal time-domain FIR convolution. It
preserves leading HRIR delay, keeps bounded per-source history across
caller-owned input blocks, and exposes explicit tail draining and reset
semantics. openjoc-sofa resolves exact directions first and performs bounded
delay-aligned spherical interpolation before registering static sources.
PartitionedBinauralRenderer is an additive,
caller-selected uniform overlap-add backend: its fixed power-of-two partition
P uses a 2P FFT, reports one-partition scheduling latency, accepts exactly
P-sample input partitions plus one explicit final partial operation, and
drains exactly the largest registered M-1 causal tail. It precomputes HRIR
spectra and keeps only bounded filter-length frequency/time state; no
duration-proportional PCM history or adaptive backend selection is used.
Direct FIR remains the numerical oracle, so cross-backend validation is
numerical rather than a promise of bit identity.
The separate openjoc-sofa crate is a construction-time, read-only adapter
from a deliberately narrow SimpleFreeFieldHRIR SOFA contract into
HrirBank. It depends on openjoc-render; the renderer remains independent
of file parsing, NetCDF/HDF5 libraries, and OS-specific APIs. The current
portable reader accepts the project-tested NetCDF classic CDF-1 and NetCDF-4/HDF5 SOFA subsets, fixed
listener pose, spherical degree/degree/metre source positions, exactly two
receivers, common sample rate, and integer sample delays. Receiver geometry,
not array order, determines left/right ear mapping. Exact lookup is preferred;
non-exact requests use a deterministic local spherical segment/triangle with
shared ear weights, while sparse/outside coverage fails closed. After
construction no SOFA file handle is retained and neither renderer performs
file I/O per audio block. The SOFA adapter parses CDF-1 or HDF5 while it builds
the bank. Custom HRIRs are resampled to the renderer rate once at setup; banks
already at that rate retain their original taps. Moving sources, SOFA writing, and JOC semantic
bridging remain outside this boundary.
Experimental device-independent listener orientation¶
openjoc-api adds an explicit opt-in control path above the same virtual-
speaker binaural renderer. A cloned ListenerOrientationPreparer owns shared,
immutable HRTF/layout resources and resolves all non-LFE HRIRs away from the
render call. It interprets a normalized (x, y, z, w) active listener-local-
to-scene quaternion by inverse-rotating each fixed world-space speaker
direction, then uses the orientation-specific bounded exact/interpolation
resolver. This follows the public quaternion rotation identity and the
existing SOFA spherical interpolation contract; the ordinary static resolver
is unchanged.
The prepared update contains the complete ordered speaker kernel set, a resource/layout fingerprint, stream epoch, and monotonic sequence. Admission checks these fields atomically. An active shared 240-sample linear crossfade (5 ms at 48 kHz) preserves each source's causal input history; one latest target may wait during the fade. The dynamic path retains full HRIR taps up to its resource-derived limit, capped at 8,192 taps, and rejects the whole update if any direction is uncovered or any pair exceeds it. It does not shorten a filter, resample an already-48-kHz built-in bank, reduce the virtual speaker set, or alter the speaker projection or gain policy. Identity-orientation PCM is regression- checked against the static path.
An additional opt-in pull surface queues at most one 1,536-sample projected virtual-speaker AU and binauralizes at most 1–256 samples per receive. This lets a host submit pose targets between chunks; an active 240-sample crossfade can defer the next target. The compressed bridge retains its lazy positive-JOC admission. It does not read a sensor, timestamp poses, own an audio device, guarantee callback timing, or reduce the independent HRIR-preparation time. The default API, CLI, WASM and DirectShow/LAV output remain static. See the Rust API, C ABI, and known limitations.
Capture and streaming¶
Capture mode may retain metadata and diagnostic artifacts. Streaming mode uses bounded AU/frame state and incremental output finalization; it does not silently capture an unbounded ObjectScene or reconstruction-row vector. Malformed or truncated raw E-AC-3 and consumed ISO BMFF structures are rejected with bounded diagnostics; streaming output is staged and promoted only after a complete successful decode, so failure cannot publish a canonical partial.
Session lifecycle invariants¶
The private drained flag records that drain has started, or that the session
has failed closed. It does not by itself mean that output has been exhausted.
Pending output comprises three distinct stores: owned PCM frames, deferred
virtual-speaker blocks, and the partially rendered current pull block. The
deferred sample counter covers only the unrendered samples in the latter two
stores. A pull-session FIR/LFE tail is separate from all three stores and is
eligible for receiving only after drain starts.
| State or operation | Result and preserved transition |
|---|---|
| Fresh, or successfully flushed/reset | Drain not started; no queued output; not drain-complete |
| Open session with any pending output | Push and drain return OutputPending; a requested discontinuity does not reset state |
| Ordinary receive | Pops one owned PCM frame; pull sessions use the separate bounded receive method |
| Bounded pull receive | Consumes deferred/current input first and decrements its remaining sample count; after drain starts it can consume the FIR/LFE tail |
| Drain with no pending output | Marks drain started, then emits reconstruction and binaural tails; an empty session reaches EndOfStream |
| Drain started, pending output remains | Push returns AlreadyDrained; another drain returns OutputPending; receiving continues |
| Drain started, only a pull tail remains | Another drain returns FrameAvailable; completion stays false until the tail is exhausted |
| Drain started, all output exhausted | Completion is true; repeated drain returns EndOfStream; push still returns AlreadyDrained |
| Failed closed | Clears all three output stores and the deferred sample count, marks drain started, and records the terminal error; completion is true even if internal FIR history remains |
| Successful flush/reset | Clears queued output before resetting decoder/render/timeline state, advances an orientation epoch, then clears the drain and terminal flags |
| Accepted discontinuity on an open, output-free session | Resets stream state before timestamp validation and decoding; pending, drained, and terminal guards take precedence |
Guard priority is part of the compatibility contract: push checks terminal
error, drain-started, pending output, empty/size checks, then discontinuity and PTS;
drain checks terminal error, pending output, then drain-started; bounded pull
checks terminal error before its mode requirement. Reset failure remains
fail-closed. The original typed terminal error is retained privately while
repeat calls preserve their historical public Render wrapper and full text.
Queue clearing does not itself reset FIR history, timestamps, epochs, or the
terminal flag; the calling lifecycle operation owns those ordered steps.
Scoped decoded-JOC/OAMD binding¶
The payload boundary has one explicit clean-room structural admission gate for
the ordinary strict profile and the exact observed raw3-compatible profile: 15
decoded JOC objects, no OAMD bed, one Base LFE at total index 0, no ISF, 15
dynamic OAMD objects, and 16 total OAMD entries. Only after this gate passes
does the canonical typed mapping produce joc_ordinal = dynamic_ordinal and
oamd_total_index = joc_ordinal + 1.
The +1 is centralized in DecodedJocBindingProfile; it is not an element-ID
lookup, audio-content match, or PCM heuristic.
SemanticBindingState::ResolvedWithinCarrier means only that decoded JOC PCM
and decoded OAMD metadata are paired inside this admitted carrier profile. It
does not recover original authored ADM identity. Bed-bearing, ISF-bearing,
alternative-LFE, count/order-mismatched, unknown-compatibility-deviation, and
incomplete-Base-LFE cases remain unresolved; inactive transitions are not
admitted by the dynamic ADM metadata exporter.
The dynamic ADM path reuses the existing absolute scene sample domain. It
retains metadata events for the export plan but never duplicates programme PCM:
each generated OpenJOC Reconstructed JOC Object NN track consumes its bound
reconstruction row during the existing bounded streaming write, while its ADM
blocks are derived from the corresponding OAMD event boundaries. Reset,
discontinuity, flush, and stream reopen create a fresh decoder/scene epoch;
stale metadata and PCM are not combined.
Error and evidence model¶
Malformed input, unsupported container shapes, strict profile violations and output failures are classified separately. A diagnostic or empirical result cannot upgrade semantic binding. Current claim boundaries are summarized in the capability matrix and known limitations.