Guard XMA loop handling against degenerate loop metadata

Streamed voices carrying degenerate loop metadata (loop_count 0xFF with loop_start == loop_end == 0) re-fired the loop machinery on every input buffer swap, truncating or skipping subframes of the stream. Require a real loop window (loop_start < loop_end, read_offset ≥ loop_end) before engaging, as in Xenia PR #1808. 47 confirmed fires in AC6; default on.
This commit is contained in:
Dipshet
2026-07-31 04:24:07 +02:00
parent 8c922abd77
commit 466cac7c00
2 changed files with 37 additions and 2 deletions
@@ -19,6 +19,16 @@ REXCVAR_DEFINE_BOOL(audio_trace_render_driver_verbose, false, "Audio",
"Trace render-driver activity");
REXCVAR_DEFINE_BOOL(audio_deep_trace, false, "Audio",
"Enable verbose runtime audio tracing");
REXCVAR_DEFINE_BOOL(audio_xma_loop_guard, true, "Audio",
"Require a real loop window (loop_start < loop_end, "
"read_offset >= loop_end) before engaging XMA loop "
"handling, as in current Xenia master (PR #1808, debugged "
"on Ace Combat 6). Off: legacy behavior, where streamed "
"voices carrying degenerate loop metadata (loop_count="
"0xff, loop_start == loop_end == 0) re-fire the loop "
"machinery on every input buffer swap, truncating/"
"skipping subframes of the stream (confirmed firing on "
"AC6 cutscene streams via the deep-trace counter).");
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 "
+27 -2
View File
@@ -1,4 +1,4 @@
/**
/**
* ReXGlue native audio runtime
* Part of the AC6 Recompilation project
*/
@@ -17,6 +17,7 @@
#include <native/stream.h>
REXCVAR_DECLARE(bool, audio_deep_trace);
REXCVAR_DECLARE(bool, audio_xma_loop_guard);
REXCVAR_DECLARE(bool, audio_xma_preserve_timeline);
namespace rex::audio {
@@ -342,7 +343,31 @@ void XmaContext::UpdateLoopStatus(XMA_CONTEXT_DATA* data) {
const uint32_t loop_start = std::max(kBitsPerPacketHeader, data->loop_start);
const uint32_t loop_end = std::max(kBitsPerPacketHeader, data->loop_end);
if (data->input_buffer_read_offset != loop_end) {
if (REXCVAR_GET(audio_xma_loop_guard)) {
// Xenia master's TrySetupNextLoop guard (PR #1808, debugged on Ace
// Combat 6): streamed voices can carry degenerate loop metadata
// (loop_count=0xff, loop_start == loop_end == 0). The clamps above turn
// that into start == end == kBitsPerPacketHeader, and SwapInputBuffer
// resets the read offset to exactly kBitsPerPacketHeader, so without a
// real-window check every buffer swap re-fires the loop machinery -
// the frame output limit truncates that frame and the loop-start skip
// drops subframes. Require loop_start < loop_end (raw values) and use
// master's read_offset >= loop_end trigger.
if (data->loop_start >= data->loop_end ||
data->input_buffer_read_offset < loop_end) {
if (data->loop_start >= data->loop_end &&
data->input_buffer_read_offset == loop_end && IsDeepTraceEnabled()) {
REXAPU_DEBUG(
"XmaContext {}: loop guard suppressed degenerate loop fire "
"(loop_start={} loop_end={} loop_count={} read_offset={})",
id(), static_cast<uint32_t>(data->loop_start),
static_cast<uint32_t>(data->loop_end),
static_cast<uint32_t>(data->loop_count),
static_cast<uint32_t>(data->input_buffer_read_offset));
}
return;
}
} else if (data->input_buffer_read_offset != loop_end) {
return;
}