Skip to content

wav64/opus: support partial (non-frame-aligned) loops#915

Open
meeq wants to merge 1 commit into
DragonMinded:previewfrom
meeq:opus-partial-loops
Open

wav64/opus: support partial (non-frame-aligned) loops#915
meeq wants to merge 1 commit into
DragonMinded:previewfrom
meeq:opus-partial-loops

Conversation

@meeq

@meeq meeq commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Problem

Looping Opus playback with a loop point that is not on a frame boundary hit two issues that together made it crash on the first wrap:

  1. waveform_opus_read() asserted loop_len == len when the decode overshot the waveform tail, so only full loops were handled. The trim it guards (samplebuffer_undo of the overshoot) is valid for partial loops too -- the mixer continues the loop via a separate seeking read at loop_start. Relax the assert to the real invariant: overshoot < frame_size.

  2. The seeking read drops intra_skip leading samples via OPUS_memmove (RSP), which required an 8-byte-aligned RDRAM destination. The mixer's sample buffer does not guarantee that alignment for a large (buffer-spanning) loop, so the destination could be 4-byte aligned and the move hit the RSP assert.

Proposed solution

Generalize OPUS_memmove: when src and dst share a common even non-zero offset, read-modify-write the destination's aligned window (preserving the original head bytes), then hand any remaining aligned chunks to the existing loop. Handles a move of any length; the aligned (offset 0) path is unchanged. Preconditions are asserted under RSPQ_DEBUG and are strictly weaker than the previous full-alignment ones, so existing callers are unaffected (the decoder's internal int32 move is always 8-byte aligned).

Looping Opus playback with a loop point that is not on a frame boundary
(an "intro'd" loop that should not replay its intro) hit two issues that
together made it crash on the first wrap:

1. waveform_opus_read() asserted loop_len == len when the decode overshot
   the waveform tail, so only full loops were handled. The trim it guards
   (samplebuffer_undo of the overshoot) is valid for partial loops too --
   the mixer continues the loop via a separate seeking read at loop_start.
   Relax the assert to the real invariant: overshoot < frame_size.

2. The seeking read drops intra_skip leading samples via OPUS_memmove (RSP),
   which required an 8-byte-aligned RDRAM destination. The mixer's sample
   buffer does not guarantee that alignment for a large (buffer-spanning)
   loop, so the destination could be 4-byte aligned and the move hit the
   0x8509 RSP assert. Generalize OPUS_memmove: when src and dst share a
   common even non-zero offset, read-modify-write the destination's aligned
   window (preserving the original head bytes), then hand any remaining
   aligned chunks to the existing loop. Handles a move of any length; the
   aligned (offset 0) path is unchanged. Preconditions are asserted under
   RSPQ_DEBUG and are strictly weaker than the previous full-alignment ones,
   so existing callers are unaffected (the decoder's internal int32 move is
   always 8-byte aligned).

Validated with a standalone harness across all four cases:
{aligned, unaligned} x {single-chunk, multi-chunk}.
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.

1 participant