From 6d945404d14076b13a8c8e2aa87e90c074158ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 9 Sep 2026 10:18:56 -0600 Subject: [PATCH] sceSas: Fix send/return levels for the reverb effects The two fudge factors in the reverb path cancelled, which is why the overall level felt roughly right. - The send is accumulator * 0x20 >> 16, i.e. sample >> 2. We used >> 1, driving the reverb 6dB hot. - The return is (evol * out) >> 11. We used >> 12, i.e. 6dB quiet. Net level is therefore unchanged, but the reverb now runs at the level the presets were designed around. That matters because the filter clamps internally, so a 6dB hot input changes how the feedback path saturates - worst on the presets with heavy feedback. Also adds a slider in the imgui. --- Core/HW/SasReverb.cpp | 15 +++++++++++++-- UI/ImDebugger/ImDebugger.cpp | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Core/HW/SasReverb.cpp b/Core/HW/SasReverb.cpp index 242327bd1d..e60abb9e24 100644 --- a/Core/HW/SasReverb.cpp +++ b/Core/HW/SasReverb.cpp @@ -287,6 +287,12 @@ void SasReverb::ProcessReverb(int16_t *output, const int16_t *input, size_t inpu memset(output, 0, inputSize * 4 * sizeof(int16_t)); return; } else { + // The ME's reverb return is (evol * out) >> 11, not >> 12. Our caller passes + // leftVol << 3 to pair with the >> 15 below, which is one bit short of that. + // The "off" path above is deliberately left alone: it really is >> 12, because the + // hardware runs a separate full-rate loop when no reverb is selected. + volLeft <<= 1; + volRight <<= 1; volLeft *= reverbVolumeMultiplier; volRight *= reverbVolumeMultiplier; } @@ -301,8 +307,13 @@ void SasReverb::ProcessReverb(int16_t *output, const int16_t *input, size_t inpu // Or we could actually template the whole thing with the parameters as template arguments, as the presets are fixed. for (size_t i = 0; i < inputSize; i++) { // Dividing by two here is an incorrect hack. Some multiplication factor is needed to prevent the reverb from getting too loud, though. - int16_t LeftInput = input[i * 2] >> 1; - int16_t RightInput = input[i * 2 + 1] >> 1; + // The ME feeds the reverb at a quarter of the mix level, not a half: its send + // accumulator reaches sample<<9 at full voice volume, and the reverb input is + // accumulator * 0x20 >> 16, i.e. sample >> 2. This used to be >> 1, which drove the + // reverb 6dB hot - inaudible on its own, since the return above was 6dB quiet by + // exactly the same factor, but it changed how hard the feedback path clipped. + int16_t LeftInput = input[i * 2] >> 2; + int16_t RightInput = input[i * 2 + 1] >> 2; int16_t Lin = LeftInput; // (d.vLIN * LeftInput) >> 15; int16_t Rin = RightInput; // (d.vRIN * RightInput) >> 15; diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index d9f0929d65..1cf62862a0 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1815,6 +1815,9 @@ void DrawSasAudio(ImConfig &cfg) { } ImGui::Checkbox("Mute", __SasGetGlobalMuteFlag()); + if (ImGui::SliderInt("Reverb", &g_Config.iReverbVolume, 0, 200)) { + g_Config.DoNotSaveSetting(&g_Config.iReverbVolume); + } ImGui::SameLine(); ImGui::Checkbox("Show all voices", &cfg.sasShowAllVoices);