From 4ed09090856789096f9ed07ba36c10a3b02e3ffd Mon Sep 17 00:00:00 2001 From: madeline Date: Thu, 9 Apr 2026 08:25:06 -0700 Subject: [PATCH] optimize dsp by 27X --- libs/freeverb/allpass.hpp | 11 +--- libs/freeverb/comb.hpp | 8 +-- libs/freeverb/denormals.h | 63 +++++++++++++++++++---- libs/freeverb/revmodel.cpp | 43 ++++++++++++---- libs/freeverb/revmodel.hpp | 6 +-- src/dusk/audio/DuskDsp.cpp | 102 +++++++++++++++++++------------------ src/dusk/audio/DuskDsp.hpp | 3 -- 7 files changed, 144 insertions(+), 92 deletions(-) diff --git a/libs/freeverb/allpass.hpp b/libs/freeverb/allpass.hpp index 4fc294d139..b9c5904229 100644 --- a/libs/freeverb/allpass.hpp +++ b/libs/freeverb/allpass.hpp @@ -6,7 +6,6 @@ #ifndef _allpass_ #define _allpass_ -#include "denormals.h" class allpass { @@ -29,18 +28,12 @@ public: inline float allpass::process(float input) { - float output; - float bufout; - - bufout = buffer[bufidx]; - undenormalise(bufout); - - output = -input + bufout; + float bufout = buffer[bufidx]; buffer[bufidx] = input + (bufout*feedback); if(++bufidx>=bufsize) bufidx = 0; - return output; + return -input + bufout; } #endif//_allpass diff --git a/libs/freeverb/comb.hpp b/libs/freeverb/comb.hpp index 4a73b615fb..52c01b070e 100644 --- a/libs/freeverb/comb.hpp +++ b/libs/freeverb/comb.hpp @@ -7,7 +7,6 @@ #ifndef _comb_ #define _comb_ -#include "denormals.h" class comb { @@ -35,14 +34,9 @@ private: inline float comb::process(float input) { - float output; - - output = buffer[bufidx]; - undenormalise(output); + float output = buffer[bufidx]; filterstore = (output*damp2) + (filterstore*damp1); - undenormalise(filterstore); - buffer[bufidx] = input + (filterstore*feedback); if(++bufidx>=bufsize) bufidx = 0; diff --git a/libs/freeverb/denormals.h b/libs/freeverb/denormals.h index f871412714..d68cb444f7 100644 --- a/libs/freeverb/denormals.h +++ b/libs/freeverb/denormals.h @@ -1,15 +1,58 @@ -// Macro for killing denormalled numbers -// -// Written by Jezar at Dreampoint, June 2000 -// http://www.dreampoint.co.uk -// Based on IS_DENORMAL macro by Jon Watte -// This code is public domain - #ifndef _denormals_ #define _denormals_ -#define undenormalise(sample) if(((*(unsigned int*)&sample)&0x7f800000)==0) sample=0.0f +#if defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || defined(_M_IX86) -#endif//_denormals_ +#include +using denormal_state = unsigned int; +inline denormal_state denormals_enable() +{ + denormal_state saved = _mm_getcsr(); + _mm_setcsr(saved | 0x8040); // FTZ (0x8000) | DAZ (0x0040) + return saved; +} +inline void denormals_restore(denormal_state saved) { _mm_setcsr(saved); } -//ends +#elif defined(__aarch64__) || defined(_M_ARM64) + +#include +using denormal_state = uint64_t; +inline denormal_state denormals_enable() +{ + denormal_state saved; + asm volatile("mrs %0, fpcr" : "=r"(saved)); + asm volatile("msr fpcr, %0" :: "r"(saved | (1ULL << 24))); // FZ + return saved; +} +inline void denormals_restore(denormal_state saved) +{ + asm volatile("msr fpcr, %0" :: "r"(saved)); +} + +#elif defined(__arm__) || defined(_M_ARM) + +#include +using denormal_state = uint32_t; +inline denormal_state denormals_enable() +{ + denormal_state saved; + asm volatile("vmrs %0, fpscr" : "=r"(saved)); + asm volatile("vmsr fpscr, %0" :: "r"(saved | (1U << 24))); // FZ + return saved; +} +inline void denormals_restore(denormal_state saved) +{ + asm volatile("vmsr fpscr, %0" :: "r"(saved)); +} + +#else + +// unknown platform so denormals will be preserved +#warning "This platform is not supported for denormals, reverb will be very slow!" +using denormal_state = int; +inline denormal_state denormals_enable() { return 0; } +inline void denormals_restore(denormal_state) {} + +#endif + +#endif diff --git a/libs/freeverb/revmodel.cpp b/libs/freeverb/revmodel.cpp index feaa3bc04c..d4f9cf2513 100644 --- a/libs/freeverb/revmodel.cpp +++ b/libs/freeverb/revmodel.cpp @@ -5,6 +5,7 @@ // This code is public domain #include "revmodel.hpp" +#include "denormals.h" revmodel::revmodel() { @@ -71,14 +72,17 @@ void revmodel::mute() } } -void revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip) +float revmodel::processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip, float inputGain) { - float outL,outR,input; + float outL,outR; + float wetSumSqL = 0.0f, wetSumSqR = 0.0f; + long totalSamples = numsamples; + auto savedCSR = denormals_enable(); while(numsamples-- > 0) { outL = outR = 0; - input = (*inputL + *inputR) * gain; + float input = (*inputL + *inputR) * gain * inputGain; // Accumulate comb filters in parallel for(int i=0; i 0) { outL = outR = 0; - input = (*inputL + *inputR) * gain; + float input = (*inputL + *inputR) * gain * inputGain; // Accumulate comb filters in parallel for(int i=0; i #include "Adpcm.hpp" +#include "freeverb/revmodel.hpp" #include "JSystem/JAudio2/JASDriverIF.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/endian.h" @@ -18,6 +19,9 @@ using namespace dusk::audio; ChannelAuxData dusk::audio::ChannelAux[DSP_CHANNELS] = {}; +static revmodel SharedReverb; +static bool ReverbHasTail = false; + static bool sDumpWasActive = false; static FILE* sChannelDumpFiles[DSP_CHANNELS] = {}; @@ -140,37 +144,37 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { std::span channels(JASDsp::CH_BUF, DSP_CHANNELS); + DspSubframe reverbInputL = {}; + DspSubframe reverbInputR = {}; + bool anyReverbInput = false; + for (int i = 0; i < channels.size(); i++) { auto& channel = channels[i]; auto& channelAux = ChannelAux[i]; - bool skipRender = false; - if (!channel.mIsActive) { - skipRender = true; + continue; } else if (channel.mPauseFlag) { // Not really sure what the practical difference between pause and // deactivation is. Either avoids clearing state or allows the DSP to avoid popping? - skipRender = true; + continue; } else if (channel.mForcedStop) { channel.mIsFinished = true; - skipRender = true; + continue; } else if (channel.mWaveAramAddress == 0) { // I think these are oscillator channels? Not backed by audio. // No idea how to implement these yet, so skip them. channel.mIsFinished = true; - skipRender = true; + continue; } + ValidateChannel(channel); + OutputSubframe channelSubframe = {}; - - if (!skipRender) { - ValidateChannel(channel); - RenderChannel(channel, channelAux, channelSubframe); - } + RenderChannel(channel, channelAux, channelSubframe); if (EnableReverb) { // scale the input to the reverb rather than using wet/dry on the output. @@ -178,31 +182,23 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { // so any tail always decays at the correct level regardless of mAutoMixerFxMix changes // prevents transients when the next sound starts playing with a different reverb level // 600.0f was pulled out of my ass and just sounds good enough for console - f32 inputGain = (!skipRender) ? (channel.mAutoMixerFxMix >> 8) / 600.0f : 0.0f; - - OutputSubframe reverbSubframe = {}; - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - reverbSubframe.channels[0][j] = channelSubframe.channels[0][j] * inputGain; - reverbSubframe.channels[1][j] = channelSubframe.channels[1][j] * inputGain; - } - - channelAux.reverb.processreplace( - reverbSubframe.channels[0].data(), reverbSubframe.channels[1].data(), - reverbSubframe.channels[0].data(), reverbSubframe.channels[1].data(), - DSP_SUBFRAME_SIZE, 1 - ); - - for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - channelSubframe.channels[0][j] += reverbSubframe.channels[0][j]; - channelSubframe.channels[1][j] += reverbSubframe.channels[1][j]; + f32 inputGain = (channel.mAutoMixerFxMix >> 8) / 600.0f; + if (inputGain > 0) { + anyReverbInput = true; + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + reverbInputL[j] += channelSubframe.channels[0][j] * inputGain; + reverbInputR[j] += channelSubframe.channels[1][j] * inputGain; + } } } if (DumpAudio && sChannelDumpFiles[i]) { + f32 interleaved[DSP_SUBFRAME_SIZE * 2]; for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { - fwrite(&channelSubframe.channels[0][j], sizeof(f32), 1, sChannelDumpFiles[i]); - fwrite(&channelSubframe.channels[1][j], sizeof(f32), 1, sChannelDumpFiles[i]); + interleaved[j * 2 + 0] = channelSubframe.channels[0][j]; + interleaved[j * 2 + 1] = channelSubframe.channels[1][j]; } + fwrite(interleaved, sizeof(f32), DSP_SUBFRAME_SIZE * 2, sChannelDumpFiles[i]); } for (int o = 0; o < subframe.channels.size(); o++) { @@ -210,6 +206,17 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } } + if (EnableReverb && (anyReverbInput || ReverbHasTail)) { + // Equivalent to -80 dBFS: rms = 1e-4, rms^2 = 1e-8, sumSq = 2 * N * 1e-8 + constexpr f32 REVERB_ENERGY_EPSILON = 2.0f * DSP_SUBFRAME_SIZE * 1e-8f; + f32 wetEnergy = SharedReverb.processmix( + reverbInputL.data(), reverbInputR.data(), + subframe.channels[0].data(), subframe.channels[1].data(), + DSP_SUBFRAME_SIZE, 1, 1.0f + ); + ReverbHasTail = wetEnergy >= REVERB_ENERGY_EPSILON; + } + for (auto& channel : subframe.channels) { ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); } @@ -341,7 +348,7 @@ static void FillDecodeBuf(JASDsp::TChannel& channel, ChannelAuxData& aux, int ne } aux.decodeBufCount += ReadChannelSamplesChunk( - channel, aux, std::min(remainingDecodeSpace, needed - aux.decodeBufCount), + channel, aux, std::min(remainingDecodeSpace, needed - aux.decodeBufCount), aux.decodeBuf + aux.decodeBufCount, remainingDecodeSpace ); } @@ -491,17 +498,17 @@ static void RenderChannel( } DspSubframe audioLoadBuffer = {}; - f64 pos = channelAux.resamplePos; + f32 pos = channelAux.resamplePos; s16 prev = channelAux.resamplePrev; s16 next = channelAux.decodeBufCount > 0 ? channelAux.decodeBuf[0] : prev; int srcIdx = 0; // linear resampling and f32 conversion for (int i = 0; i < DSP_SUBFRAME_SIZE; i++) { - audioLoadBuffer[i] = static_cast(prev + pos * (next - prev)) / 32768.0f; + audioLoadBuffer[i] = (prev + pos * (next - prev)) / 32768.0f; pos += step; - while (pos >= 1.0) { - pos -= 1.0; + while (pos >= 1.0f) { + pos -= 1.0f; prev = next; srcIdx++; next = srcIdx < channelAux.decodeBufCount ? channelAux.decodeBuf[srcIdx] : prev; @@ -529,16 +536,13 @@ static void RenderChannel( } void dusk::audio::DspInit() { - for (int i = 0; i < DSP_CHANNELS; i++) { - auto& channelAux = ChannelAux[i]; - channelAux.reverb.setwet(1.0f); - channelAux.reverb.setdry(0.0f); - channelAux.reverb.setroomsize(0.5f); - channelAux.reverb.setdamp(0.7f); - channelAux.reverb.setwidth(1.0f); - channelAux.reverb.setmode(0.0f); - channelAux.reverb.mute(); - } + SharedReverb.setwet(1.0f); + SharedReverb.setdry(0.0f); + SharedReverb.setroomsize(0.5f); + SharedReverb.setdamp(0.7f); + SharedReverb.setwidth(1.0f); + SharedReverb.setmode(0.0f); + SharedReverb.mute(); } void dusk::audio::ApplyVolume( @@ -549,15 +553,13 @@ void dusk::audio::ApplyVolume( assert(dst.size() >= src.size()); if (startVolume == endVolume) { - for (int i = 0; i < src.size(); i++) { + for (int i = 0; i < (int)src.size(); i++) { dst[i] = src[i] * startVolume; } } else { const f32 step = (endVolume - startVolume) / static_cast(src.size()); - auto curVolume = startVolume; - for (int i = 0; i < src.size(); i++) { - dst[i] = src[i] * curVolume; - curVolume += step; + for (int i = 0; i < (int)src.size(); i++) { + dst[i] = src[i] * (startVolume + i * step); } } } diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 728234b75c..c7cb751458 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -8,8 +8,6 @@ #include "SDL3/SDL_audio.h" #include -#include "freeverb/revmodel.hpp" - // ReSharper disable once CppUnusedIncludeDirective #include "global.h" @@ -31,7 +29,6 @@ namespace dusk::audio { // Used for debugging tools. u32 resetCount; - revmodel reverb; /** * Previous volume values, per output channel.