mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
sceSas: Filter the reverb send before halving its rate
The ME decimates the send by keeping one frame of each pair, so everything above 11khz aliases into the reverb (pspautotests' audio/reverb/upsample: a 22.05khz tone reaches it as DC). Use the output side's half-band filter at half gain instead. Splitting the send into its even and odd frames first makes each tap one multiply-add for two outputs and both channels. This deliberately departs from the hardware, like the output side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf94ea8886
commit
36fbf29364
3 files changed
+65
-8
No files matched your search
@@ -792,14 +792,8 @@ void SasInstance::ApplyWaveformEffect() {
|
||||
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]);
|
||||
}
|
||||
|
||||
// First, downsample the send buffer to 22khz, with a filter the hardware doesn't have.
|
||||
reverb_.DownsampleSend(sendBufferDownsampled, sendBuffer, grainSize / 2);
|
||||
reverb_.ProcessReverb(sendBufferProcessed, sendBufferDownsampled, grainSize / 2, volLeft, volRight);
|
||||
}
|
||||
|
||||
|
||||
@@ -176,12 +176,16 @@ SasReverb::SasReverb() : preset_(-1), pos_(0) {
|
||||
workspace_ = new int16_t[BUFSIZE];
|
||||
data_ = new SasReverbData{};
|
||||
wet_ = new float[(UPSAMPLE_HISTORY + MAX_INPUT_FRAMES) * 2]();
|
||||
sendEven_ = new float[(UPSAMPLE_HISTORY + MAX_INPUT_FRAMES) * 2]();
|
||||
sendOdd_ = new float[(UPSAMPLE_HISTORY + MAX_INPUT_FRAMES) * 2]();
|
||||
}
|
||||
|
||||
SasReverb::~SasReverb() {
|
||||
delete[] workspace_;
|
||||
delete data_;
|
||||
delete[] wet_;
|
||||
delete[] sendEven_;
|
||||
delete[] sendOdd_;
|
||||
}
|
||||
|
||||
const char *SasReverb::GetPresetName(int preset) {
|
||||
@@ -204,6 +208,8 @@ void SasReverb::SetPreset(int preset) {
|
||||
pos_ = BUFSIZE - presets[preset_].size;
|
||||
memset(workspace_, 0, sizeof(int16_t) * BUFSIZE);
|
||||
memset(wet_, 0, sizeof(float) * UPSAMPLE_HISTORY * 2);
|
||||
memset(sendEven_, 0, sizeof(float) * UPSAMPLE_HISTORY * 2);
|
||||
memset(sendOdd_, 0, sizeof(float) * UPSAMPLE_HISTORY * 2);
|
||||
} else {
|
||||
pos_ = 0;
|
||||
}
|
||||
@@ -330,6 +336,55 @@ void SasReverb::UpsampleWet(int16_t *out, int count) {
|
||||
memmove(wet_, wet_ + count * 2, sizeof(float) * UPSAMPLE_HISTORY * 2);
|
||||
}
|
||||
|
||||
// The hardware decimates the send by keeping one frame of each pair, with no filter
|
||||
// (tests/audio/reverb/upsample: a 22.05khz tone reaches the reverb as DC), so everything above
|
||||
// 11khz aliases into the reverb. We filter it instead, with the same half-band filter as the
|
||||
// output, at half gain: the center frame times one half, plus the odd frames on each side.
|
||||
//
|
||||
// With the send split into its even frames E and odd frames O, output n is
|
||||
// E[n] / 2 + sum(c[j] / 2 * (O[n - 1 - j] + O[n + j])), so one load of O gives that tap for two
|
||||
// consecutive outputs, both channels, with the same coefficient. Delayed by UPSAMPLE_HALF_TAPS
|
||||
// frames at 22khz, like the output side.
|
||||
void SasReverb::DownsampleSend(int16_t *output, const int32_t *input, int outputFrames) {
|
||||
_dbg_assert_(outputFrames <= MAX_INPUT_FRAMES);
|
||||
const int halfTaps = UPSAMPLE_HALF_TAPS;
|
||||
float *even = sendEven_ + UPSAMPLE_HISTORY * 2;
|
||||
float *odd = sendOdd_ + UPSAMPLE_HISTORY * 2;
|
||||
for (int i = 0; i < outputFrames; i++) {
|
||||
even[i * 2 + 0] = (float)input[i * 4 + 0];
|
||||
even[i * 2 + 1] = (float)input[i * 4 + 1];
|
||||
odd[i * 2 + 0] = (float)input[i * 4 + 2];
|
||||
odd[i * 2 + 1] = (float)input[i * 4 + 3];
|
||||
}
|
||||
|
||||
float coefs[UPSAMPLE_HALF_TAPS];
|
||||
for (int j = 0; j < halfTaps; j++) {
|
||||
coefs[j] = (float)upsampleCoefs[j] * (0.5f / 32768.0f);
|
||||
}
|
||||
|
||||
// Output n centers on history frame n + halfTaps, so its newest tap is frame n - 1 of this call.
|
||||
for (int n = 0; n < outputFrames; n += 2) {
|
||||
Vec4F32 acc = Vec4F32::Load(sendEven_ + (n + halfTaps) * 2) * 0.5f;
|
||||
for (int j = 0; j < halfTaps; j++) {
|
||||
const Vec4F32 pair = Vec4F32::Load(sendOdd_ + (n + halfTaps - 1 - j) * 2) + Vec4F32::Load(sendOdd_ + (n + halfTaps + j) * 2);
|
||||
acc = MulAdd(pair, Vec4F32::Splat(coefs[j]), acc);
|
||||
}
|
||||
if (n + 1 < outputFrames) {
|
||||
Vec4S32FromF32Round(acc).StoreS16Sat(output + n * 2);
|
||||
} else {
|
||||
// An odd count: the second output belongs to the next call (its taps haven't all
|
||||
// arrived yet), so only the first is stored.
|
||||
int16_t last[4];
|
||||
Vec4S32FromF32Round(acc).StoreS16Sat(last);
|
||||
output[n * 2 + 0] = last[0];
|
||||
output[n * 2 + 1] = last[1];
|
||||
}
|
||||
}
|
||||
|
||||
memmove(sendEven_, sendEven_ + outputFrames * 2, sizeof(float) * UPSAMPLE_HISTORY * 2);
|
||||
memmove(sendOdd_, sendOdd_ + outputFrames * 2, sizeof(float) * UPSAMPLE_HISTORY * 2);
|
||||
}
|
||||
|
||||
void SasReverb::ProcessReverb(int16_t *output, const int16_t *input, size_t inputSize, int volLeft, int volRight) {
|
||||
// "Off" runs at the full rate and never gets here (see SasInstance::ApplyWaveformEffect).
|
||||
if (preset_ == -1) {
|
||||
|
||||
@@ -32,6 +32,10 @@ public:
|
||||
|
||||
static const char *GetPresetName(int preset);
|
||||
|
||||
// Halves the rate of the 44khz send (interleaved stereo, outputFrames * 2 frames of it), into
|
||||
// ProcessReverb's input.
|
||||
void DownsampleSend(int16_t *output, const int32_t *input, int outputFrames);
|
||||
|
||||
// Input should be a mixdown of all the channels that have reverb enabled, at 22khz.
|
||||
// Output is written back at 44khz.
|
||||
void ProcessReverb(int16_t *output, const int16_t *input, size_t inputSize, int volLeft, int volRight);
|
||||
@@ -64,4 +68,8 @@ private:
|
||||
// previous call, then this call's. Not saved in savestates: the history is about a millisecond
|
||||
// of audio, and the delay line itself isn't saved either.
|
||||
float *wet_;
|
||||
// The send at 44khz split into its even and its odd frames (interleaved stereo each), with
|
||||
// UPSAMPLE_HISTORY frames of history, for DownsampleSend. Not saved either.
|
||||
float *sendEven_;
|
||||
float *sendOdd_;
|
||||
};
|
||||
Reference in new issue
Block a user