From cf94ea8886860a7558da3ed78de544ccafb23f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 9 Nov 2026 15:57:22 +0100 Subject: [PATCH] sceSas: Pass the send through at the full rate when no reverb is selected The "off" path decimated the send to 22khz and then doubled every sample, which folded everything above 11khz down. The ME runs a separate full-rate loop for it instead: pspautotests' audio/reverb/upsample shows a 22.05khz tone surviving with no reverb selected. The comments on the reverb's resampling now cite that test too. Co-Authored-By: Claude Opus 5.5 --- Core/HW/SasAudio.cpp | 21 ++++++++++++++++++--- Core/HW/SasReverb.cpp | 14 ++++---------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Core/HW/SasAudio.cpp b/Core/HW/SasAudio.cpp index 7cc23ea566..2f53008eb3 100644 --- a/Core/HW/SasAudio.cpp +++ b/Core/HW/SasAudio.cpp @@ -778,14 +778,29 @@ void SasInstance::SetWaveformEffectParams(int delay, int feedback) { // See http://report.ppsspp.org/logs/kind/772 for a list of games that use different types. Maybe can help us figure out // which is which. void SasInstance::ApplyWaveformEffect() { - // First, downsample the send buffer to 22khz. We do this naively for now. + // Volume max is 0x1000, while our factor is up to 0x8000. Shifting left by 3 fixes that. + const int volLeft = (uint16_t)(waveformEffect.leftVol << 3); + const int volRight = (uint16_t)(waveformEffect.rightVol << 3); + + if (reverb_.GetPreset() == -1) { + // With no reverb selected, the ME runs a separate loop at the full 44.1khz, so the send + // goes straight through (tests/audio/reverb/upsample: a 22.05khz tone survives). + for (int i = 0; i < grainSize * 2; i += 2) { + sendBufferProcessed[i + 0] = clamp_s16(clamp_s16(sendBuffer[i + 0]) * volLeft >> 15); + sendBufferProcessed[i + 1] = clamp_s16(clamp_s16(sendBuffer[i + 1]) * volRight >> 15); + } + return; + } + + // First, downsample the send buffer to 22khz. The ME keeps one frame of each pair, with no + // filter (tests/audio/reverb/upsample: a 22.05khz tone reaches the reverb as DC). It keeps the + // second frame; we keep the first. for (int i = 0; i < grainSize / 2; i++) { sendBufferDownsampled[i * 2] = clamp_s16(sendBuffer[i * 4]); sendBufferDownsampled[i * 2 + 1] = clamp_s16(sendBuffer[i * 4 + 1]); } - // Volume max is 0x1000, while our factor is up to 0x8000. Shifting left by 3 fixes that. - reverb_.ProcessReverb(sendBufferProcessed, sendBufferDownsampled, grainSize / 2, (uint16_t)(waveformEffect.leftVol << 3), (uint16_t)(waveformEffect.rightVol << 3)); + reverb_.ProcessReverb(sendBufferProcessed, sendBufferDownsampled, grainSize / 2, volLeft, volRight); } void SasInstance::DoState(PointerWrap &p) { diff --git a/Core/HW/SasReverb.cpp b/Core/HW/SasReverb.cpp index e6ebd4011a..d3543a4f23 100644 --- a/Core/HW/SasReverb.cpp +++ b/Core/HW/SasReverb.cpp @@ -272,7 +272,8 @@ private: int size_; }; -// The hardware upsamples the reverb's 22khz output by zero-stuffing, with no filter after it. That +// The hardware upsamples the reverb's 22khz output by zero-stuffing, with no filter after it +// (tests/audio/reverb/upsample: with dry off, every odd frame is exactly zero). That // leaves a mirror image of the whole wet signal just below 22khz, at full amplitude: it is barely // audible itself, but it eats the headroom of the final mix, so loud scenes clip much more (Outrun // 2006 while racing). We deliberately replace that with a proper interpolation filter. @@ -330,16 +331,9 @@ void SasReverb::UpsampleWet(int16_t *out, int count) { } void SasReverb::ProcessReverb(int16_t *output, const int16_t *input, size_t inputSize, int volLeft, int volRight) { - // This means replicate the input signal in the processed buffer. - // Can also be used to verify that the error is in here... + // "Off" runs at the full rate and never gets here (see SasInstance::ApplyWaveformEffect). if (preset_ == -1) { - // Strangely, OFF is not filled with zeroes every other. Seems special cased. - for (size_t i = 0; i < inputSize; ++i) { - output[i * 4 + 0] = clamp_s16((int)input[i * 2 + 0] * volLeft >> 15); - output[i * 4 + 1] = clamp_s16((int)input[i * 2 + 1] * volRight >> 15); - output[i * 4 + 2] = clamp_s16((int)input[i * 2 + 0] * volLeft >> 15); - output[i * 4 + 3] = clamp_s16((int)input[i * 2 + 1] * volRight >> 15); - } + memset(output, 0, inputSize * 4 * sizeof(int16_t)); return; }