mirror of
https://github.com/sal063/AC6_recomp
synced 2026-08-21 23:00:53 -04:00
apu: guard the XMA loop-end frame test against degenerate loop metadata
The loop-end test runs before UpdateLoopStatus, so the degenerate-loop guard there never reaches it. With loop_count > 0 and loop_start >= loop_end, the clamped loop end equals the start offset of the stream at packet 0, arming the frame output limit and silently truncating 384 samples from its first frame. Cvar audio_xma_loop_end_guard, default on.
This commit is contained in:
@@ -84,6 +84,19 @@ REXCVAR_DEFINE_BOOL(audio_xma_header_straddle_fix, true, "Audio",
|
||||
"combs on the stereo fold - the long-reported doubled/"
|
||||
"robotic cutscene dialogue. Off restores the legacy "
|
||||
"walk for A/B.");
|
||||
REXCVAR_DEFINE_BOOL(audio_xma_loop_end_guard, true, "Audio",
|
||||
"Apply the degenerate-loop guard (see "
|
||||
"audio_xma_loop_guard) to the loop-end frame test as "
|
||||
"well. That test is evaluated before the loop-status "
|
||||
"update, so the guard there never reaches it: with "
|
||||
"loop_count > 0 and loop_start >= loop_end (metadata "
|
||||
"AC6's streamed voices carry), the clamped loop_end "
|
||||
"equals a voice's starting read offset for the stream "
|
||||
"that begins at packet 0, arming the frame output limit "
|
||||
"and silently discarding 384 samples of that stream's "
|
||||
"first frame - permanently offsetting one context of a "
|
||||
"multi-stream voice from its siblings. Off restores the "
|
||||
"legacy behavior for A/B.");
|
||||
REXCVAR_DEFINE_BOOL(audio_xma_preserve_timeline, true, "Audio",
|
||||
"When the XMA decoder backend rejects a damaged frame, emit that "
|
||||
"frame's worth of silence instead of dropping it. The input read "
|
||||
|
||||
@@ -20,6 +20,7 @@ REXCVAR_DECLARE(bool, audio_deep_trace);
|
||||
REXCVAR_DECLARE(bool, audio_xma_loop_guard);
|
||||
REXCVAR_DECLARE(bool, audio_xma_preserve_timeline);
|
||||
REXCVAR_DECLARE(bool, audio_xma_header_straddle_fix);
|
||||
REXCVAR_DECLARE(bool, audio_xma_loop_end_guard);
|
||||
|
||||
namespace rex::audio {
|
||||
|
||||
@@ -726,6 +727,22 @@ void XmaContext::Decode(XMA_CONTEXT_DATA* data) {
|
||||
if (data->loop_count > 0) {
|
||||
const uint32_t loop_end = std::max(kBitsPerPacketHeader, data->loop_end);
|
||||
is_loop_end_frame = (data->input_buffer_read_offset == loop_end);
|
||||
if (is_loop_end_frame && REXCVAR_GET(audio_xma_loop_end_guard) &&
|
||||
data->loop_start >= data->loop_end) {
|
||||
// This test is evaluated before UpdateLoopStatus, so the degenerate-loop
|
||||
// guard there (audio_xma_loop_guard) never reaches it. With
|
||||
// loop_count > 0 and loop_start >= loop_end the clamp above turns
|
||||
// loop_end into kBitsPerPacketHeader - which is exactly a voice's
|
||||
// starting read offset for the stream that begins at packet 0, and
|
||||
// exactly what SwapInputBuffer resets the read offset to - so the
|
||||
// frame output limit armed on the first frame of that stream (and on
|
||||
// any post-swap pass that lands at the raw offset), and Consume()
|
||||
// silently discarded the tail of the frame: 384 samples lost on ONE
|
||||
// context of a multi-stream voice, which permanently offsets it from
|
||||
// its siblings. Apply the same real-window requirement here that
|
||||
// UpdateLoopStatus applies.
|
||||
is_loop_end_frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateLoopStatus(data);
|
||||
|
||||
Reference in New Issue
Block a user