From e040fa522336507997b62bc745cf567f70c6339a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 9 Nov 2026 21:03:53 +0100 Subject: [PATCH] Audio: Show granular mixer stats in the audio debug overlay Queue fill and cap (in granules and ms), under/overruns, output rate, read size and frame time, replacing the "no stats yet" placeholder. Co-Authored-By: Claude Opus 5.5 --- Core/HW/GranularMixer.cpp | 34 ++++++++++++++++++++++++++++++++++ Core/HW/GranularMixer.h | 4 ++++ UI/AudioCommon.cpp | 3 ++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Core/HW/GranularMixer.cpp b/Core/HW/GranularMixer.cpp index 0636995593..847f1394aa 100644 --- a/Core/HW/GranularMixer.cpp +++ b/Core/HW/GranularMixer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "Common/CommonTypes.h" @@ -80,6 +81,7 @@ void GranularMixer::Mix(s16 *samples, u32 num_samples, int outSampleRate, float memset(samples, 0, num_samples * 2 * sizeof(s16)); // The estimate is 0 until a second has gone by. frameTimeEstimate_ = 1.0f / std::max(fpsEstimate, 1.0f); + outSampleRate_ = outSampleRate; smoothedReadSize_ = smoothedReadSize_ == 0 ? num_samples : (smoothedReadSize_ * 0.95f + num_samples * 0.05f); @@ -309,3 +311,35 @@ void GranularMixer::GetStats(GranularStats *stats) { queuedGranulesMin_ = 10000; queuedGranulesMax_ = 0; } + +void GranularMixer::GetAudioDebugStats(char *buf, size_t bufSize) { + GranularStats stats; + GetStats(&stats); + // Each queued granule holds GRANULE_OVERLAP new samples. + const double msPerGranule = GRANULE_OVERLAP * 1000.0 / 44100.0; + snprintf(buf, bufSize, + "Granular mixer\n" + "Queued: %0.1f granules (%0.1fms)\n" + "Queued min/max: %d / %d\n" + "Cap: %d granules (%0.1fms, %d samples)\n" + "Underruns: %d\n" + "Overruns: %d\n" + "Output sample rate: %d\n" + "Read size: %0.1f\n" + "Frame time estimate: %0.1fms\n" + "Fade volume: %0.2f%s\n", + stats.smoothedQueuedGranules, stats.smoothedQueuedGranules * msPerGranule, + stats.queuedGranulesMin, stats.queuedGranulesMax, + stats.targetQueueSize, stats.targetQueueSize * msPerGranule, stats.queuedSamplesTarget, + stats.underruns, + stats.overruns, + outSampleRate_, + stats.smoothedReadSize, + stats.frameTimeEstimate * 1000.0f, + stats.fadeVolume, stats.looping ? " (looping)" : ""); +} + +void GranularMixer::ResetStatCounters() { + underruns_ = 0; + overruns_ = 0; +} diff --git a/Core/HW/GranularMixer.h b/Core/HW/GranularMixer.h index 0010d6afd6..c64d393ddb 100644 --- a/Core/HW/GranularMixer.h +++ b/Core/HW/GranularMixer.h @@ -46,6 +46,9 @@ public: void PushSamples(const s32 *samples, u32 num_samples, float volume); void GetStats(GranularStats *stats); + // Text for the audio debug overlay. + void GetAudioDebugStats(char *buf, size_t bufSize); + void ResetStatCounters(); static constexpr u32 GRANULE_SIZE = 256; @@ -107,6 +110,7 @@ private: int queuedSamplesTarget_ = 0; float smoothedReadSize_ = 0.0f; float frameTimeEstimate_ = 0.0f; + int outSampleRate_ = 0; void Enqueue(); void Dequeue(Granule* granule); diff --git a/UI/AudioCommon.cpp b/UI/AudioCommon.cpp index 69e329f5e9..bbc0d1f0e8 100644 --- a/UI/AudioCommon.cpp +++ b/UI/AudioCommon.cpp @@ -47,12 +47,13 @@ void NativeMix(int16_t *outStereo, int numFrames, int sampleRateHz, void *userda void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) { if (g_Config.iAudioPlaybackMode == (int)AudioSyncMode::GRANULAR) { - snprintf(buf, bufSize, "(No stats available for granular yet)"); + g_granular.GetAudioDebugStats(buf, bufSize); } else { g_resampler.GetAudioDebugStats(buf, bufSize); } } else { g_resampler.ResetStatCounters(); + g_granular.ResetStatCounters(); } }