Skip to content

Apply pre-render object positions and head rotation from the first frame - #24

Open
trsonic wants to merge 2 commits into
mainfrom
fix/obr-initial-object-position
Open

Apply pre-render object positions and head rotation from the first frame#24
trsonic wants to merge 2 commits into
mainfrom
fix/obr-initial-object-position

Conversation

@trsonic

@trsonic trsonic commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Fixes #21.

Problem

As analyzed in #21, OLR and OBR disagree on metadata applied before the first render call. When an audio element is added, OBR registers each object source with the ambisonic encoder at the default position (azimuth 0, elevation 0, distance 1). A position update arriving before the first Process() call only moves the ramp target, so the first rendered block audibly glides the object from front-center to its configured position. OLR applies pre-render metadata immediately, so loudspeaker and binaural output differ over the first block. The same pattern exists in the ambisonic rotator: a head pose set before the first render slerps in from identity.

Fix

Until a component has rendered any audio there is no previously audible state to interpolate from, so updates snap instead of scheduling a ramp:

  • AmbisonicEncoder tracks whether it has processed a block; while it hasn't, SetSource() sets current along with target. This covers every pre-render update path (object channels, loudspeaker-channel positions, gains) since they all funnel through SetSource().
  • AmbisonicRotator does the same for the target rotation on its first processed block.
  • Blocks rendered with head tracking disabled bypass the rotator but are audible at the identity rotation, so ProcessingGroup marks them via MarkAudioRendered(); enabling head tracking with a stored pose after such blocks still slerps instead of snapping.

Updates arriving after audio has been rendered still ramp/slerp across one block to avoid clicks, and mid-stream update semantics are unchanged.

Tests

  • AmbisonicEncoderTest.PreRenderPositionUpdateTakesEffectImmediately — reproduces the Question: Initial object position OLR vs OBR #21 glide on unfixed code (frame 0 carries the front-position coefficients instead of the configured position).
  • AmbisonicEncoderTest.PostRenderPositionUpdateRampsAcrossBlock — guards the click-avoidance ramp after audio has flowed.
  • AmbisonicRotatorTest.FirstProcessedBlockAppliesTargetRotationInFull, RotationAfterBypassedBlocksStillSlerps, RotationChangeAfterFirstBlockStillSlerps — same contract for the rotator, including the head-tracking-disabled case.

Each "first block" test fails without its fix; the existing encoder and rotator tests pass unchanged.

trsonic added 2 commits July 30, 2026 21:41
An object source is registered with the ambisonic encoder at its default
position (azimuth 0, elevation 0, distance 1) as soon as the audio
element is added. A position update arriving before the first render
call therefore only moved the ramp target, and the first processed block
audibly glided the object from front-center to its configured position.
Loudspeaker rendering (OLR) applies pre-render metadata immediately, so
binaural output disagreed with loudspeaker output over the first block.

Track whether the encoder has processed any audio yet; until it has,
SetSource() snaps the current parameters to the new target instead of
scheduling a ramp. There is no previously audible position to
interpolate from, so metadata applied before the first render now takes
effect from the first frame, matching OLR. Updates arriving after audio
has been rendered still ramp across one block to avoid clicks.

Reported upstream as #21.
The ambisonic rotator's current rotation starts at identity and only
the target rotation is supplied per Process() call, so a head pose set
before the first render call made the whole scene slerp from identity
to that pose across the first processed block.

Apply the same rule as for object positions: until any audio has been
rendered there is no previously audible rotation to interpolate from,
so the first processed block applies the target rotation in full from
the first frame. Rotation changes after audio has been rendered still
slerp across the block in 32-frame intervals.

Blocks rendered with head tracking disabled bypass the rotator but are
audible at the identity rotation, so the processing group marks them
via MarkAudioRendered(); enabling head tracking with a stored pose
after such blocks still slerps instead of snapping.

@jingbo-marquis jingbo-marquis left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Question: Initial object position OLR vs OBR

2 participants