diff --git a/thirdparty/rexglue-sdk/src/native/audio/audio_system.cpp b/thirdparty/rexglue-sdk/src/native/audio/audio_system.cpp index 10c5f301..5b0127ae 100644 --- a/thirdparty/rexglue-sdk/src/native/audio/audio_system.cpp +++ b/thirdparty/rexglue-sdk/src/native/audio/audio_system.cpp @@ -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 " diff --git a/thirdparty/rexglue-sdk/src/native/audio/xma/context.cpp b/thirdparty/rexglue-sdk/src/native/audio/xma/context.cpp index 6550bc44..cb39582a 100644 --- a/thirdparty/rexglue-sdk/src/native/audio/xma/context.cpp +++ b/thirdparty/rexglue-sdk/src/native/audio/xma/context.cpp @@ -1,4 +1,4 @@ -/** +/** * ReXGlue native audio runtime * Part of the AC6 Recompilation project */ @@ -17,6 +17,7 @@ #include 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(data->loop_start), + static_cast(data->loop_end), + static_cast(data->loop_count), + static_cast(data->input_buffer_read_offset)); + } + return; + } + } else if (data->input_buffer_read_offset != loop_end) { return; }