From 851ba192f6f27dda3abd21d30fe9ca7bb749d3b7 Mon Sep 17 00:00:00 2001 From: Gregory Heskett Date: Wed, 15 Mar 2023 10:52:05 -0400 Subject: [PATCH] Add lightweight configuration settings to BETTER_REVERB (#602) * Add light configuration settings to BETTER_REVERB This can reduce runtime demand down to ~75% of the standard demand, at the cost of the configurability of generally more advanced parameters * Reformat the BETTER_REVERB preset entries to be easier to look at * :peterVOID: * haha lol formatting change definitely related to reverb yes this isn't unwarranted at all lmao hehe hoho --- src/audio/data.c | 88 ++++++++++++++------ src/audio/heap.c | 1 + src/audio/internal.h | 1 + src/audio/synthesis.c | 186 ++++++++++++++++++++++++++++++++++-------- src/audio/synthesis.h | 44 ++++++++-- src/game/puppyprint.c | 4 +- 6 files changed, 254 insertions(+), 70 deletions(-) diff --git a/src/audio/data.c b/src/audio/data.c index b235130d..97c843cf 100644 --- a/src/audio/data.c +++ b/src/audio/data.c @@ -48,19 +48,19 @@ u32 delaysArr[][NUM_ALLPASS] = { 4, 4, 4, 4, 4, 4, 4, 4, 4, - 4, 4, 4 + 4, 4, 4, }, { /* 1 */ 1080, 1352, 1200, 1200, 1232, 1432, 1384, 1048, 1352, - 928, 1504, 1512 + 928, 1504, 1512, }, { /* 2 */ 1384, 1352, 1048, - 928, 1512, 1504, + 928, 1512, 1504, 1080, 1200, 1352, - 1200, 1432, 1232 + 1200, 1432, 1232, }, }; @@ -74,34 +74,72 @@ s32 reverbMultsArr[][NUM_ALLPASS / 3] = { /** * Format: - * - downsampleRate (Higher values exponentially reduce the number of input samples to process, improving perfomance at cost of quality) - * - isMono (Only process reverb on the left channel and share it with the right channel, improving performance at cost of quality) - * - filterCount (Number of filters to process data with; in general, more filters means higher quality at the cost of performance demand) - * - windowSize (Size of circular reverb buffer; higher values work better for a more open soundscape, lower is better for a more compact sound) - * - gain (Amount of audio retransmitted into the circular reverb buffer, emulating decay; higher values represent a lengthier decay period) - * - gainIndex (Advanced parameter used to tune the outputs of every first two of three filters) - * - reverbIndex (Advanced parameter used to tune the incoming output of every third filter) + * - useLightweightSettings (Reduce some runtime configurability options in favor of a slight speed boost during processing; Light configurability settings are found in synthesis.h) + * - downsampleRate (Higher values exponentially reduce the number of input samples to process, improving perfomance at cost of quality) + * - isMono (Only process reverb on the left channel and share it with the right channel, improving performance at cost of quality) + * - filterCount (Number of filters to process data with; in general, more filters means higher quality at the cost of performance demand; always 3 with light settings) * - * - *delaysL (Array of variable audio buffer sizes / delays for each respective filter [left channel]) - * - *delaysR (Array of variable audio buffer sizes / delays for each respective filter [right channel]) - * - *reverbMultsL (Array of multipliers applied to the final output of each group of 3 filters [left channel]) - * - *reverbMultsR (Array of multipliers applied to the final output of each group of 3 filters [right channel]) + * - windowSize (Size of circular reverb buffer; higher values work better for a more open soundscape, lower is better for a more compact sound) + * - gain (Amount of audio retransmitted into the circular reverb buffer, emulating decay; higher values represent a lengthier decay period) + * - gainIndex (Advanced parameter; used to tune the outputs of every first two of three filters; overridden when using light settings) + * - reverbIndex (Advanced parameter; used to tune the incoming output of every third filter; overridden when using light settings) * - * NOTE: First entry will always be used by default when not using the level commands to specify a preset. + * - *delaysL (Advanced parameter; array of variable audio buffer sizes / delays for each respective filter [left channel]; overridden when using light settings) + * - *delaysR (Advanced parameter; array of variable audio buffer sizes / delays for each respective filter [right channel]; overridden when using light settings) + * - *reverbMultsL (Advanced parameter; array of multipliers applied to the final output of each group of 3 filters [left channel]) + * - *reverbMultsR (Advanced parameter; array of multipliers applied to the final output of each group of 3 filters [right channel]) + * + * NOTE: The first entry will always be used by default when not using the level commands to specify a preset. * Please reference the HackerSM64 Wiki for more descriptive documentation of these parameters and usage of BETTER_REVERB in general. */ struct BetterReverbSettings gBetterReverbSettings[] = { - { /* 0 */ - -1, FALSE, NUM_ALLPASS, -1, -1, 0x00, 0x00, // Vanilla Reverb - delaysArr[0], delaysArr[0], reverbMultsArr[0], reverbMultsArr[0] + { /* Preset 0 - Vanilla Reverb [Default Preset] */ + .useLightweightSettings = FALSE, // Ignored with vanilla reverb + .downsampleRate = -1, // Signifies use of vanilla reverb + .isMono = FALSE, // Ignored with vanilla reverb + .filterCount = NUM_ALLPASS, // Ignored with vanilla reverb + + .windowSize = -1, // Use vanilla preset window size + .gain = -1, // Use vanilla preset gain value + .gainIndex = 0x00, // Ignored with vanilla reverb + .reverbIndex = 0x00, // Ignored with vanilla reverb + + .delaysL = delaysArr[0], // Ignored with vanilla reverb + .delaysR = delaysArr[0], // Ignored with vanilla reverb + .reverbMultsL = reverbMultsArr[0], // Ignored with vanilla reverb + .reverbMultsR = reverbMultsArr[0], // Ignored with vanilla reverb }, - { /* 1 */ - 2, FALSE, (NUM_ALLPASS - 9), 0xE00, 0x43FF, 0xA0, 0x30, // Default Console - delaysArr[1], delaysArr[2], reverbMultsArr[1], reverbMultsArr[2] + { /* Preset 1 - Sample Console Configuration */ + .useLightweightSettings = TRUE, + .downsampleRate = 2, + .isMono = FALSE, + .filterCount = (NUM_ALLPASS - 9), // Ignored with lightweight settings + + .windowSize = 0x0E00, + .gain = 0x43FF, + .gainIndex = 0xA0, // Ignored with lightweight settings + .reverbIndex = 0x30, // Ignored with lightweight settings + + .delaysL = delaysArr[1], + .delaysR = delaysArr[2], + .reverbMultsL = reverbMultsArr[1], // Ignored with lightweight settings + .reverbMultsR = reverbMultsArr[2], // Ignored with lightweight settings }, - { /* 2 */ - 1, FALSE, NUM_ALLPASS, 0xE00, 0x28FF, 0xA0, 0x60, // Default Emulator (RCVI Hack only) - delaysArr[1], delaysArr[2], reverbMultsArr[1], reverbMultsArr[2] + { /* Preset 2 - Sample Emulator Configuration (RCVI Hack Only) */ + .useLightweightSettings = FALSE, + .downsampleRate = 1, + .isMono = FALSE, + .filterCount = NUM_ALLPASS, + + .windowSize = 0x0E00, + .gain = 0x28FF, + .gainIndex = 0xA0, + .reverbIndex = 0x60, + + .delaysL = delaysArr[1], + .delaysR = delaysArr[2], + .reverbMultsL = reverbMultsArr[1], + .reverbMultsR = reverbMultsArr[2], }, }; #endif diff --git a/src/audio/heap.c b/src/audio/heap.c index f02d0a64..3a0703fc 100644 --- a/src/audio/heap.c +++ b/src/audio/heap.c @@ -1051,6 +1051,7 @@ void init_reverb_us(s32 presetId) { // This will likely crash if given an invalid preset value. Adding a safety check here isn't worth the usability interference. struct BetterReverbSettings *betterReverbPreset = &gBetterReverbSettings[gBetterReverbPreset]; + betterReverbLightweight = betterReverbPreset->useLightweightSettings; betterReverbDownsampleRate = betterReverbPreset->downsampleRate; monoReverb = betterReverbPreset->isMono; reverbFilterCount = betterReverbPreset->filterCount; diff --git a/src/audio/internal.h b/src/audio/internal.h index 1401e0d7..f60ca5ea 100644 --- a/src/audio/internal.h +++ b/src/audio/internal.h @@ -732,6 +732,7 @@ struct NoteSynthesisBuffers { #ifdef BETTER_REVERB struct BetterReverbSettings { + u8 useLightweightSettings; s8 downsampleRate; u8 isMono; u8 filterCount; diff --git a/src/audio/synthesis.c b/src/audio/synthesis.c index 98f8e3e5..1884fdde 100644 --- a/src/audio/synthesis.c +++ b/src/audio/synthesis.c @@ -42,6 +42,7 @@ // Do not touch these values manually, unless you want potential for problems. u8 gBetterReverbPreset = 0; u8 toggleBetterReverb = FALSE; +u8 betterReverbLightweight = FALSE; u8 monoReverb; s8 betterReverbDownsampleRate; static u8 reverbMultsL[NUM_ALLPASS / 3] = {0}; @@ -52,6 +53,8 @@ static s32 delaysL[NUM_ALLPASS] = {0}; static s32 delaysR[NUM_ALLPASS] = {0}; static s32 **delayBufsL; static s32 **delayBufsR; +static s32 lastDelayLightL; +static s32 lastDelayLightR; s32 reverbLastFilterIndex; s32 reverbFilterCount; s32 betterReverbWindowsSize; @@ -99,13 +102,13 @@ static void reverb_samples(s16 *outSampleL, s16 *outSampleR, s32 inSampleL, s32 s32 *curDelaySampleR; s32 historySampleL; s32 historySampleR; - s32 i = 0; - s32 j = 0; - s32 k = 0; s32 outTmpL = 0; s32 outTmpR = 0; s32 tmpCarryoverL = ((delayBufsL[reverbLastFilterIndex][allpassIdxL[reverbLastFilterIndex]] * betterReverbRevIndex) >> 8) + inSampleL; s32 tmpCarryoverR = ((delayBufsR[reverbLastFilterIndex][allpassIdxR[reverbLastFilterIndex]] * betterReverbRevIndex) >> 8) + inSampleR; + s32 i = 0; + s32 j = 0; + s32 k = 0; for (; i <= reverbLastFilterIndex; ++i, ++j) { curDelaySampleL = &delayBufsL[i][allpassIdxL[i]]; @@ -141,11 +144,11 @@ static void reverb_samples(s16 *outSampleL, s16 *outSampleR, s32 inSampleL, s32 static void reverb_mono_sample(s16 *outSample, s32 inSample) { s32 *curDelaySample; s32 historySample; + s32 outTmp = 0; + s32 tmpCarryover = ((delayBufsL[reverbLastFilterIndex][allpassIdxL[reverbLastFilterIndex]] * betterReverbRevIndex) >> 8) + inSample; s32 i = 0; s32 j = 0; s32 k = 0; - s32 outTmp = 0; - s32 tmpCarryover = ((delayBufsL[reverbLastFilterIndex][allpassIdxL[reverbLastFilterIndex]] * betterReverbRevIndex) >> 8) + inSample; for (; i <= reverbLastFilterIndex; ++i, ++j) { curDelaySample = &delayBufsL[i][allpassIdxL[i]]; @@ -168,6 +171,72 @@ static void reverb_mono_sample(s16 *outSample, s32 inSample) { *outSample = CLAMP_S16(outTmp); } +// Light reverb processing functions! +#define FILTERS_MINUS_1 (BETTER_REVERB_FILTER_COUNT_LIGHT - 1) +static void reverb_samples_light(s16 *outSampleL, s16 *outSampleR, s32 inSampleL, s32 inSampleR) { + s32 *curDelaySampleL; + s32 *curDelaySampleR; + s32 historySampleL; + s32 historySampleR; + s32 tmpCarryoverL = (((delayBufsL[FILTERS_MINUS_1][allpassIdxL[FILTERS_MINUS_1]] * BETTER_REVERB_REVERB_INDEX_LIGHT) >> 8) + inSampleL); + s32 tmpCarryoverR = (((delayBufsR[FILTERS_MINUS_1][allpassIdxR[FILTERS_MINUS_1]] * BETTER_REVERB_REVERB_INDEX_LIGHT) >> 8) + inSampleR); + s32 i = 0; + + for (; i < FILTERS_MINUS_1; ++i) { + curDelaySampleL = &delayBufsL[i][allpassIdxL[i]]; + curDelaySampleR = &delayBufsR[i][allpassIdxR[i]]; + historySampleL = *curDelaySampleL; + historySampleR = *curDelaySampleR; + + *curDelaySampleL = (((historySampleL * (-BETTER_REVERB_GAIN_INDEX_LIGHT)) >> 8) + tmpCarryoverL); + *curDelaySampleR = (((historySampleR * (-BETTER_REVERB_GAIN_INDEX_LIGHT)) >> 8) + tmpCarryoverR); + tmpCarryoverL = (((*curDelaySampleL * BETTER_REVERB_GAIN_INDEX_LIGHT) >> 8) + historySampleL); + tmpCarryoverR = (((*curDelaySampleR * BETTER_REVERB_GAIN_INDEX_LIGHT) >> 8) + historySampleR); + + if (++allpassIdxL[i] == delaysL[i]) allpassIdxL[i] = 0; + if (++allpassIdxR[i] == delaysR[i]) allpassIdxR[i] = 0; + } + + curDelaySampleL = &delayBufsL[FILTERS_MINUS_1][allpassIdxL[FILTERS_MINUS_1]]; + curDelaySampleR = &delayBufsR[FILTERS_MINUS_1][allpassIdxR[FILTERS_MINUS_1]]; + historySampleL = ((*curDelaySampleL * BETTER_REVERB_MULTIPLE_LIGHT) >> 8); // outTmpL variable not needed, as there is no sample addition happening here. Not really a history sample though. + historySampleR = ((*curDelaySampleR * BETTER_REVERB_MULTIPLE_LIGHT) >> 8); // outTmpR variable not needed, as there is no sample addition happening here. Not really a history sample though. + *curDelaySampleL = tmpCarryoverL; + *curDelaySampleR = tmpCarryoverR; + + if (++allpassIdxL[FILTERS_MINUS_1] == lastDelayLightL) allpassIdxL[FILTERS_MINUS_1] = 0; + if (++allpassIdxR[FILTERS_MINUS_1] == lastDelayLightR) allpassIdxR[FILTERS_MINUS_1] = 0; + + *outSampleL = CLAMP_S16(historySampleL); + *outSampleR = CLAMP_S16(historySampleR); +} + +static void reverb_mono_sample_light(s16 *outSample, s32 inSample) { + s32 *curDelaySample; + s32 historySample; + s32 tmpCarryover = (((delayBufsL[FILTERS_MINUS_1][allpassIdxL[FILTERS_MINUS_1]] * BETTER_REVERB_REVERB_INDEX_LIGHT) >> 8) + inSample); + s32 i = 0; + + for (; i < FILTERS_MINUS_1; ++i) { + curDelaySample = &delayBufsL[i][allpassIdxL[i]]; + historySample = *curDelaySample; + + *curDelaySample = (((historySample * (-BETTER_REVERB_GAIN_INDEX_LIGHT)) >> 8) + tmpCarryover); + tmpCarryover = (((*curDelaySample * BETTER_REVERB_GAIN_INDEX_LIGHT) >> 8) + historySample); + + if (++allpassIdxL[i] == delaysL[i]) allpassIdxL[i] = 0; + } + + curDelaySample = &delayBufsL[FILTERS_MINUS_1][allpassIdxL[FILTERS_MINUS_1]]; + historySample = ((*curDelaySample * BETTER_REVERB_MULTIPLE_LIGHT) >> 8); // outTmp variable not needed, as there is no sample addition happening here. Not really a history sample though. + *curDelaySample = tmpCarryover; + + if (++allpassIdxL[FILTERS_MINUS_1] == lastDelayLightL) allpassIdxL[FILTERS_MINUS_1] = 0; + + *outSample = CLAMP_S16(historySample); +} +#undef FILTERS_MINUS_1 + void initialize_better_reverb_buffers(void) { delayBufsL = (s32**) soundAlloc(&gBetterReverbPool, BETTER_REVERB_PTR_SIZE); delayBufsR = &delayBufsL[NUM_ALLPASS]; @@ -176,6 +245,10 @@ void initialize_better_reverb_buffers(void) { void set_better_reverb_buffers(u32 *inputDelaysL, u32 *inputDelaysR) { s32 bufOffset = 0; s32 i; + s32 filterCount = reverbFilterCount; + + if (betterReverbLightweight) + filterCount = BETTER_REVERB_FILTER_COUNT_LIGHT; gBetterReverbPool.cur = gBetterReverbPool.start + ALIGN16(BETTER_REVERB_PTR_SIZE); // Reset reverb data pool @@ -183,9 +256,9 @@ void set_better_reverb_buffers(u32 *inputDelaysL, u32 *inputDelaysR) { if (!toggleBetterReverb) return; - // NOTE: Using reverbFilterCount over NUM_ALLPASS will report less memory usage with fewer filters, but poses an additional + // NOTE: Using filterCount over NUM_ALLPASS will report less memory usage with fewer filters, but poses an additional // risk to anybody testing on console with performance compromises, as emulator can be easily overlooked. - for (i = 0; i < reverbFilterCount; ++i) { + for (i = 0; i < filterCount; ++i) { delaysL[i] = (s32) (inputDelaysL[i] / gReverbDownsampleRate); delaysR[i] = (s32) (inputDelaysR[i] / gReverbDownsampleRate); delayBufsL[i] = soundAlloc(&gBetterReverbPool, delaysL[i] * sizeof(s32)); @@ -196,6 +269,9 @@ void set_better_reverb_buffers(u32 *inputDelaysL, u32 *inputDelaysR) { aggress(bufOffset * sizeof(s32) <= BETTER_REVERB_SIZE - ALIGN16(BETTER_REVERB_PTR_SIZE), "BETTER_REVERB_SIZE is too small for this preset!"); + lastDelayLightL = delaysL[filterCount-1]; + lastDelayLightR = delaysR[filterCount-1]; + bzero(allpassIdxL, sizeof(allpassIdxL)); bzero(allpassIdxR, sizeof(allpassIdxR)); } @@ -300,40 +376,82 @@ void prepare_reverb_ring_buffer(s32 chunkLen, u32 updateIndex) { } #ifdef BETTER_REVERB else if (toggleBetterReverb) { + s32 firstLoopMax; + s32 secondLoopMax; item = &gSynthesisReverb.items[gSynthesisReverb.curFrame][updateIndex]; - if (gSoundMode == SOUND_MODE_MONO || monoReverb) { - if (gReverbDownsampleRate != 1) { - osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH); - for (srcPos = 0, dstPos = item->startPos; dstPos < ((item->lengthA / 2) + item->startPos); srcPos += gReverbDownsampleRate, dstPos++) { - reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) item->toDownsampleLeft[srcPos] + (s32) item->toDownsampleRight[srcPos]) / 2); - gSynthesisReverb.ringBuffer.right[dstPos] = gSynthesisReverb.ringBuffer.left[dstPos]; - } - for (dstPos = 0; dstPos < (item->lengthB / 2); srcPos += gReverbDownsampleRate, dstPos++) { - reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) item->toDownsampleLeft[srcPos] + (s32) item->toDownsampleRight[srcPos]) / 2); - gSynthesisReverb.ringBuffer.right[dstPos] = gSynthesisReverb.ringBuffer.left[dstPos]; + dstPos = item->startPos; + firstLoopMax = ((item->lengthA / 2) + item->startPos); + secondLoopMax = (item->lengthB / 2); + + // This block could be simplified probably with function pointers or rewriting reverb_mono_sample to support L and R individually. Another idea would be to process more than one sample at a time + // for each function call. In practice with HackerSM64, these ideas either aren't super trivial to implement efficiently or don't benefit performance at all, so I'm leaving this as it is for now. + if (betterReverbLightweight) { + if (gSoundMode == SOUND_MODE_MONO || monoReverb) { + if (gReverbDownsampleRate != 1) { + osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH); + for (srcPos = 0; dstPos < firstLoopMax; srcPos += gReverbDownsampleRate, dstPos++) + reverb_mono_sample_light(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) item->toDownsampleLeft[srcPos] + (s32) item->toDownsampleRight[srcPos]) / 2); + bcopy(&gSynthesisReverb.ringBuffer.left[item->startPos], &gSynthesisReverb.ringBuffer.right[item->startPos], (dstPos - item->startPos) * sizeof(s16)); + + for (dstPos = 0; dstPos < secondLoopMax; srcPos += gReverbDownsampleRate, dstPos++) + reverb_mono_sample_light(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) item->toDownsampleLeft[srcPos] + (s32) item->toDownsampleRight[srcPos]) / 2); + bcopy(gSynthesisReverb.ringBuffer.left, gSynthesisReverb.ringBuffer.right, dstPos * sizeof(s16)); + } else { + for (; dstPos < firstLoopMax; dstPos++) + reverb_mono_sample_light(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) gSynthesisReverb.ringBuffer.left[dstPos] + (s32) gSynthesisReverb.ringBuffer.right[dstPos]) / 2); + bcopy(&gSynthesisReverb.ringBuffer.left[item->startPos], &gSynthesisReverb.ringBuffer.right[item->startPos], (dstPos - item->startPos) * sizeof(s16)); + + for (dstPos = 0; dstPos < secondLoopMax; dstPos++) + reverb_mono_sample_light(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) gSynthesisReverb.ringBuffer.left[dstPos] + (s32) gSynthesisReverb.ringBuffer.right[dstPos]) / 2); + bcopy(gSynthesisReverb.ringBuffer.left, gSynthesisReverb.ringBuffer.right, dstPos * sizeof(s16)); } } else { - for (dstPos = item->startPos; dstPos < ((item->lengthA / 2) + item->startPos); dstPos++) { - reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) gSynthesisReverb.ringBuffer.left[dstPos] + (s32) gSynthesisReverb.ringBuffer.right[dstPos]) / 2); - gSynthesisReverb.ringBuffer.right[dstPos] = gSynthesisReverb.ringBuffer.left[dstPos]; - } - for (dstPos = 0; dstPos < (item->lengthB / 2); dstPos++) { - reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) gSynthesisReverb.ringBuffer.left[dstPos] + (s32) gSynthesisReverb.ringBuffer.right[dstPos]) / 2); - gSynthesisReverb.ringBuffer.right[dstPos] = gSynthesisReverb.ringBuffer.left[dstPos]; + if (gReverbDownsampleRate != 1) { + osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH); + for (srcPos = 0; dstPos < firstLoopMax; srcPos += gReverbDownsampleRate, dstPos++) + reverb_samples_light(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], item->toDownsampleLeft[srcPos], item->toDownsampleRight[srcPos]); + for (dstPos = 0; dstPos < secondLoopMax; srcPos += gReverbDownsampleRate, dstPos++) + reverb_samples_light(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], item->toDownsampleLeft[srcPos], item->toDownsampleRight[srcPos]); + } else { + for (; dstPos < firstLoopMax; dstPos++) + reverb_samples_light(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], gSynthesisReverb.ringBuffer.left[dstPos], gSynthesisReverb.ringBuffer.right[dstPos]); + for (dstPos = 0; dstPos < secondLoopMax; dstPos++) + reverb_samples_light(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], gSynthesisReverb.ringBuffer.left[dstPos], gSynthesisReverb.ringBuffer.right[dstPos]); } } } else { - if (gReverbDownsampleRate != 1) { - osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH); - for (srcPos = 0, dstPos = item->startPos; dstPos < ((item->lengthA / 2) + item->startPos); srcPos += gReverbDownsampleRate, dstPos++) - reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], item->toDownsampleLeft[srcPos], item->toDownsampleRight[srcPos]); - for (dstPos = 0; dstPos < (item->lengthB / 2); srcPos += gReverbDownsampleRate, dstPos++) - reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], item->toDownsampleLeft[srcPos], item->toDownsampleRight[srcPos]); + if (gSoundMode == SOUND_MODE_MONO || monoReverb) { + if (gReverbDownsampleRate != 1) { + osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH); + for (srcPos = 0; dstPos < firstLoopMax; srcPos += gReverbDownsampleRate, dstPos++) + reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) item->toDownsampleLeft[srcPos] + (s32) item->toDownsampleRight[srcPos]) / 2); + bcopy(&gSynthesisReverb.ringBuffer.left[item->startPos], &gSynthesisReverb.ringBuffer.right[item->startPos], (dstPos - item->startPos) * sizeof(s16)); + + for (dstPos = 0; dstPos < secondLoopMax; srcPos += gReverbDownsampleRate, dstPos++) + reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) item->toDownsampleLeft[srcPos] + (s32) item->toDownsampleRight[srcPos]) / 2); + bcopy(gSynthesisReverb.ringBuffer.left, gSynthesisReverb.ringBuffer.right, dstPos * sizeof(s16)); + } else { + for (; dstPos < firstLoopMax; dstPos++) + reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) gSynthesisReverb.ringBuffer.left[dstPos] + (s32) gSynthesisReverb.ringBuffer.right[dstPos]) / 2); + bcopy(&gSynthesisReverb.ringBuffer.left[item->startPos], &gSynthesisReverb.ringBuffer.right[item->startPos], (dstPos - item->startPos) * sizeof(s16)); + + for (dstPos = 0; dstPos < secondLoopMax; dstPos++) + reverb_mono_sample(&gSynthesisReverb.ringBuffer.left[dstPos], ((s32) gSynthesisReverb.ringBuffer.left[dstPos] + (s32) gSynthesisReverb.ringBuffer.right[dstPos]) / 2); + bcopy(gSynthesisReverb.ringBuffer.left, gSynthesisReverb.ringBuffer.right, dstPos * sizeof(s16)); + } } else { - for (dstPos = item->startPos; dstPos < ((item->lengthA / 2) + item->startPos); dstPos++) - reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], gSynthesisReverb.ringBuffer.left[dstPos], gSynthesisReverb.ringBuffer.right[dstPos]); - for (dstPos = 0; dstPos < (item->lengthB / 2); dstPos++) - reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], gSynthesisReverb.ringBuffer.left[dstPos], gSynthesisReverb.ringBuffer.right[dstPos]); + if (gReverbDownsampleRate != 1) { + osInvalDCache(item->toDownsampleLeft, DEFAULT_LEN_2CH); + for (srcPos = 0; dstPos < firstLoopMax; srcPos += gReverbDownsampleRate, dstPos++) + reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], item->toDownsampleLeft[srcPos], item->toDownsampleRight[srcPos]); + for (dstPos = 0; dstPos < secondLoopMax; srcPos += gReverbDownsampleRate, dstPos++) + reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], item->toDownsampleLeft[srcPos], item->toDownsampleRight[srcPos]); + } else { + for (; dstPos < firstLoopMax; dstPos++) + reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], gSynthesisReverb.ringBuffer.left[dstPos], gSynthesisReverb.ringBuffer.right[dstPos]); + for (dstPos = 0; dstPos < secondLoopMax; dstPos++) + reverb_samples(&gSynthesisReverb.ringBuffer.left[dstPos], &gSynthesisReverb.ringBuffer.right[dstPos], gSynthesisReverb.ringBuffer.left[dstPos], gSynthesisReverb.ringBuffer.right[dstPos]); + } } } } diff --git a/src/audio/synthesis.h b/src/audio/synthesis.h index 79592b26..c6564e3c 100644 --- a/src/audio/synthesis.h +++ b/src/audio/synthesis.h @@ -19,14 +19,37 @@ #ifdef BETTER_REVERB +#define REVERB_WINDOW_SIZE_MAX 0x2000 + + +/* ------------ BETTER REVERB GENERAL PARAMETERS ------------ */ + #define NUM_ALLPASS 12 // Maximum number of delay filters to use with better reverb; do not change this value if you don't know what you're doing. #define BETTER_REVERB_PTR_SIZE ALIGN16(NUM_ALLPASS * sizeof(s32*) * 2) // Allocation space consumed by dynamically allocated pointers // Size determined by (all delaysL/R values * 8) / (2 ^ Minimum Downsample Factor). -// The default value can be increased or decreased in conjunction with the values in delaysL/R -#define BETTER_REVERB_SIZE ALIGN16(0x1E000 + BETTER_REVERB_PTR_SIZE) // This can be significantly decreased if a downsample rate of 1 is not being used. +// The default value can be increased or decreased in conjunction with the values in delaysL/R. +// This can be significantly decreased if a downsample rate of 1 is not being used or if filter count is less than NUM_ALLPASS, +// as this default is configured to handle the emulator RCVI settings. +#define BETTER_REVERB_SIZE ALIGN16(0x1E000 + BETTER_REVERB_PTR_SIZE) + +/* ------ BETTER REVERB LIGHTWEIGHT PARAMETER OVERRIDES ------ */ + +// Filter count works differently than normal when used with light settings and can support numbers that are not multiples of 3, though 3 is generally recommended. +// This can be reduced to 2 to save a third of runtime overhead, but substantially reduces reverb saturation. +// Similarly this can be increased from 3, but likely won't have beneficial outcomes worth the runtime expense compared to the modification of other parameters without using light settings. +#define BETTER_REVERB_FILTER_COUNT_LIGHT 3 +#define BETTER_REVERB_GAIN_INDEX_LIGHT 0xA0 // Advanced parameter; used to tune the outputs of every filter except for the final one +#define BETTER_REVERB_REVERB_INDEX_LIGHT 0x30 // Advanced parameter; used to tune the incoming output of the final filter +#define BETTER_REVERB_MULTIPLE_LIGHT 0xD0 // Advanced parameter; multiplier applied to the final output signal for both the left and right channels (divided by 256) + + +/* ------------ BETTER REVERB EXTERNED VARIABLES ------------ */ + +extern u8 toggleBetterReverb; extern u8 gBetterReverbPreset; +extern u8 betterReverbLightweight; extern s8 betterReverbDownsampleRate; extern u8 monoReverb; extern s32 reverbFilterCount; @@ -36,10 +59,18 @@ extern s32 betterReverbGainIndex; extern s32 *gReverbMultsL; extern s32 *gReverbMultsR; -extern u8 toggleBetterReverb; -#define REVERB_WINDOW_SIZE_MAX 0x2000 + +/* ------------ BETTER REVERB EXTERNED FUNCTIONS ------------ */ + +void initialize_better_reverb_buffers(void); +void set_better_reverb_buffers(u32 *inputDelaysL, u32 *inputDelaysR); + + +/* -------------- BETTER REVERB STATIC ASSERTS -------------- */ STATIC_ASSERT(NUM_ALLPASS % 3 == 0, "NUM_ALLPASS must be a multiple of 3!"); +STATIC_ASSERT(BETTER_REVERB_FILTER_COUNT_LIGHT >= 2, "BETTER_REVERB_FILTER_COUNT_LIGHT should be no less than 2!"); +STATIC_ASSERT(BETTER_REVERB_FILTER_COUNT_LIGHT <= NUM_ALLPASS, "BETTER_REVERB_FILTER_COUNT_LIGHT cannot be larger than NUM_ALLPASS!"); #else @@ -137,11 +168,6 @@ extern struct SynthesisReverb gSynthesisReverb; extern s16 D_SH_803479B4; #endif -#ifdef BETTER_REVERB -void initialize_better_reverb_buffers(void); -void set_better_reverb_buffers(u32 *inputDelaysL, u32 *inputDelaysR); -#endif - u64 *synthesis_execute(u64 *cmdBuf, s32 *writtenCmds, s16 *aiBuf, s32 bufLen); #if defined(VERSION_JP) || defined(VERSION_US) void note_init_volume(struct Note *note); diff --git a/src/game/puppyprint.c b/src/game/puppyprint.c index 5d7074f2..30651dcb 100644 --- a/src/game/puppyprint.c +++ b/src/game/puppyprint.c @@ -289,7 +289,7 @@ static const char *audioPoolNames[NUM_AUDIO_POOLS] = { "Temporary Sequence Pool:\t ", "Temporary Bank Pool:\t\t ", #ifdef BETTER_REVERB - "Better Reverb Pool", + "Better Reverb Pool:\t\t ", #endif }; @@ -1424,7 +1424,7 @@ void get_char_from_byte(s32 *textX, s32 *textPos, u8 letter, u8 *wideX, u8 *spac } } -// Because we're using bcopy when both reading and writing to the text buffer, this doesn't care about alignment with the multi-bit types. +// Because we're using bcopy when both reading and writing to the text buffer, this doesn't care about alignment with the multi-byte types. struct PuppyprintDeferredBufferHeader { u16 x; u16 y;