diff options
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/sounddsp')
8 files changed, 2371 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/sounddsp/AGC.cpp b/Src/external_dependencies/openmpt-trunk/sounddsp/AGC.cpp new file mode 100644 index 00000000..fac3408a --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/sounddsp/AGC.cpp @@ -0,0 +1,141 @@ +/* + * AGC.cpp + * ------- + * Purpose: Automatic Gain Control + * Notes : Ugh... This should really be removed at some point. + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "../sounddsp/AGC.h" + + +OPENMPT_NAMESPACE_BEGIN + + +////////////////////////////////////////////////////////////////////////////////// +// Automatic Gain Control + +#ifndef NO_AGC + +#define AGC_PRECISION 10 +#define AGC_UNITY (1 << AGC_PRECISION) + +// Limiter +#define MIXING_LIMITMAX (0x08100000) +#define MIXING_LIMITMIN (-MIXING_LIMITMAX) + + +static UINT ProcessAGC(int *pBuffer, int *pRearBuffer, std::size_t nSamples, std::size_t nChannels, int nAGC) +{ + if(nChannels == 1) + { + while(nSamples--) + { + int val = (int)(((int64)*pBuffer * (int32)nAGC) >> AGC_PRECISION); + if(val < MIXING_LIMITMIN || val > MIXING_LIMITMAX) nAGC--; + *pBuffer = val; + pBuffer++; + } + } else + { + if(nChannels == 2) + { + while(nSamples--) + { + int fl = (int)(((int64)pBuffer[0] * (int32)nAGC) >> AGC_PRECISION); + int fr = (int)(((int64)pBuffer[1] * (int32)nAGC) >> AGC_PRECISION); + bool dec = false; + dec = dec || (fl < MIXING_LIMITMIN || fl > MIXING_LIMITMAX); + dec = dec || (fr < MIXING_LIMITMIN || fr > MIXING_LIMITMAX); + if(dec) nAGC--; + pBuffer[0] = fl; + pBuffer[1] = fr; + pBuffer += 2; + } + } else if(nChannels == 4) + { + while(nSamples--) + { + int fl = (int)(((int64)pBuffer[0] * (int32)nAGC) >> AGC_PRECISION); + int fr = (int)(((int64)pBuffer[1] * (int32)nAGC) >> AGC_PRECISION); + int rl = (int)(((int64)pRearBuffer[0] * (int32)nAGC) >> AGC_PRECISION); + int rr = (int)(((int64)pRearBuffer[1] * (int32)nAGC) >> AGC_PRECISION); + bool dec = false; + dec = dec || (fl < MIXING_LIMITMIN || fl > MIXING_LIMITMAX); + dec = dec || (fr < MIXING_LIMITMIN || fr > MIXING_LIMITMAX); + dec = dec || (rl < MIXING_LIMITMIN || rl > MIXING_LIMITMAX); + dec = dec || (rr < MIXING_LIMITMIN || rr > MIXING_LIMITMAX); + if(dec) nAGC--; + pBuffer[0] = fl; + pBuffer[1] = fr; + pRearBuffer[0] = rl; + pRearBuffer[1] = rr; + pBuffer += 2; + pRearBuffer += 2; + } + } + } + return nAGC; +} + + +CAGC::CAGC() +{ + Initialize(true, 44100); +} + + +void CAGC::Process(int *MixSoundBuffer, int *RearSoundBuffer, std::size_t count, std::size_t nChannels) +{ + UINT agc = ProcessAGC(MixSoundBuffer, RearSoundBuffer, count, nChannels, m_nAGC); + // Some kind custom law, so that the AGC stays quite stable, but slowly + // goes back up if the sound level stays below a level inversely proportional + // to the AGC level. (J'me comprends) + if((agc >= m_nAGC) && (m_nAGC < AGC_UNITY)) + { + m_nAGCRecoverCount += count; + if(m_nAGCRecoverCount >= m_Timeout) + { + m_nAGCRecoverCount = 0; + m_nAGC++; + } + } else + { + m_nAGC = agc; + m_nAGCRecoverCount = 0; + } +} + + +void CAGC::Adjust(UINT oldVol, UINT newVol) +{ + m_nAGC = m_nAGC * oldVol / newVol; + if (m_nAGC > AGC_UNITY) m_nAGC = AGC_UNITY; +} + + +void CAGC::Initialize(bool bReset, DWORD MixingFreq) +{ + if(bReset) + { + m_nAGC = AGC_UNITY; + m_nAGCRecoverCount = 0; + } + m_Timeout = (MixingFreq >> (AGC_PRECISION-8)) >> 1; +} + + +#else + + +MPT_MSVC_WORKAROUND_LNK4221(AGC) + + +#endif // NO_AGC + + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/sounddsp/AGC.h b/Src/external_dependencies/openmpt-trunk/sounddsp/AGC.h new file mode 100644 index 00000000..4317e035 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/sounddsp/AGC.h @@ -0,0 +1,38 @@ +/* + * AGC.h + * ----- + * Purpose: Automatic Gain Control + * Notes : Ugh... This should really be removed at some point. + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + + +OPENMPT_NAMESPACE_BEGIN + + +#ifndef NO_AGC + +class CAGC +{ +private: + UINT m_nAGC; + std::size_t m_nAGCRecoverCount; + UINT m_Timeout; +public: + CAGC(); + void Initialize(bool bReset, DWORD MixingFreq); +public: + void Process(int *MixSoundBuffer, int *RearSoundBuffer, std::size_t count, std::size_t nChannels); + void Adjust(UINT oldVol, UINT newVol); +}; + +#endif // NO_AGC + + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/sounddsp/DSP.cpp b/Src/external_dependencies/openmpt-trunk/sounddsp/DSP.cpp new file mode 100644 index 00000000..0c8ec501 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/sounddsp/DSP.cpp @@ -0,0 +1,487 @@ +/* + * DSP.cpp + * ----------- + * Purpose: Mixing code for various DSPs (EQ, Mega-Bass, ...) + * Notes : Ugh... This should really be removed at some point. + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "DSP.h" +#include "openmpt/soundbase/MixSample.hpp" +#include <math.h> + +OPENMPT_NAMESPACE_BEGIN + +#ifndef NO_DSP + + +// Bass Expansion +#define DEFAULT_XBASS_RANGE 14 // (x+2)*20 Hz (320Hz) +#define DEFAULT_XBASS_DEPTH 6 // 1+(3>>(x-4)) (+6dB) + + +//////////////////////////////////////////////////////////////////// +// DSP Effects internal state + +static void X86_StereoDCRemoval(int *, uint32 count, int32 &nDCRFlt_Y1l, int32 &nDCRFlt_X1l, int32 &nDCRFlt_Y1r, int32 &nDCRFlt_X1r); +static void X86_MonoDCRemoval(int *, uint32 count, int32 &nDCRFlt_Y1l, int32 &nDCRFlt_X1l); + +/////////////////////////////////////////////////////////////////////////////////// +// +// Biquad setup +// + + +#define PI 3.14159265358979323f +static inline float Sgn(float x) { return (x >= 0) ? 1.0f : -1.0f; } +static void ShelfEQ(int32 scale, + int32 &outA1, int32 &outB0, int32 &outB1, + int32 F_c, int32 F_s, float gainDC, float gainFT, float gainPI) +{ + float a1, b0, b1; + float gainFT2, gainDC2, gainPI2; + float alpha, beta0, beta1, rho; + float wT, quad; + + wT = PI * F_c / F_s; + gainPI2 = gainPI * gainPI; + gainFT2 = gainFT * gainFT; + gainDC2 = gainDC * gainDC; + + quad = gainPI2 + gainDC2 - (gainFT2*2); + + alpha = 0; + + if (quad != 0) + { + float lambda = (gainPI2 - gainDC2) / quad; + alpha = (float)(lambda - Sgn(lambda)*sqrt(lambda*lambda - 1.0f)); + } + + beta0 = 0.5f * ((gainDC + gainPI) + (gainDC - gainPI) * alpha); + beta1 = 0.5f * ((gainDC - gainPI) + (gainDC + gainPI) * alpha); + rho = (float)((sin((wT*0.5f) - (PI/4.0f))) / (sin((wT*0.5f) + (PI/4.0f)))); + + quad = 1.0f / (1.0f + rho*alpha); + + b0 = ((beta0 + rho*beta1) * quad); + b1 = ((beta1 + rho*beta0) * quad); + a1 = - ((rho + alpha) * quad); + + outA1 = mpt::saturate_round<int32>(a1 * scale); + outB0 = mpt::saturate_round<int32>(b0 * scale); + outB1 = mpt::saturate_round<int32>(b1 * scale); +} + + +CSurroundSettings::CSurroundSettings() : m_nProLogicDepth(12), m_nProLogicDelay(20) +{ + +} + + +CMegaBassSettings::CMegaBassSettings() : m_nXBassDepth(DEFAULT_XBASS_DEPTH), m_nXBassRange(DEFAULT_XBASS_RANGE) +{ + +} + + +CSurround::CSurround() +{ + // Surround Encoding: 1 delay line + low-pass filter + high-pass filter + nSurroundSize = 0; + nSurroundPos = 0; + nDolbyDepth = 0; + + // Surround Biquads + nDolbyHP_Y1 = 0; + nDolbyHP_X1 = 0; + nDolbyLP_Y1 = 0; + nDolbyHP_B0 = 0; + nDolbyHP_B1 = 0; + nDolbyHP_A1 = 0; + nDolbyLP_B0 = 0; + nDolbyLP_B1 = 0; + nDolbyLP_A1 = 0; + + MemsetZero(SurroundBuffer); + +} + + +CMegaBass::CMegaBass() +{ + + // Bass Expansion: low-pass filter + nXBassFlt_Y1 = 0; + nXBassFlt_X1 = 0; + nXBassFlt_B0 = 0; + nXBassFlt_B1 = 0; + nXBassFlt_A1 = 0; + + // DC Removal Biquad + nDCRFlt_Y1lf = 0; + nDCRFlt_X1lf = 0; + nDCRFlt_Y1rf = 0; + nDCRFlt_X1rf = 0; + nDCRFlt_Y1lb = 0; + nDCRFlt_X1lb = 0; + nDCRFlt_Y1rb = 0; + nDCRFlt_X1rb = 0; + +} + + +void CSurround::Initialize(bool bReset, DWORD MixingFreq) +{ + MPT_UNREFERENCED_PARAMETER(bReset); + if (!m_Settings.m_nProLogicDelay) m_Settings.m_nProLogicDelay = 20; + + // Pro-Logic Surround + nSurroundPos = nSurroundSize = 0; + { + memset(SurroundBuffer, 0, sizeof(SurroundBuffer)); + nSurroundSize = (MixingFreq * m_Settings.m_nProLogicDelay) / 1000; + if (nSurroundSize > SURROUNDBUFFERSIZE) nSurroundSize = SURROUNDBUFFERSIZE; + nDolbyDepth = m_Settings.m_nProLogicDepth; + if (nDolbyDepth < 1) nDolbyDepth = 1; + if (nDolbyDepth > 16) nDolbyDepth = 16; + // Setup biquad filters + ShelfEQ(1024, nDolbyHP_A1, nDolbyHP_B0, nDolbyHP_B1, 200, MixingFreq, 0, 0.5f, 1); + ShelfEQ(1024, nDolbyLP_A1, nDolbyLP_B0, nDolbyLP_B1, 7000, MixingFreq, 1, 0.75f, 0); + nDolbyHP_X1 = nDolbyHP_Y1 = 0; + nDolbyLP_Y1 = 0; + // Surround Level + nDolbyHP_B0 = (nDolbyHP_B0 * nDolbyDepth) >> 5; + nDolbyHP_B1 = (nDolbyHP_B1 * nDolbyDepth) >> 5; + // +6dB + nDolbyLP_B0 *= 2; + nDolbyLP_B1 *= 2; + } +} + + +void CMegaBass::Initialize(bool bReset, DWORD MixingFreq) +{ + // Bass Expansion Reset + { + int32 a1 = 0, b0 = 1024, b1 = 0; + int nXBassCutOff = 50 + (m_Settings.m_nXBassRange+2) * 20; + int nXBassGain = m_Settings.m_nXBassDepth; + nXBassGain = std::clamp(nXBassGain, 2, 8); + nXBassCutOff = std::clamp(nXBassCutOff, 60, 600); + ShelfEQ(1024, a1, b0, b1, nXBassCutOff, MixingFreq, + 1.0f + (1.0f/16.0f) * (0x300 >> nXBassGain), + 1.0f, + 0.0000001f); + if (nXBassGain > 5) + { + b0 >>= (nXBassGain-5); + b1 >>= (nXBassGain-5); + } + nXBassFlt_A1 = a1; + nXBassFlt_B0 = b0; + nXBassFlt_B1 = b1; + //Log("b0=%d b1=%d a1=%d\n", b0, b1, a1); + } + if (bReset) + { + nXBassFlt_X1 = 0; + nXBassFlt_Y1 = 0; + nDCRFlt_X1lf = 0; + nDCRFlt_X1rf = 0; + nDCRFlt_Y1lf = 0; + nDCRFlt_Y1rf = 0; + nDCRFlt_X1lb = 0; + nDCRFlt_X1rb = 0; + nDCRFlt_Y1lb = 0; + nDCRFlt_Y1rb = 0; + } +} + + +// 2-channel surround +void CSurround::ProcessStereoSurround(int * MixSoundBuffer, int count) +{ + int *pr = MixSoundBuffer, hy1 = nDolbyHP_Y1; + for (int r=count; r; r--) + { + // Delay + int secho = SurroundBuffer[nSurroundPos]; + SurroundBuffer[nSurroundPos] = (pr[0]+pr[1]+256) >> 9; + // High-pass + int v0 = (nDolbyHP_B0 * secho + nDolbyHP_B1 * nDolbyHP_X1 + nDolbyHP_A1 * hy1) >> 10; + nDolbyHP_X1 = secho; + // Low-pass + int v = (nDolbyLP_B0 * v0 + nDolbyLP_B1 * hy1 + nDolbyLP_A1 * nDolbyLP_Y1) >> (10-8); + hy1 = v0; + nDolbyLP_Y1 = v >> 8; + // Add echo + pr[0] += v; + pr[1] -= v; + if (++nSurroundPos >= nSurroundSize) nSurroundPos = 0; + pr += 2; + } + nDolbyHP_Y1 = hy1; +} + + +// 4-channels surround +void CSurround::ProcessQuadSurround(int * MixSoundBuffer, int * MixRearBuffer, int count) +{ + int *pr = MixSoundBuffer, hy1 = nDolbyHP_Y1; + for (int r=count; r; r--) + { + int vl = pr[0] >> 1; + int vr = pr[1] >> 1; + pr[(uint32)(MixRearBuffer-MixSoundBuffer)] += vl; + pr[((uint32)(MixRearBuffer-MixSoundBuffer))+1] += vr; + // Delay + int secho = SurroundBuffer[nSurroundPos]; + SurroundBuffer[nSurroundPos] = (vr+vl+256) >> 9; + // High-pass + int v0 = (nDolbyHP_B0 * secho + nDolbyHP_B1 * nDolbyHP_X1 + nDolbyHP_A1 * hy1) >> 10; + nDolbyHP_X1 = secho; + // Low-pass + int v = (nDolbyLP_B0 * v0 + nDolbyLP_B1 * hy1 + nDolbyLP_A1 * nDolbyLP_Y1) >> (10-8); + hy1 = v0; + nDolbyLP_Y1 = v >> 8; + // Add echo + pr[(uint32)(MixRearBuffer-MixSoundBuffer)] += v; + pr[((uint32)(MixRearBuffer-MixSoundBuffer))+1] += v; + if (++nSurroundPos >= nSurroundSize) nSurroundPos = 0; + pr += 2; + } + nDolbyHP_Y1 = hy1; +} + + +void CSurround::Process(int * MixSoundBuffer, int * MixRearBuffer, int count, uint32 nChannels) +{ + + if(nChannels >= 2) + + // Dolby Pro-Logic Surround + { + if (nChannels > 2) ProcessQuadSurround(MixSoundBuffer, MixRearBuffer, count); else + ProcessStereoSurround(MixSoundBuffer, count); + } + +} + + +void CMegaBass::Process(int * MixSoundBuffer, int * MixRearBuffer, int count, uint32 nChannels) +{ + + if(nChannels >= 2) + { + X86_StereoDCRemoval(MixSoundBuffer, count, nDCRFlt_Y1lf, nDCRFlt_X1lf, nDCRFlt_Y1rf, nDCRFlt_X1rf); + if(nChannels > 2) X86_StereoDCRemoval(MixRearBuffer, count, nDCRFlt_Y1lb, nDCRFlt_X1lb, nDCRFlt_Y1rb, nDCRFlt_X1rb); + int *px = MixSoundBuffer; + int *py = MixRearBuffer; + int x1 = nXBassFlt_X1; + int y1 = nXBassFlt_Y1; + if(nChannels > 2) for (int x=count; x; x--) + { + int x_m = (px[0]+px[1]+py[0]+py[1]+0x100)>>9; + + y1 = (nXBassFlt_B0 * x_m + nXBassFlt_B1 * x1 + nXBassFlt_A1 * y1) >> (10-8); + x1 = x_m; + px[0] += y1; + px[1] += y1; + py[0] += y1; + py[1] += y1; + y1 = (y1+0x80) >> 8; + px += 2; + py += 2; + } else for (int x=count; x; x--) + { + int x_m = (px[0]+px[1]+0x100)>>9; + + y1 = (nXBassFlt_B0 * x_m + nXBassFlt_B1 * x1 + nXBassFlt_A1 * y1) >> (10-8); + x1 = x_m; + px[0] += y1; + px[1] += y1; + y1 = (y1+0x80) >> 8; + px += 2; + } + nXBassFlt_X1 = x1; + nXBassFlt_Y1 = y1; + } else + { + X86_MonoDCRemoval(MixSoundBuffer, count, nDCRFlt_Y1lf, nDCRFlt_X1lf); + int *px = MixSoundBuffer; + int x1 = nXBassFlt_X1; + int y1 = nXBassFlt_Y1; + for (int x=count; x; x--) + { + int x_m = (px[0]+0x80)>>8; + + y1 = (nXBassFlt_B0 * x_m + nXBassFlt_B1 * x1 + nXBassFlt_A1 * y1) >> (10-8); + x1 = x_m; + px[0] += y1; + y1 = (y1+0x40) >> 8; + px++; + } + nXBassFlt_X1 = x1; + nXBassFlt_Y1 = y1; + } + +} + + + +////////////////////////////////////////////////////////////////////////// +// +// DC Removal +// + +#define DCR_AMOUNT 9 + +static void X86_StereoDCRemoval(int *pBuffer, uint32 nSamples, int32 &nDCRFlt_Y1l, int32 &nDCRFlt_X1l, int32 &nDCRFlt_Y1r, int32 &nDCRFlt_X1r) +{ + int y1l = nDCRFlt_Y1l, x1l = nDCRFlt_X1l; + int y1r = nDCRFlt_Y1r, x1r = nDCRFlt_X1r; + + while(nSamples--) + { + int inL = pBuffer[0]; + int inR = pBuffer[1]; + int diffL = x1l - inL; + int diffR = x1r - inR; + x1l = inL; + x1r = inR; + int outL = diffL / (1 << (DCR_AMOUNT + 1)) - diffL + y1l; + int outR = diffR / (1 << (DCR_AMOUNT + 1)) - diffR + y1r; + pBuffer[0] = outL; + pBuffer[1] = outR; + pBuffer += 2; + y1l = outL - outL / (1 << DCR_AMOUNT); + y1r = outR - outR / (1 << DCR_AMOUNT); + } + + nDCRFlt_Y1l = y1l; + nDCRFlt_X1l = x1l; + nDCRFlt_Y1r = y1r; + nDCRFlt_X1r = x1r; +} + + +static void X86_MonoDCRemoval(int *pBuffer, uint32 nSamples, int32 &nDCRFlt_Y1l, int32 &nDCRFlt_X1l) +{ + int y1l = nDCRFlt_Y1l, x1l = nDCRFlt_X1l; + while(nSamples--) + { + int inM = pBuffer[0]; + int diff = x1l - inM; + x1l = inM; + pBuffer[0] = inM = diff / (1 << (DCR_AMOUNT + 1)) - diff + y1l; + pBuffer++; + y1l = inM - inM / (1 << DCR_AMOUNT); + } + + nDCRFlt_Y1l = y1l; + nDCRFlt_X1l = x1l; +} + + +///////////////////////////////////////////////////////////////// +// Clean DSP Effects interface + +// [XBass level 0(quiet)-100(loud)], [cutoff in Hz 20-100] +void CMegaBass::SetXBassParameters(uint32 nDepth, uint32 nRange) +{ + if (nDepth > 100) nDepth = 100; + uint32 gain = nDepth / 20; + if (gain > 4) gain = 4; + m_Settings.m_nXBassDepth = 8 - gain; // filter attenuation 1/256 .. 1/16 + uint32 range = nRange / 5; + if (range > 5) range -= 5; else range = 0; + if (nRange > 16) nRange = 16; + m_Settings.m_nXBassRange = 21 - range; // filter average on 0.5-1.6ms +} + + +// [Surround level 0(quiet)-100(heavy)] [delay in ms, usually 5-50ms] +void CSurround::SetSurroundParameters(uint32 nDepth, uint32 nDelay) +{ + uint32 gain = (nDepth * 16) / 100; + if (gain > 16) gain = 16; + if (gain < 1) gain = 1; + m_Settings.m_nProLogicDepth = gain; + if (nDelay < 4) nDelay = 4; + if (nDelay > 50) nDelay = 50; + m_Settings.m_nProLogicDelay = nDelay; +} + + +BitCrushSettings::BitCrushSettings() + : m_Bits(8) +{ + return; +} + + +BitCrush::BitCrush() +{ +} + + +void BitCrush::Initialize(bool bReset, DWORD MixingFreq) +{ + MPT_UNREFERENCED_PARAMETER(bReset); + MPT_UNREFERENCED_PARAMETER(MixingFreq); +} + + +void BitCrush::Process(int * MixSoundBuffer, int * MixRearBuffer, int count, uint32 nChannels) +{ + if(m_Settings.m_Bits <= 0) + { + return; + } + if(m_Settings.m_Bits > MixSampleIntTraits::mix_precision_bits) + { + return; + } + unsigned int mask = ~((1u << (MixSampleIntTraits::mix_precision_bits - m_Settings.m_Bits)) - 1u); + if(nChannels == 4) + { + for(int frame = 0; frame < count; ++frame) + { + MixSoundBuffer[frame*2 + 0] &= mask; + MixSoundBuffer[frame*2 + 1] &= mask; + MixRearBuffer[frame*2 + 0] &= mask; + MixRearBuffer[frame*2 + 1] &= mask; + } + } else if(nChannels == 2) + { + for(int frame = 0; frame < count; ++frame) + { + MixSoundBuffer[frame*2 + 0] &= mask; + MixSoundBuffer[frame*2 + 1] &= mask; + } + } else if(nChannels == 1) + { + for(int frame = 0; frame < count; ++frame) + { + MixSoundBuffer[frame] &= mask; + } + } +} + + +#else + + +MPT_MSVC_WORKAROUND_LNK4221(DSP) + + +#endif // NO_DSP + + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/sounddsp/DSP.h b/Src/external_dependencies/openmpt-trunk/sounddsp/DSP.h new file mode 100644 index 00000000..90eb9849 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/sounddsp/DSP.h @@ -0,0 +1,142 @@ +/* + * DSP.h + * ----- + * Purpose: Mixing code for various DSPs (EQ, Mega-Bass, ...) + * Notes : Ugh... This should really be removed at some point. + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + + +OPENMPT_NAMESPACE_BEGIN + + +#ifndef NO_DSP + +// Buffer Sizes +#define SURROUNDBUFFERSIZE 2048 // 50ms @ 48kHz + + +class CSurroundSettings +{ +public: + uint32 m_nProLogicDepth; + uint32 m_nProLogicDelay; +public: + CSurroundSettings(); +}; + + +class CMegaBassSettings +{ +public: + uint32 m_nXBassDepth; + uint32 m_nXBassRange; +public: + CMegaBassSettings(); +}; + + +struct BitCrushSettings +{ + int m_Bits; + BitCrushSettings(); +}; + + +class CSurround +{ +public: + CSurroundSettings m_Settings; + + // Surround Encoding: 1 delay line + low-pass filter + high-pass filter + int32 nSurroundSize; + int32 nSurroundPos; + int32 nDolbyDepth; + + // Surround Biquads + int32 nDolbyHP_Y1; + int32 nDolbyHP_X1; + int32 nDolbyLP_Y1; + int32 nDolbyHP_B0; + int32 nDolbyHP_B1; + int32 nDolbyHP_A1; + int32 nDolbyLP_B0; + int32 nDolbyLP_B1; + int32 nDolbyLP_A1; + + int32 SurroundBuffer[SURROUNDBUFFERSIZE]; + +public: + CSurround(); +public: + void SetSettings(const CSurroundSettings &settings) { m_Settings = settings; } + // [XBass level 0(quiet)-100(loud)], [cutoff in Hz 10-100] + bool SetXBassParameters(uint32 nDepth, uint32 nRange); + // [Surround level 0(quiet)-100(heavy)] [delay in ms, usually 5-40ms] + void SetSurroundParameters(uint32 nDepth, uint32 nDelay); + void Initialize(bool bReset, DWORD MixingFreq); + void Process(int * MixSoundBuffer, int * MixRearBuffer, int count, uint32 nChannels); +private: + void ProcessStereoSurround(int * MixSoundBuffer, int count); + void ProcessQuadSurround(int * MixSoundBuffer, int * MixRearBuffer, int count); +}; + + + +class CMegaBass +{ +public: + CMegaBassSettings m_Settings; + + // Bass Expansion: low-pass filter + int32 nXBassFlt_Y1; + int32 nXBassFlt_X1; + int32 nXBassFlt_B0; + int32 nXBassFlt_B1; + int32 nXBassFlt_A1; + + // DC Removal Biquad + int32 nDCRFlt_Y1lf; + int32 nDCRFlt_X1lf; + int32 nDCRFlt_Y1rf; + int32 nDCRFlt_X1rf; + int32 nDCRFlt_Y1lb; + int32 nDCRFlt_X1lb; + int32 nDCRFlt_Y1rb; + int32 nDCRFlt_X1rb; + +public: + CMegaBass(); +public: + void SetSettings(const CMegaBassSettings &settings) { m_Settings = settings; } + // [XBass level 0(quiet)-100(loud)], [cutoff in Hz 10-100] + void SetXBassParameters(uint32 nDepth, uint32 nRange); + void Initialize(bool bReset, DWORD MixingFreq); + void Process(int * MixSoundBuffer, int * MixRearBuffer, int count, uint32 nChannels); +}; + + +class BitCrush +{ +public: + BitCrushSettings m_Settings; +public: + BitCrush(); +public: + void SetSettings(const BitCrushSettings &settings) { m_Settings = settings; } + void Initialize(bool bReset, DWORD MixingFreq); + void Process(int * MixSoundBuffer, int * MixRearBuffer, int count, uint32 nChannels); +}; + + +#endif // NO_DSP + + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/sounddsp/EQ.cpp b/Src/external_dependencies/openmpt-trunk/sounddsp/EQ.cpp new file mode 100644 index 00000000..5ffa22bc --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/sounddsp/EQ.cpp @@ -0,0 +1,237 @@ +/* + * EQ.cpp + * ------ + * Purpose: Mixing code for equalizer. + * Notes : Ugh... This should really be removed at some point. + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" + +#include "EQ.h" + +#include "mpt/audio/span.hpp" +#include "mpt/base/numbers.hpp" +#include "openmpt/base/Types.hpp" +#include "openmpt/soundbase/MixSample.hpp" +#include "openmpt/soundbase/MixSampleConvert.hpp" + +#ifndef NO_EQ +#include "../misc/mptCPU.h" +#endif + +#include <algorithm> +#include <array> + +#include <cstddef> + +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE) +#include <xmmintrin.h> +#endif + + +OPENMPT_NAMESPACE_BEGIN + + +#ifndef NO_EQ + + +static constexpr float EQ_BANDWIDTH = 2.0f; + + +static constexpr std::array<uint32, 33> gEqLinearToDB = +{ + 16, 19, 22, 25, 28, 31, 34, 37, + 40, 43, 46, 49, 52, 55, 58, 61, + 64, 76, 88, 100, 112, 124, 136, 148, + 160, 172, 184, 196, 208, 220, 232, 244, 256 +}; + + +static constexpr std::array<EQBANDSETTINGS, MAX_EQ_BANDS> gEQDefaults = +{{ + // Default: Flat EQ + {0,0,0,0,0, 1, 120}, + {0,0,0,0,0, 1, 600}, + {0,0,0,0,0, 1, 1200}, + {0,0,0,0,0, 1, 3000}, + {0,0,0,0,0, 1, 6000}, + {0,0,0,0,0, 1, 10000} +}}; + + +template <std::size_t channels, typename Tbuf> +static void EQFilter(Tbuf & buf, const std::array<EQBANDSETTINGS, MAX_EQ_BANDS> &bands, std::array<std::array<EQBANDSTATE, MAX_EQ_BANDS>, MAX_EQ_CHANNELS> &states) +{ + for(std::size_t frame = 0; frame < buf.size_frames(); ++frame) + { + for(std::size_t channel = 0; channel < channels; ++channel) + { + float sample = mix_sample_cast<float>(buf(channel, frame)); + for(std::size_t b = 0; b < std::size(bands); ++b) + { + const EQBANDSETTINGS &band = bands[b]; + if(band.Gain != 1.0f) + { + EQBANDSTATE &bandState = states[channel][b]; + float x = sample; + float y = band.a1 * bandState.x1 + band.a2 * bandState.x2 + band.a0 * x + band.b1 * bandState.y1 + band.b2 * bandState.y2; + bandState.x2 = bandState.x1; + bandState.y2 = bandState.y1; + bandState.x1 = x; + bandState.y1 = y; + sample = y; + } + } + buf(channel, frame) = mix_sample_cast<typename Tbuf::sample_type>(sample); + } + } +} + + +template <typename TMixSample> +void CEQ::ProcessTemplate(TMixSample *frontBuffer, TMixSample *rearBuffer, std::size_t countFrames, std::size_t numChannels) +{ +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE) + unsigned int old_csr = 0; + if(CPU::HasFeatureSet(CPU::feature::sse)) + { + old_csr = _mm_getcsr(); + _mm_setcsr((old_csr & ~(_MM_DENORMALS_ZERO_MASK | _MM_FLUSH_ZERO_MASK)) | _MM_DENORMALS_ZERO_ON | _MM_FLUSH_ZERO_ON); + } +#endif + if(numChannels == 1) + { + mpt::audio_span_interleaved<TMixSample> buf{ frontBuffer, 1, countFrames }; + EQFilter<1>(buf, m_Bands, m_ChannelState); + } else if(numChannels == 2) + { + mpt::audio_span_interleaved<TMixSample> buf{ frontBuffer, 2, countFrames }; + EQFilter<2>(buf, m_Bands, m_ChannelState); + } else if(numChannels == 4) + { + std::array<TMixSample*, 4> buffers = { &frontBuffer[0], &frontBuffer[1], &rearBuffer[0], &rearBuffer[1] }; + mpt::audio_span_planar_strided<TMixSample> buf{ buffers.data(), 4, countFrames, 2 }; + EQFilter<4>(buf, m_Bands, m_ChannelState); + } +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE) + if(CPU::HasFeatureSet(CPU::feature::sse)) + { + _mm_setcsr(old_csr); + } +#endif +} + + +void CEQ::Process(MixSampleInt *frontBuffer, MixSampleInt *rearBuffer, std::size_t countFrames, std::size_t numChannels) +{ + ProcessTemplate<MixSampleInt>(frontBuffer, rearBuffer, countFrames, numChannels); +} + + +void CEQ::Process(MixSampleFloat *frontBuffer, MixSampleFloat *rearBuffer, std::size_t countFrames, std::size_t numChannels) +{ + ProcessTemplate<MixSampleFloat>(frontBuffer, rearBuffer, countFrames, numChannels); +} + + +CEQ::CEQ() + : m_Bands(gEQDefaults) +{ + return; +} + + +void CEQ::Initialize(bool bReset, uint32 MixingFreq) +{ + float fMixingFreq = static_cast<float>(MixingFreq); + // Gain = 0.5 (-6dB) .. 2 (+6dB) + for(std::size_t band = 0; band < MAX_EQ_BANDS; ++band) + { + float k, k2, r, f; + float v0, v1; + bool b = bReset; + f = m_Bands[band].CenterFrequency / fMixingFreq; + if(f > 0.45f) + { + m_Bands[band].Gain = 1.0f; + } + k = f * mpt::numbers::pi_v<float>; + k = k + k*f; + k2 = k*k; + v0 = m_Bands[band].Gain; + v1 = 1; + if(m_Bands[band].Gain < 1.0f) + { + v0 *= (0.5f/EQ_BANDWIDTH); + v1 *= (0.5f/EQ_BANDWIDTH); + } else + { + v0 *= (1.0f/EQ_BANDWIDTH); + v1 *= (1.0f/EQ_BANDWIDTH); + } + r = (1 + v0*k + k2) / (1 + v1*k + k2); + if(r != m_Bands[band].a0) + { + m_Bands[band].a0 = r; + b = true; + } + r = 2 * (k2 - 1) / (1 + v1*k + k2); + if(r != m_Bands[band].a1) + { + m_Bands[band].a1 = r; + b = true; + } + r = (1 - v0*k + k2) / (1 + v1*k + k2); + if(r != m_Bands[band].a2) + { + m_Bands[band].a2 = r; + b = true; + } + r = - 2 * (k2 - 1) / (1 + v1*k + k2); + if(r != m_Bands[band].b1) + { + m_Bands[band].b1 = r; + b = true; + } + r = - (1 - v1*k + k2) / (1 + v1*k + k2); + if(r != m_Bands[band].b2) + { + m_Bands[band].b2 = r; + b = true; + } + if(b) + { + for(std::size_t channel = 0; channel < MAX_EQ_CHANNELS; ++channel) + { + m_ChannelState[channel][band] = EQBANDSTATE{}; + } + } + } +} + + +void CEQ::SetEQGains(const uint32 *pGains, const uint32 *pFreqs, bool bReset, uint32 MixingFreq) +{ + for(std::size_t i = 0; i < MAX_EQ_BANDS; ++i) + { + m_Bands[i].Gain = static_cast<float>(gEqLinearToDB[std::clamp(pGains[i], static_cast<uint32>(0), static_cast<uint32>(std::size(gEqLinearToDB) - 1))]) / 64.0f; + m_Bands[i].CenterFrequency = static_cast<float>(pFreqs[i]); + } + Initialize(bReset, MixingFreq); +} + + +#else + + +MPT_MSVC_WORKAROUND_LNK4221(EQ) + + +#endif // !NO_EQ + + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/sounddsp/EQ.h b/Src/external_dependencies/openmpt-trunk/sounddsp/EQ.h new file mode 100644 index 00000000..77d451f7 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/sounddsp/EQ.h @@ -0,0 +1,66 @@ +/* + * EQ.h + * ---- + * Purpose: Mixing code for equalizer. + * Notes : Ugh... This should really be removed at some point. + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#include "openmpt/base/Types.hpp" +#include "openmpt/soundbase/MixSample.hpp" + +#include <array> + +#include <cstddef> + +OPENMPT_NAMESPACE_BEGIN + +#ifndef NO_EQ + +inline constexpr std::size_t MAX_EQ_CHANNELS = 4; +inline constexpr std::size_t MAX_EQ_BANDS = 6; + +struct EQBANDSTATE +{ + float x1 = 0.0f; + float x2 = 0.0f; + float y1 = 0.0f; + float y2 = 0.0f; +}; + +struct EQBANDSETTINGS +{ + float a0; + float a1; + float a2; + float b1; + float b2; + float Gain; + float CenterFrequency; +}; + +class CEQ +{ +private: + std::array<std::array<EQBANDSTATE, MAX_EQ_BANDS>, MAX_EQ_CHANNELS> m_ChannelState; + std::array<EQBANDSETTINGS, MAX_EQ_BANDS> m_Bands; + template <typename TMixSample> + void ProcessTemplate(TMixSample *frontBuffer, TMixSample *rearBuffer, std::size_t countFrames, std::size_t numChannels); +public: + CEQ(); + void Initialize(bool bReset, uint32 MixingFreq); + void Process(MixSampleInt *frontBuffer, MixSampleInt *rearBuffer, std::size_t countFrames, std::size_t numChannels); + void Process(MixSampleFloat *frontBuffer, MixSampleFloat *rearBuffer, std::size_t countFrames, std::size_t numChannels); + void SetEQGains(const uint32 *pGains, const uint32 *pFreqs, bool bReset, uint32 MixingFreq); +}; + +#endif // !NO_EQ + +OPENMPT_NAMESPACE_END diff --git a/Src/external_dependencies/openmpt-trunk/sounddsp/Reverb.cpp b/Src/external_dependencies/openmpt-trunk/sounddsp/Reverb.cpp new file mode 100644 index 00000000..5355568c --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/sounddsp/Reverb.cpp @@ -0,0 +1,1040 @@ +/* + * Reverb.cpp + * ---------- + * Purpose: Mixing code for reverb. + * Notes : Ugh... This should really be removed at some point. + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" + +#ifndef NO_REVERB +#include "Reverb.h" +#include "../soundlib/MixerLoops.h" +#include "mpt/base/numbers.hpp" + +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +#include <emmintrin.h> +#endif + +#endif // NO_REVERB + + +OPENMPT_NAMESPACE_BEGIN + + +#ifndef NO_REVERB + + +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +// Load two 32-bit values +static MPT_FORCEINLINE __m128i Load64SSE(const int32 *x) { return _mm_loadl_epi64(reinterpret_cast<const __m128i *>(x)); } +// Load four 16-bit values +static MPT_FORCEINLINE __m128i Load64SSE(const LR16 (&x)[2]) { return _mm_loadl_epi64(&reinterpret_cast<const __m128i &>(x)); } +// Store two 32-bit or four 16-bit values from register +static MPT_FORCEINLINE void Store64SSE(int32 *dst, __m128i src) { return _mm_storel_epi64(reinterpret_cast<__m128i *>(dst), src); } +static MPT_FORCEINLINE void Store64SSE(LR16 (&dst)[2], __m128i src) { return _mm_storel_epi64(&reinterpret_cast<__m128i &>(dst), src); } +#endif + + +CReverb::CReverb() +{ + // Reverb mix buffers + MemsetZero(g_RefDelay); + MemsetZero(g_LateReverb); +} + + +static int32 OnePoleLowPassCoef(int32 scale, float g, float F_c, float F_s) +{ + if(g > 0.999999f) return 0; + + g *= g; + double scale_over_1mg = scale / (1.0 - g); + double cosw = std::cos((2.0 * mpt::numbers::pi) * F_c / F_s); + return mpt::saturate_round<int32>((1.0 - (std::sqrt((g + g) * (1.0 - cosw) - g * g * (1.0 - cosw * cosw)) + g * cosw)) * scale_over_1mg); +} + +static float mBToLinear(int32 value_mB) +{ + if(!value_mB) return 1; + if(value_mB <= -100000) return 0; + + const double val = value_mB * 3.321928094887362304 / (100.0 * 20.0); // log2(10)/(100*20) + return static_cast<float>(std::pow(2.0, val - static_cast<int32>(0.5 + val))); +} + +static int32 mBToLinear(int32 scale, int32 value_mB) +{ + return mpt::saturate_round<int32>(mBToLinear(value_mB) * scale); +} + +static constexpr std::pair<SNDMIX_REVERB_PROPERTIES, const char *> ReverbPresets[NUM_REVERBTYPES] = +{ + // Examples simulating General MIDI 2'musical' reverb presets + // Name (Decay time) Description + // Plate (1.3s) A plate reverb simulation. + {{ -1000, -200, 1.30f,0.90f, 0,0.002f, 0,0.010f,100.0f, 75.0f }, "GM Plate"}, + // Small Room (1.1s) A small size room with a length of 5m or so. + {{ -1000, -600, 1.10f,0.83f, -400,0.005f, 500,0.010f,100.0f,100.0f }, "GM Small Room"}, + // Medium Room (1.3s) A medium size room with a length of 10m or so. + {{ -1000, -600, 1.30f,0.83f, -1000,0.010f, -200,0.020f,100.0f,100.0f }, "GM Medium Room"}, + // Large Room (1.5s) A large size room suitable for live performances. + {{ -1000, -600, 1.50f,0.83f, -1600,0.020f, -1000,0.040f,100.0f,100.0f }, "GM Large Room"}, + // Medium Hall (1.8s) A medium size concert hall. + {{ -1000, -600, 1.80f,0.70f, -1300,0.015f, -800,0.030f,100.0f,100.0f }, "GM Medium Hall"}, + // Large Hall (1.8s) A large size concert hall suitable for a full orchestra. + {{ -1000, -600, 1.80f,0.70f, -2000,0.030f, -1400,0.060f,100.0f,100.0f }, "GM Large Hall"}, + + {{ -1000, -100, 1.49f,0.83f, -2602,0.007f, 200,0.011f,100.0f,100.0f }, "Generic"}, + {{ -1000,-6000, 0.17f,0.10f, -1204,0.001f, 207,0.002f,100.0f,100.0f }, "Padded Cell"}, + {{ -1000, -454, 0.40f,0.83f, -1646,0.002f, 53,0.003f,100.0f,100.0f }, "Room"}, + {{ -1000,-1200, 1.49f,0.54f, -370,0.007f, 1030,0.011f,100.0f, 60.0f }, "Bathroom"}, + {{ -1000,-6000, 0.50f,0.10f, -1376,0.003f, -1104,0.004f,100.0f,100.0f }, "Living Room"}, + {{ -1000, -300, 2.31f,0.64f, -711,0.012f, 83,0.017f,100.0f,100.0f }, "Stone Room"}, + {{ -1000, -476, 4.32f,0.59f, -789,0.020f, -289,0.030f,100.0f,100.0f }, "Auditorium"}, + {{ -1000, -500, 3.92f,0.70f, -1230,0.020f, -2,0.029f,100.0f,100.0f }, "Concert Hall"}, + {{ -1000, 0, 2.91f,1.30f, -602,0.015f, -302,0.022f,100.0f,100.0f }, "Cave"}, + {{ -1000, -698, 7.24f,0.33f, -1166,0.020f, 16,0.030f,100.0f,100.0f }, "Arena"}, + {{ -1000,-1000,10.05f,0.23f, -602,0.020f, 198,0.030f,100.0f,100.0f }, "Hangar"}, + {{ -1000,-4000, 0.30f,0.10f, -1831,0.002f, -1630,0.030f,100.0f,100.0f }, "Carpeted Hallway"}, + {{ -1000, -300, 1.49f,0.59f, -1219,0.007f, 441,0.011f,100.0f,100.0f }, "Hallway"}, + {{ -1000, -237, 2.70f,0.79f, -1214,0.013f, 395,0.020f,100.0f,100.0f }, "Stone Corridor"}, + {{ -1000, -270, 1.49f,0.86f, -1204,0.007f, -4,0.011f,100.0f,100.0f }, "Alley"}, + {{ -1000,-3300, 1.49f,0.54f, -2560,0.162f, -613,0.088f, 79.0f,100.0f }, "Forest"}, + {{ -1000, -800, 1.49f,0.67f, -2273,0.007f, -2217,0.011f, 50.0f,100.0f }, "City"}, + {{ -1000,-2500, 1.49f,0.21f, -2780,0.300f, -2014,0.100f, 27.0f,100.0f }, "Mountains"}, + {{ -1000,-1000, 1.49f,0.83f,-10000,0.061f, 500,0.025f,100.0f,100.0f }, "Quarry"}, + {{ -1000,-2000, 1.49f,0.50f, -2466,0.179f, -2514,0.100f, 21.0f,100.0f }, "Plain"}, + {{ -1000, 0, 1.65f,1.50f, -1363,0.008f, -1153,0.012f,100.0f,100.0f }, "Parking Lot"}, + {{ -1000,-1000, 2.81f,0.14f, 429,0.014f, 648,0.021f, 80.0f, 60.0f }, "Sewer Pipe"}, + {{ -1000,-4000, 1.49f,0.10f, -449,0.007f, 1700,0.011f,100.0f,100.0f }, "Underwater"}, +}; + +mpt::ustring GetReverbPresetName(uint32 preset) +{ + return (preset < NUM_REVERBTYPES) ? mpt::ToUnicode(mpt::Charset::ASCII, ReverbPresets[preset].second) : mpt::ustring{}; +} + +const SNDMIX_REVERB_PROPERTIES *GetReverbPreset(uint32 preset) +{ + return (preset < NUM_REVERBTYPES) ? &ReverbPresets[preset].first : nullptr; +} + +////////////////////////////////////////////////////////////////////////// +// +// I3DL2 environmental reverb support +// + +struct REFLECTIONPRESET +{ + int32 lDelayFactor; + int16 sGainLL, sGainRR, sGainLR, sGainRL; +}; + +const REFLECTIONPRESET gReflectionsPreset[ENVIRONMENT_NUMREFLECTIONS] = +{ + // %Delay, ll, rr, lr, rl + {0, 9830, 6554, 0, 0}, + {10, 6554, 13107, 0, 0}, + {24, -9830, 13107, 0, 0}, + {36, 13107, -6554, 0, 0}, + {54, 16384, 16384, -1638, -1638}, + {61, -13107, 8192, -328, -328}, + {73, -11468, -11468, -3277, 3277}, + {87, 13107, -9830, 4916, -4916} +}; + +//////////////////////////////////////////////////////////////////////////////////// +// +// Implementation +// + +MPT_FORCEINLINE int32 ftol(float f) { return static_cast<int32>(f); } + +static void I3dl2_to_Generic( + const SNDMIX_REVERB_PROPERTIES *pReverb, + EnvironmentReverb *pRvb, + float flOutputFreq, + int32 lMinRefDelay, + int32 lMaxRefDelay, + int32 lMinRvbDelay, + int32 lMaxRvbDelay, + int32 lTankLength) +{ + float flDelayFactor, flDelayFactorHF, flDecayTimeHF; + int32 lDensity, lTailDiffusion; + + // Common parameters + pRvb->ReverbLevel = pReverb->lReverb; + pRvb->ReflectionsLevel = pReverb->lReflections; + pRvb->RoomHF = pReverb->lRoomHF; + + // HACK: Somewhat normalize the reverb output level + int32 lMaxLevel = (pRvb->ReverbLevel > pRvb->ReflectionsLevel) ? pRvb->ReverbLevel : pRvb->ReflectionsLevel; + if (lMaxLevel < -600) + { + lMaxLevel += 600; + pRvb->ReverbLevel -= lMaxLevel; + pRvb->ReflectionsLevel -= lMaxLevel; + } + + // Pre-Diffusion factor (for both reflections and late reverb) + lDensity = 8192 + ftol(79.31f * pReverb->flDensity); + pRvb->PreDiffusion = lDensity; + + // Late reverb diffusion + lTailDiffusion = ftol((0.15f + pReverb->flDiffusion * (0.36f*0.01f)) * 32767.0f); + if (lTailDiffusion > 0x7f00) lTailDiffusion = 0x7f00; + pRvb->TankDiffusion = lTailDiffusion; + + // Verify reflections and reverb delay parameters + float flRefDelay = pReverb->flReflectionsDelay; + if (flRefDelay > 0.100f) flRefDelay = 0.100f; + int32 lReverbDelay = ftol(pReverb->flReverbDelay * flOutputFreq); + int32 lReflectionsDelay = ftol(flRefDelay * flOutputFreq); + int32 lReverbDecayTime = ftol(pReverb->flDecayTime * flOutputFreq); + if (lReflectionsDelay < lMinRefDelay) + { + lReverbDelay -= (lMinRefDelay - lReflectionsDelay); + lReflectionsDelay = lMinRefDelay; + } + if (lReflectionsDelay > lMaxRefDelay) + { + lReverbDelay += (lReflectionsDelay - lMaxRefDelay); + lReflectionsDelay = lMaxRefDelay; + } + // Adjust decay time when adjusting reverb delay + if (lReverbDelay < lMinRvbDelay) + { + lReverbDecayTime -= (lMinRvbDelay - lReverbDelay); + lReverbDelay = lMinRvbDelay; + } + if (lReverbDelay > lMaxRvbDelay) + { + lReverbDecayTime += (lReverbDelay - lMaxRvbDelay); + lReverbDelay = lMaxRvbDelay; + } + pRvb->ReverbDelay = lReverbDelay; + pRvb->ReverbDecaySamples = lReverbDecayTime; + // Setup individual reflections delay and gains + for (uint32 iRef=0; iRef<ENVIRONMENT_NUMREFLECTIONS; iRef++) + { + EnvironmentReflection &ref = pRvb->Reflections[iRef]; + ref.Delay = lReflectionsDelay + (gReflectionsPreset[iRef].lDelayFactor * lReverbDelay + 50)/100; + ref.GainLL = gReflectionsPreset[iRef].sGainLL; + ref.GainRL = gReflectionsPreset[iRef].sGainRL; + ref.GainLR = gReflectionsPreset[iRef].sGainLR; + ref.GainRR = gReflectionsPreset[iRef].sGainRR; + } + + // Late reverb decay time + if (lTankLength < 10) lTankLength = 10; + flDelayFactor = (lReverbDecayTime <= lTankLength) ? 1.0f : ((float)lTankLength / (float)lReverbDecayTime); + pRvb->ReverbDecay = ftol(std::pow(0.001f, flDelayFactor) * 32768.0f); + + // Late Reverb Decay HF + flDecayTimeHF = (float)lReverbDecayTime * pReverb->flDecayHFRatio; + flDelayFactorHF = (flDecayTimeHF <= (float)lTankLength) ? 1.0f : ((float)lTankLength / flDecayTimeHF); + pRvb->flReverbDamping = std::pow(0.001f, flDelayFactorHF); +} + + +void CReverb::Shutdown(MixSampleInt &gnRvbROfsVol, MixSampleInt &gnRvbLOfsVol) +{ + gnReverbSend = false; + + gnRvbLOfsVol = 0; + gnRvbROfsVol = 0; + + // Clear out all reverb state + g_bLastInPresent = false; + g_bLastOutPresent = false; + g_nLastRvbIn_xl = g_nLastRvbIn_xr = 0; + g_nLastRvbIn_yl = g_nLastRvbIn_yr = 0; + g_nLastRvbOut_xl = g_nLastRvbOut_xr = 0; + MemsetZero(gnDCRRvb_X1); + MemsetZero(gnDCRRvb_Y1); + + // Zero internal buffers + MemsetZero(g_LateReverb.Diffusion1); + MemsetZero(g_LateReverb.Diffusion2); + MemsetZero(g_LateReverb.Delay1); + MemsetZero(g_LateReverb.Delay2); + MemsetZero(g_RefDelay.RefDelayBuffer); + MemsetZero(g_RefDelay.PreDifBuffer); + MemsetZero(g_RefDelay.RefOut); +} + + +void CReverb::Initialize(bool bReset, MixSampleInt &gnRvbROfsVol, MixSampleInt &gnRvbLOfsVol, uint32 MixingFreq) +{ + if (m_Settings.m_nReverbType >= NUM_REVERBTYPES) m_Settings.m_nReverbType = 0; + const SNDMIX_REVERB_PROPERTIES *rvbPreset = &ReverbPresets[m_Settings.m_nReverbType].first; + + if ((rvbPreset != m_currentPreset) || (bReset)) + { + // Reverb output frequency is half of the dry output rate + float flOutputFrequency = (float)MixingFreq; + EnvironmentReverb rvb; + + // Reset reverb parameters + m_currentPreset = rvbPreset; + I3dl2_to_Generic(rvbPreset, &rvb, flOutputFrequency, + RVBMINREFDELAY, RVBMAXREFDELAY, + RVBMINRVBDELAY, RVBMAXRVBDELAY, + ( RVBDIF1L_LEN + RVBDIF1R_LEN + + RVBDIF2L_LEN + RVBDIF2R_LEN + + RVBDLY1L_LEN + RVBDLY1R_LEN + + RVBDLY2L_LEN + RVBDLY2R_LEN) / 2); + + // Store reverb decay time (in samples) for reverb auto-shutdown + gnReverbDecaySamples = rvb.ReverbDecaySamples; + + // Room attenuation at high frequencies + int32 nRoomLP; + nRoomLP = OnePoleLowPassCoef(32768, mBToLinear(rvb.RoomHF), 5000, flOutputFrequency); + g_RefDelay.nCoeffs.c.l = (int16)nRoomLP; + g_RefDelay.nCoeffs.c.r = (int16)nRoomLP; + + // Pre-Diffusion factor (for both reflections and late reverb) + g_RefDelay.nPreDifCoeffs.c.l = (int16)(rvb.PreDiffusion*2); + g_RefDelay.nPreDifCoeffs.c.r = (int16)(rvb.PreDiffusion*2); + + // Setup individual reflections delay and gains + for (uint32 iRef=0; iRef<8; iRef++) + { + SWRvbReflection &ref = g_RefDelay.Reflections[iRef]; + ref.DelayDest = rvb.Reflections[iRef].Delay; + ref.Delay = ref.DelayDest; + ref.Gains[0].c.l = rvb.Reflections[iRef].GainLL; + ref.Gains[0].c.r = rvb.Reflections[iRef].GainRL; + ref.Gains[1].c.l = rvb.Reflections[iRef].GainLR; + ref.Gains[1].c.r = rvb.Reflections[iRef].GainRR; + } + g_LateReverb.nReverbDelay = rvb.ReverbDelay; + + // Reflections Master Gain + uint32 lReflectionsGain = 0; + if (rvb.ReflectionsLevel > -9000) + { + lReflectionsGain = mBToLinear(32768, rvb.ReflectionsLevel); + } + g_RefDelay.lMasterGain = lReflectionsGain; + + // Late reverb master gain + uint32 lReverbGain = 0; + if (rvb.ReverbLevel > -9000) + { + lReverbGain = mBToLinear(32768, rvb.ReverbLevel); + } + g_LateReverb.lMasterGain = lReverbGain; + + // Late reverb diffusion + uint32 nTailDiffusion = rvb.TankDiffusion; + if (nTailDiffusion > 0x7f00) nTailDiffusion = 0x7f00; + g_LateReverb.nDifCoeffs[0].c.l = (int16)nTailDiffusion; + g_LateReverb.nDifCoeffs[0].c.r = (int16)nTailDiffusion; + g_LateReverb.nDifCoeffs[1].c.l = (int16)nTailDiffusion; + g_LateReverb.nDifCoeffs[1].c.r = (int16)nTailDiffusion; + g_LateReverb.Dif2InGains[0].c.l = 0x7000; + g_LateReverb.Dif2InGains[0].c.r = 0x1000; + g_LateReverb.Dif2InGains[1].c.l = 0x1000; + g_LateReverb.Dif2InGains[1].c.r = 0x7000; + + // Late reverb decay time + int32 nReverbDecay = rvb.ReverbDecay; + Limit(nReverbDecay, 0, 0x7ff0); + g_LateReverb.nDecayDC[0].c.l = (int16)nReverbDecay; + g_LateReverb.nDecayDC[0].c.r = 0; + g_LateReverb.nDecayDC[1].c.l = 0; + g_LateReverb.nDecayDC[1].c.r = (int16)nReverbDecay; + + // Late Reverb Decay HF + float fReverbDamping = rvb.flReverbDamping * rvb.flReverbDamping; + int32 nDampingLowPass; + + nDampingLowPass = OnePoleLowPassCoef(32768, fReverbDamping, 5000, flOutputFrequency); + Limit(nDampingLowPass, 0x100, 0x7f00); + + g_LateReverb.nDecayLP[0].c.l = (int16)nDampingLowPass; + g_LateReverb.nDecayLP[0].c.r = 0; + g_LateReverb.nDecayLP[1].c.l = 0; + g_LateReverb.nDecayLP[1].c.r = (int16)nDampingLowPass; + } + if (bReset) + { + gnReverbSamples = 0; + Shutdown(gnRvbROfsVol, gnRvbLOfsVol); + } + // Wait at least 5 seconds before shutting down the reverb + if (gnReverbDecaySamples < MixingFreq*5) + { + gnReverbDecaySamples = MixingFreq*5; + } +} + + +void CReverb::TouchReverbSendBuffer(MixSampleInt *MixReverbBuffer, MixSampleInt &gnRvbROfsVol, MixSampleInt &gnRvbLOfsVol, uint32 nSamples) +{ + if(!gnReverbSend) + { // and we did not clear the buffer yet, do it now because we will get new data + StereoFill(MixReverbBuffer, nSamples, gnRvbROfsVol, gnRvbLOfsVol); + } + gnReverbSend = true; // we will have to process reverb +} + + +// Reverb +void CReverb::Process(MixSampleInt *MixSoundBuffer, MixSampleInt *MixReverbBuffer, MixSampleInt &gnRvbROfsVol, MixSampleInt &gnRvbLOfsVol, uint32 nSamples) +{ + if((!gnReverbSend) && (!gnReverbSamples)) + { // no data is sent to reverb and reverb decayed completely + return; + } + if(!gnReverbSend) + { // no input data in MixReverbBuffer, so the buffer got not cleared in TouchReverbSendBuffer(), do it now for decay + StereoFill(MixReverbBuffer, nSamples, gnRvbROfsVol, gnRvbLOfsVol); + } + + uint32 nIn, nOut; + // Dynamically adjust reverb master gains + int32 lMasterGain; + lMasterGain = ((g_RefDelay.lMasterGain * m_Settings.m_nReverbDepth) >> 4); + if (lMasterGain > 0x7fff) lMasterGain = 0x7fff; + g_RefDelay.ReflectionsGain.c.l = (int16)lMasterGain; + g_RefDelay.ReflectionsGain.c.r = (int16)lMasterGain; + lMasterGain = ((g_LateReverb.lMasterGain * m_Settings.m_nReverbDepth) >> 4); + if (lMasterGain > 0x10000) lMasterGain = 0x10000; + g_LateReverb.RvbOutGains[0].c.l = (int16)((lMasterGain+0x7f) >> 3); // l->l + g_LateReverb.RvbOutGains[0].c.r = (int16)((lMasterGain+0xff) >> 4); // r->l + g_LateReverb.RvbOutGains[1].c.l = (int16)((lMasterGain+0xff) >> 4); // l->r + g_LateReverb.RvbOutGains[1].c.r = (int16)((lMasterGain+0x7f) >> 3); // r->r + // Process Dry/Wet Mix + int32 lMaxRvbGain = (g_RefDelay.lMasterGain > g_LateReverb.lMasterGain) ? g_RefDelay.lMasterGain : g_LateReverb.lMasterGain; + if (lMaxRvbGain > 32768) lMaxRvbGain = 32768; + int32 lDryVol = (36 - m_Settings.m_nReverbDepth)>>1; + if (lDryVol < 8) lDryVol = 8; + if (lDryVol > 16) lDryVol = 16; + lDryVol = 16 - (((16-lDryVol) * lMaxRvbGain) >> 15); + ReverbDryMix(MixSoundBuffer, MixReverbBuffer, lDryVol, nSamples); + // Downsample 2x + 1st stage of lowpass filter + nIn = ReverbProcessPreFiltering1x(MixReverbBuffer, nSamples); + nOut = nIn; + // Main reverb processing: split into small chunks (needed for short reverb delays) + // Reverb Input + Low-Pass stage #2 + Pre-diffusion + if (nIn > 0) ProcessPreDelay(&g_RefDelay, MixReverbBuffer, nIn); + // Process Reverb Reflections and Late Reverberation + int32 *pRvbOut = MixReverbBuffer; + uint32 nRvbSamples = nOut; + while (nRvbSamples > 0) + { + uint32 nPosRef = g_RefDelay.nRefOutPos & SNDMIX_REVERB_DELAY_MASK; + uint32 nPosRvb = (nPosRef - g_LateReverb.nReverbDelay) & SNDMIX_REVERB_DELAY_MASK; + uint32 nmax1 = (SNDMIX_REVERB_DELAY_MASK+1) - nPosRef; + uint32 nmax2 = (SNDMIX_REVERB_DELAY_MASK+1) - nPosRvb; + nmax1 = (nmax1 < nmax2) ? nmax1 : nmax2; + uint32 n = nRvbSamples; + if (n > nmax1) n = nmax1; + if (n > 64) n = 64; + // Reflections output + late reverb delay + ProcessReflections(&g_RefDelay, &g_RefDelay.RefOut[nPosRef], pRvbOut, n); + // Late Reverberation + ProcessLateReverb(&g_LateReverb, &g_RefDelay.RefOut[nPosRvb], pRvbOut, n); + // Update delay positions + g_RefDelay.nRefOutPos = (g_RefDelay.nRefOutPos + n) & SNDMIX_REVERB_DELAY_MASK; + g_RefDelay.nDelayPos = (g_RefDelay.nDelayPos + n) & SNDMIX_REFLECTIONS_DELAY_MASK; + pRvbOut += n*2; + nRvbSamples -= n; + } + // Adjust nDelayPos, in case nIn != nOut + g_RefDelay.nDelayPos = (g_RefDelay.nDelayPos - nOut + nIn) & SNDMIX_REFLECTIONS_DELAY_MASK; + // Upsample 2x + ReverbProcessPostFiltering1x(MixReverbBuffer, MixSoundBuffer, nSamples); + // Automatically shut down if needed + if(gnReverbSend) gnReverbSamples = gnReverbDecaySamples; // reset decay counter + else if(gnReverbSamples > nSamples) gnReverbSamples -= nSamples; // decay + else // decayed + { + Shutdown(gnRvbROfsVol, gnRvbLOfsVol); + gnReverbSamples = 0; + } + gnReverbSend = false; // no input data in MixReverbBuffer +} + + +void CReverb::ReverbDryMix(int32 * MPT_RESTRICT pDry, int32 * MPT_RESTRICT pWet, int lDryVol, uint32 nSamples) +{ + for (uint32 i=0; i<nSamples; i++) + { + pDry[i*2] += (pWet[i*2]>>4) * lDryVol; + pDry[i*2+1] += (pWet[i*2+1]>>4) * lDryVol; + } +} + + +uint32 CReverb::ReverbProcessPreFiltering2x(int32 * MPT_RESTRICT pWet, uint32 nSamples) +{ + uint32 nOutSamples = 0; + int lowpass = g_RefDelay.nCoeffs.c.l; + int y1_l = g_nLastRvbIn_yl, y1_r = g_nLastRvbIn_yr; + uint32 n = nSamples; + + if (g_bLastInPresent) + { + int x1_l = g_nLastRvbIn_xl, x1_r = g_nLastRvbIn_xr; + int x2_l = pWet[0], x2_r = pWet[1]; + x1_l = (x1_l+x2_l)>>13; + x1_r = (x1_r+x2_r)>>13; + y1_l = x1_l + (((x1_l - y1_l)*lowpass)>>15); + y1_r = x1_r + (((x1_r - y1_r)*lowpass)>>15); + pWet[0] = y1_l; + pWet[1] = y1_r; + pWet+=2; + n--; + nOutSamples = 1; + g_bLastInPresent = false; + } + if (n & 1) + { + n--; + g_nLastRvbIn_xl = pWet[n*2]; + g_nLastRvbIn_xr = pWet[n*2+1]; + g_bLastInPresent = true; + } + n >>= 1; + for (uint32 i=0; i<n; i++) + { + int x1_l = pWet[i*4]; + int x2_l = pWet[i*4+2]; + x1_l = (x1_l+x2_l)>>13; + int x1_r = pWet[i*4+1]; + int x2_r = pWet[i*4+3]; + x1_r = (x1_r+x2_r)>>13; + y1_l = x1_l + (((x1_l - y1_l)*lowpass)>>15); + y1_r = x1_r + (((x1_r - y1_r)*lowpass)>>15); + pWet[i*2] = y1_l; + pWet[i*2+1] = y1_r; + } + g_nLastRvbIn_yl = y1_l; + g_nLastRvbIn_yr = y1_r; + return nOutSamples + n; +} + + +uint32 CReverb::ReverbProcessPreFiltering1x(int32 * MPT_RESTRICT pWet, uint32 nSamples) +{ + int lowpass = g_RefDelay.nCoeffs.c.l; + int y1_l = g_nLastRvbIn_yl, y1_r = g_nLastRvbIn_yr; + + for (uint32 i=0; i<nSamples; i++) + { + int x_l = pWet[i*2] >> 12; + int x_r = pWet[i*2+1] >> 12; + y1_l = x_l + (((x_l - y1_l)*lowpass)>>15); + y1_r = x_r + (((x_r - y1_r)*lowpass)>>15); + pWet[i*2] = y1_l; + pWet[i*2+1] = y1_r; + } + g_nLastRvbIn_yl = y1_l; + g_nLastRvbIn_yr = y1_r; + return nSamples; +} + + +void CReverb::ReverbProcessPostFiltering2x(const int32 * MPT_RESTRICT pRvb, int32 * MPT_RESTRICT pDry, uint32 nSamples) +{ + uint32 n0 = nSamples, n; + int x1_l = g_nLastRvbOut_xl, x1_r = g_nLastRvbOut_xr; + + if (g_bLastOutPresent) + { + pDry[0] += x1_l; + pDry[1] += x1_r; + pDry += 2; + n0--; + g_bLastOutPresent = false; + } + n = n0 >> 1; + for (uint32 i=0; i<n; i++) + { + int x_l = pRvb[i*2], x_r = pRvb[i*2+1]; + pDry[i*4] += (x_l + x1_l)>>1; + pDry[i*4+1] += (x_r + x1_r)>>1; + pDry[i*4+2] += x_l; + pDry[i*4+3] += x_r; + x1_l = x_l; + x1_r = x_r; + } + if (n0 & 1) + { + int x_l = pRvb[n*2], x_r = pRvb[n*2+1]; + pDry[n*4] += (x_l + x1_l)>>1; + pDry[n*4+1] += (x_r + x1_r)>>1; + x1_l = x_l; + x1_r = x_r; + g_bLastOutPresent = true; + } + g_nLastRvbOut_xl = x1_l; + g_nLastRvbOut_xr = x1_r; +} + + +#define DCR_AMOUNT 9 + +// Stereo Add + DC removal +void CReverb::ReverbProcessPostFiltering1x(const int32 * MPT_RESTRICT pRvb, int32 * MPT_RESTRICT pDry, uint32 nSamples) +{ +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) + if(CPU::HasFeatureSet(CPU::feature::sse2)) + { + __m128i nDCRRvb_Y1 = Load64SSE(gnDCRRvb_Y1); + __m128i nDCRRvb_X1 = Load64SSE(gnDCRRvb_X1); + __m128i in = _mm_set1_epi32(0); + while(nSamples--) + { + in = Load64SSE(pRvb); + pRvb += 2; + // x(n-1) - x(n) + __m128i diff = _mm_sub_epi32(nDCRRvb_X1, in); + nDCRRvb_X1 = _mm_add_epi32(nDCRRvb_Y1, _mm_sub_epi32(_mm_srai_epi32(diff, DCR_AMOUNT + 1), diff)); + __m128i out = _mm_add_epi32(Load64SSE(pDry), nDCRRvb_X1); + nDCRRvb_Y1 = _mm_sub_epi32(nDCRRvb_X1, _mm_srai_epi32(nDCRRvb_X1, DCR_AMOUNT)); + nDCRRvb_X1 = in; + Store64SSE(pDry, out); + pDry += 2; + } + Store64SSE(gnDCRRvb_X1, in); + Store64SSE(gnDCRRvb_Y1, nDCRRvb_Y1); + return; + } +#endif + int32 X1L = gnDCRRvb_X1[0], X1R = gnDCRRvb_X1[1]; + int32 Y1L = gnDCRRvb_Y1[0], Y1R = gnDCRRvb_Y1[1]; + int32 inL = 0, inR = 0; + while(nSamples--) + { + inL = pRvb[0]; + inR = pRvb[1]; + pRvb += 2; + int32 outL = pDry[0], outR = pDry[1]; + + // x(n-1) - x(n) + X1L -= inL; + X1R -= inR; + X1L = X1L / (1 << (DCR_AMOUNT + 1)) - X1L; + X1R = X1R / (1 << (DCR_AMOUNT + 1)) - X1R; + Y1L += X1L; + Y1R += X1R; + // add to dry mix + outL += Y1L; + outR += Y1R; + Y1L -= Y1L / (1 << DCR_AMOUNT); + Y1R -= Y1R / (1 << DCR_AMOUNT); + X1L = inL; + X1R = inR; + + pDry[0] = outL; + pDry[1] = outR; + pDry += 2; + } + gnDCRRvb_Y1[0] = Y1L; + gnDCRRvb_Y1[1] = Y1R; + gnDCRRvb_X1[0] = inL; + gnDCRRvb_X1[1] = inR; +} + + +void CReverb::ReverbDCRemoval(int32 * MPT_RESTRICT pBuffer, uint32 nSamples) +{ +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) + if(CPU::HasFeatureSet(CPU::feature::sse2)) + { + __m128i nDCRRvb_Y1 = Load64SSE(gnDCRRvb_Y1); + __m128i nDCRRvb_X1 = Load64SSE(gnDCRRvb_X1); + while(nSamples--) + { + __m128i in = Load64SSE(pBuffer); + __m128i diff = _mm_sub_epi32(nDCRRvb_X1, in); + __m128i out = _mm_add_epi32(nDCRRvb_Y1, _mm_sub_epi32(_mm_srai_epi32(diff, DCR_AMOUNT + 1), diff)); + Store64SSE(pBuffer, out); + pBuffer += 2; + nDCRRvb_Y1 = _mm_sub_epi32(out, _mm_srai_epi32(out, DCR_AMOUNT)); + nDCRRvb_X1 = in; + } + Store64SSE(gnDCRRvb_X1, nDCRRvb_X1); + Store64SSE(gnDCRRvb_Y1, nDCRRvb_Y1); + return; + } +#endif + int32 X1L = gnDCRRvb_X1[0], X1R = gnDCRRvb_X1[1]; + int32 Y1L = gnDCRRvb_Y1[0], Y1R = gnDCRRvb_Y1[1]; + int32 inL = 0, inR = 0; + while(nSamples--) + { + inL = pBuffer[0]; + inR = pBuffer[1]; + // x(n-1) - x(n) + X1L -= inL; + X1R -= inR; + X1L = X1L / (1 << (DCR_AMOUNT + 1)) - X1L; + X1R = X1R / (1 << (DCR_AMOUNT + 1)) - X1R; + Y1L += X1L; + Y1R += X1R; + pBuffer[0] = Y1L; + pBuffer[1] = Y1R; + pBuffer += 2; + Y1L -= Y1L / (1 << DCR_AMOUNT); + Y1R -= Y1R / (1 << DCR_AMOUNT); + X1L = inL; + X1R = inR; + } + gnDCRRvb_Y1[0] = Y1L; + gnDCRRvb_Y1[1] = Y1R; + gnDCRRvb_X1[0] = inL; + gnDCRRvb_X1[1] = inR; +} + + +////////////////////////////////////////////////////////////////////////// +// +// Pre-Delay: +// +// 1. Saturate and low-pass the reverb input (stage 2 of roomHF) +// 2. Process pre-diffusion +// 3. Insert the result in the reflections delay buffer +// + +// Save some typing +static MPT_FORCEINLINE int32 Clamp16(int32 x) { return Clamp(x, std::numeric_limits<int16>::min(), std::numeric_limits<int16>::max()); } + +void CReverb::ProcessPreDelay(SWRvbRefDelay * MPT_RESTRICT pPreDelay, const int32 * MPT_RESTRICT pIn, uint32 nSamples) +{ + uint32 preDifPos = pPreDelay->nPreDifPos; + uint32 delayPos = pPreDelay->nDelayPos - 1; +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) + if(CPU::HasFeatureSet(CPU::feature::sse2)) + { + __m128i coeffs = _mm_cvtsi32_si128(pPreDelay->nCoeffs.lr); + __m128i history = _mm_cvtsi32_si128(pPreDelay->History.lr); + __m128i preDifCoeffs = _mm_cvtsi32_si128(pPreDelay->nPreDifCoeffs.lr); + while(nSamples--) + { + __m128i in32 = Load64SSE(pIn); // 16-bit unsaturated reverb input [ r | l ] + __m128i inSat = _mm_packs_epi32(in32, in32); // [ r | l | r | l ] (16-bit saturated) + pIn += 2; + // Low-pass + __m128i lp = _mm_mulhi_epi16(_mm_subs_epi16(history, inSat), coeffs); + __m128i preDif = _mm_cvtsi32_si128(pPreDelay->PreDifBuffer[preDifPos].lr); + history = _mm_adds_epi16(_mm_adds_epi16(lp, lp), inSat); + // Pre-Diffusion + preDifPos = (preDifPos + 1) & SNDMIX_PREDIFFUSION_DELAY_MASK; + delayPos = (delayPos + 1) & SNDMIX_REFLECTIONS_DELAY_MASK; + __m128i preDif2 = _mm_subs_epi16(history, _mm_mulhi_epi16(preDif, preDifCoeffs)); + pPreDelay->PreDifBuffer[preDifPos].lr = _mm_cvtsi128_si32(preDif2); + pPreDelay->RefDelayBuffer[delayPos].lr = _mm_cvtsi128_si32(_mm_adds_epi16(_mm_mulhi_epi16(preDifCoeffs, preDif2), preDif)); + } + pPreDelay->nPreDifPos = preDifPos; + pPreDelay->History.lr = _mm_cvtsi128_si32(history); + return; + } +#endif + const int32 coeffsL = pPreDelay->nCoeffs.c.l, coeffsR = pPreDelay->nCoeffs.c.r; + const int32 preDifCoeffsL = pPreDelay->nPreDifCoeffs.c.l, preDifCoeffsR = pPreDelay->nPreDifCoeffs.c.r; + int16 historyL = pPreDelay->History.c.l, historyR = pPreDelay->History.c.r; + while(nSamples--) + { + int32 inL = Clamp16(pIn[0]); + int32 inR = Clamp16(pIn[1]); + pIn += 2; + // Low-pass + int32 lpL = (Clamp16(historyL - inL) * coeffsL) / 65536; + int32 lpR = (Clamp16(historyR - inR) * coeffsR) / 65536; + historyL = mpt::saturate_cast<int16>(Clamp16(lpL + lpL) + inL); + historyR = mpt::saturate_cast<int16>(Clamp16(lpR + lpR) + inR); + // Pre-Diffusion + int32 preDifL = pPreDelay->PreDifBuffer[preDifPos].c.l; + int32 preDifR = pPreDelay->PreDifBuffer[preDifPos].c.r; + preDifPos = (preDifPos + 1) & SNDMIX_PREDIFFUSION_DELAY_MASK; + delayPos = (delayPos + 1) & SNDMIX_REFLECTIONS_DELAY_MASK; + int16 preDif2L = mpt::saturate_cast<int16>(historyL - preDifL * preDifCoeffsL / 65536); + int16 preDif2R = mpt::saturate_cast<int16>(historyR - preDifR * preDifCoeffsR / 65536); + pPreDelay->PreDifBuffer[preDifPos].c.l = preDif2L; + pPreDelay->PreDifBuffer[preDifPos].c.r = preDif2R; + pPreDelay->RefDelayBuffer[delayPos].c.l = mpt::saturate_cast<int16>(preDifCoeffsL * preDif2L / 65536 + preDifL); + pPreDelay->RefDelayBuffer[delayPos].c.r = mpt::saturate_cast<int16>(preDifCoeffsR * preDif2R / 65536 + preDifR); + } + pPreDelay->nPreDifPos = preDifPos; + pPreDelay->History.c.l = historyL; + pPreDelay->History.c.r = historyR; +} + + +//////////////////////////////////////////////////////////////////// +// +// ProcessReflections: +// First stage: +// - process 4 reflections, output to pRefOut +// - output results to pRefOut +// Second stage: +// - process another 3 reflections +// - sum with pRefOut +// - apply reflections master gain and accumulate in the given output +// + +void CReverb::ProcessReflections(SWRvbRefDelay * MPT_RESTRICT pPreDelay, LR16 * MPT_RESTRICT pRefOut, int32 * MPT_RESTRICT pOut, uint32 nSamples) +{ +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) + if(CPU::HasFeatureSet(CPU::feature::sse2)) + { + union + { + __m128i xmm; + int16 i[8]; + } pos; + const LR16 *refDelayBuffer = pPreDelay->RefDelayBuffer; +#define GETDELAY(x) static_cast<int16>(pPreDelay->Reflections[x].Delay) + __m128i delayPos = _mm_set_epi16(GETDELAY(7), GETDELAY(6), GETDELAY(5), GETDELAY(4), GETDELAY(3), GETDELAY(2), GETDELAY(1), GETDELAY(0)); +#undef GETDELAY + delayPos = _mm_sub_epi16(_mm_set1_epi16(static_cast<int16>(pPreDelay->nDelayPos - 1)), delayPos); + __m128i gain12 = _mm_unpacklo_epi64(Load64SSE(pPreDelay->Reflections[0].Gains), Load64SSE(pPreDelay->Reflections[1].Gains)); + __m128i gain34 = _mm_unpacklo_epi64(Load64SSE(pPreDelay->Reflections[2].Gains), Load64SSE(pPreDelay->Reflections[3].Gains)); + __m128i gain56 = _mm_unpacklo_epi64(Load64SSE(pPreDelay->Reflections[4].Gains), Load64SSE(pPreDelay->Reflections[5].Gains)); + __m128i gain78 = _mm_unpacklo_epi64(Load64SSE(pPreDelay->Reflections[6].Gains), Load64SSE(pPreDelay->Reflections[7].Gains)); + // For 28-bit final output: 16+15-3 = 28 + __m128i refGain = _mm_srai_epi32(_mm_set_epi32(0, 0, pPreDelay->ReflectionsGain.c.r, pPreDelay->ReflectionsGain.c.l), 3); + __m128i delayInc = _mm_set1_epi16(1), delayMask = _mm_set1_epi16(SNDMIX_REFLECTIONS_DELAY_MASK); + while(nSamples--) + { + delayPos = _mm_and_si128(_mm_add_epi16(delayInc, delayPos), delayMask); + _mm_storeu_si128(&pos.xmm, delayPos); + __m128i ref12 = _mm_set_epi32(refDelayBuffer[pos.i[1]].lr, refDelayBuffer[pos.i[1]].lr, refDelayBuffer[pos.i[0]].lr, refDelayBuffer[pos.i[0]].lr); + __m128i ref34 = _mm_set_epi32(refDelayBuffer[pos.i[3]].lr, refDelayBuffer[pos.i[3]].lr, refDelayBuffer[pos.i[2]].lr, refDelayBuffer[pos.i[2]].lr); + __m128i ref56 = _mm_set_epi32(refDelayBuffer[pos.i[5]].lr, refDelayBuffer[pos.i[5]].lr, refDelayBuffer[pos.i[4]].lr, refDelayBuffer[pos.i[4]].lr); + __m128i ref78 = _mm_set_epi32(0, 0, refDelayBuffer[pos.i[6]].lr, refDelayBuffer[pos.i[6]].lr); + // First stage + __m128i refOut1 = _mm_add_epi32(_mm_madd_epi16(ref12, gain12), _mm_madd_epi16(ref34, gain34)); + refOut1 = _mm_srai_epi32(_mm_add_epi32(refOut1, _mm_shuffle_epi32(refOut1, _MM_SHUFFLE(1, 0, 3, 2))), 15); + + // Second stage + __m128i refOut2 = _mm_add_epi32(_mm_madd_epi16(ref56, gain56), _mm_madd_epi16(ref78, gain78)); + refOut2 = _mm_srai_epi32(_mm_add_epi32(refOut2, _mm_shuffle_epi32(refOut2, _MM_SHUFFLE(1, 0, 3, 2))), 15); + + // Saturate to 16-bit and sum stages + __m128i refOut = _mm_adds_epi16(_mm_packs_epi32(refOut1, refOut1), _mm_packs_epi32(refOut2, refOut2)); + pRefOut->lr = _mm_cvtsi128_si32(refOut); + pRefOut++; + + __m128i out = _mm_madd_epi16(_mm_unpacklo_epi16(refOut, refOut), refGain); // Apply reflections gain + // At this, point, this is the only output of the reverb + Store64SSE(pOut, out); + pOut += 2; + } + return; + } +#endif + int pos[7]; + for(int i = 0; i < 7; i++) + pos[i] = pPreDelay->nDelayPos - pPreDelay->Reflections[i].Delay - 1; + // For 28-bit final output: 16+15-3 = 28 + int16 refGain = pPreDelay->ReflectionsGain.c.l / (1 << 3); + while(nSamples--) + { + // First stage + int32 refOutL = 0, refOutR = 0; + for(int i = 0; i < 4; i++) + { + pos[i] = (pos[i] + 1) & SNDMIX_REFLECTIONS_DELAY_MASK; + int16 refL = pPreDelay->RefDelayBuffer[pos[i]].c.l, refR = pPreDelay->RefDelayBuffer[pos[i]].c.r; + refOutL += refL * pPreDelay->Reflections[i].Gains[0].c.l + refR * pPreDelay->Reflections[i].Gains[0].c.r; + refOutR += refL * pPreDelay->Reflections[i].Gains[1].c.l + refR * pPreDelay->Reflections[i].Gains[1].c.r; + } + int16 stage1l = mpt::saturate_cast<int16>(refOutL / (1 << 15)); + int16 stage1r = mpt::saturate_cast<int16>(refOutR / (1 << 15)); + // Second stage + refOutL = 0; + refOutR = 0; + for(int i = 4; i < 7; i++) + { + pos[i] = (pos[i] + 1) & SNDMIX_REFLECTIONS_DELAY_MASK; + int16 refL = pPreDelay->RefDelayBuffer[pos[i]].c.l, refR = pPreDelay->RefDelayBuffer[pos[i]].c.r; + refOutL += refL * pPreDelay->Reflections[i].Gains[0].c.l + refR * pPreDelay->Reflections[i].Gains[0].c.r; + refOutR += refL * pPreDelay->Reflections[i].Gains[1].c.l + refR * pPreDelay->Reflections[i].Gains[1].c.r; + } + pOut[0] = (pRefOut->c.l = mpt::saturate_cast<int16>(stage1l + refOutL / (1 << 15))) * refGain; + pOut[1] = (pRefOut->c.r = mpt::saturate_cast<int16>(stage1r + refOutR / (1 << 15))) * refGain; + pRefOut++; + pOut += 2; + } +} + + +////////////////////////////////////////////////////////////////////////// +// +// Late reverberation (with SW reflections) +// + +void CReverb::ProcessLateReverb(SWLateReverb * MPT_RESTRICT pReverb, LR16 * MPT_RESTRICT pRefOut, int32 * MPT_RESTRICT pMixOut, uint32 nSamples) +{ + // Calculate delay line offset from current delay position + #define DELAY_OFFSET(x) ((delayPos - (x)) & RVBDLY_MASK) + +#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) + if(CPU::HasFeatureSet(CPU::feature::sse2)) + { + int delayPos = pReverb->nDelayPos & RVBDLY_MASK; + __m128i rvbOutGains = Load64SSE(pReverb->RvbOutGains); + __m128i difCoeffs = Load64SSE(pReverb->nDifCoeffs); + __m128i decayLP = Load64SSE(pReverb->nDecayLP); + __m128i lpHistory = Load64SSE(pReverb->LPHistory); + while(nSamples--) + { + __m128i refIn = _mm_cvtsi32_si128(pRefOut->lr); // 16-bit stereo input + pRefOut++; + + __m128i delay2 = _mm_unpacklo_epi32( + _mm_cvtsi32_si128(pReverb->Delay2[DELAY_OFFSET(RVBDLY2L_LEN)].lr), + _mm_cvtsi32_si128(pReverb->Delay2[DELAY_OFFSET(RVBDLY2R_LEN)].lr)); + + // Unsigned to avoid sign extension + uint16 diff1L = pReverb->Diffusion1[DELAY_OFFSET(RVBDIF1L_LEN)].c.l; + uint16 diff1R = pReverb->Diffusion1[DELAY_OFFSET(RVBDIF1R_LEN)].c.r; + int32 diffusion1 = diff1L | (diff1R << 16); // diffusion1 history + + uint16 diff2L = pReverb->Diffusion2[DELAY_OFFSET(RVBDIF2L_LEN)].c.l; + uint16 diff2R = pReverb->Diffusion2[DELAY_OFFSET(RVBDIF2R_LEN)].c.r; + int32 diffusion2 = diff2L | (diff2R << 16); // diffusion2 history + + __m128i lpDecay = _mm_mulhi_epi16(_mm_subs_epi16(lpHistory, delay2), decayLP); + lpHistory = _mm_adds_epi16(_mm_adds_epi16(lpDecay, lpDecay), delay2); // Low-passed decay + + // Apply decay gain + __m128i histDecay = _mm_srai_epi32(_mm_madd_epi16(Load64SSE(pReverb->nDecayDC), lpHistory), 15); + __m128i histDecayPacked = _mm_shuffle_epi32(_mm_packs_epi32(histDecay, histDecay), _MM_SHUFFLE(2, 0, 2, 0)); + __m128i histDecayIn = _mm_adds_epi16(_mm_shuffle_epi32(_mm_packs_epi32(histDecay, histDecay), _MM_SHUFFLE(2, 0, 2, 0)), _mm_srai_epi16(_mm_unpacklo_epi32(refIn, refIn), 2)); + __m128i histDecayInDiff = _mm_subs_epi16(histDecayIn, _mm_mulhi_epi16(_mm_cvtsi32_si128(diffusion1), difCoeffs)); + pReverb->Diffusion1[delayPos].lr = _mm_cvtsi128_si32(histDecayInDiff); + + __m128i delay1Out = _mm_adds_epi16(_mm_mulhi_epi16(difCoeffs, histDecayInDiff), _mm_cvtsi32_si128(diffusion1)); + // Insert the diffusion output in the reverb delay line + pReverb->Delay1[delayPos].lr = _mm_cvtsi128_si32(delay1Out); + __m128i histDecayInDelay = _mm_adds_epi16(histDecayIn, _mm_unpacklo_epi32(delay1Out, delay1Out)); + + // Input to second diffuser + __m128i delay1 = _mm_unpacklo_epi32( + _mm_cvtsi32_si128(pReverb->Delay1[DELAY_OFFSET(RVBDLY1L_LEN)].lr), + _mm_cvtsi32_si128(pReverb->Delay1[DELAY_OFFSET(RVBDLY1R_LEN)].lr)); + + __m128i delay1Gains = _mm_srai_epi32(_mm_madd_epi16(delay1, Load64SSE(pReverb->Dif2InGains)), 15); + __m128i delay1GainsSat = _mm_shuffle_epi32(_mm_packs_epi32(delay1Gains, delay1Gains), _MM_SHUFFLE(2, 0, 2, 0)); + __m128i histDelay1 = _mm_subs_epi16(_mm_adds_epi16(histDecayInDelay, delay1), delay1GainsSat); // accumulate with reverb output + __m128i diff2out = _mm_subs_epi16(delay1GainsSat, _mm_mulhi_epi16(_mm_cvtsi32_si128(diffusion2), difCoeffs)); + __m128i diff2outCoeffs = _mm_mulhi_epi16(difCoeffs, diff2out); + pReverb->Diffusion2[delayPos].lr = _mm_cvtsi128_si32(diff2out); + + __m128i mixOut = Load64SSE(pMixOut); + __m128i delay2out = _mm_adds_epi16(diff2outCoeffs, _mm_cvtsi32_si128(diffusion2)); + pReverb->Delay2[delayPos].lr = _mm_cvtsi128_si32(delay2out); + delayPos = (delayPos + 1) & RVBDLY_MASK; + // Accumulate with reverb output + __m128i out = _mm_add_epi32(_mm_madd_epi16(_mm_adds_epi16(histDelay1, delay2out), rvbOutGains), mixOut); + Store64SSE(pMixOut, out); + pMixOut += 2; + } + Store64SSE(pReverb->LPHistory, lpHistory); + pReverb->nDelayPos = delayPos; + return; + } +#endif + int delayPos = pReverb->nDelayPos & RVBDLY_MASK; + while(nSamples--) + { + int16 refInL = pRefOut->c.l, refInR = pRefOut->c.r; + pRefOut++; + + int32 delay2LL = pReverb->Delay2[DELAY_OFFSET(RVBDLY2L_LEN)].c.l, delay2LR = pReverb->Delay2[DELAY_OFFSET(RVBDLY2L_LEN)].c.r; + int32 delay2RL = pReverb->Delay2[DELAY_OFFSET(RVBDLY2R_LEN)].c.l, delay2RR = pReverb->Delay2[DELAY_OFFSET(RVBDLY2R_LEN)].c.r; + + int32 diff1L = pReverb->Diffusion1[DELAY_OFFSET(RVBDIF1L_LEN)].c.l; + int32 diff1R = pReverb->Diffusion1[DELAY_OFFSET(RVBDIF1R_LEN)].c.r; + + int32 diff2L = pReverb->Diffusion2[DELAY_OFFSET(RVBDIF2L_LEN)].c.l; + int32 diff2R = pReverb->Diffusion2[DELAY_OFFSET(RVBDIF2R_LEN)].c.r; + + int32 lpDecayLL = Clamp16(pReverb->LPHistory[0].c.l - delay2LL) * pReverb->nDecayLP[0].c.l / 65536; + int32 lpDecayLR = Clamp16(pReverb->LPHistory[0].c.r - delay2LR) * pReverb->nDecayLP[0].c.r / 65536; + int32 lpDecayRL = Clamp16(pReverb->LPHistory[1].c.l - delay2RL) * pReverb->nDecayLP[1].c.l / 65536; + int32 lpDecayRR = Clamp16(pReverb->LPHistory[1].c.r - delay2RR) * pReverb->nDecayLP[1].c.r / 65536; + // Low-passed decay + pReverb->LPHistory[0].c.l = mpt::saturate_cast<int16>(Clamp16(lpDecayLL + lpDecayLL) + delay2LL); + pReverb->LPHistory[0].c.r = mpt::saturate_cast<int16>(Clamp16(lpDecayLR + lpDecayLR) + delay2LR); + pReverb->LPHistory[1].c.l = mpt::saturate_cast<int16>(Clamp16(lpDecayRL + lpDecayRL) + delay2RL); + pReverb->LPHistory[1].c.r = mpt::saturate_cast<int16>(Clamp16(lpDecayRR + lpDecayRR) + delay2RR); + + // Apply decay gain + int32 histDecayL = Clamp16((int32)pReverb->nDecayDC[0].c.l * pReverb->LPHistory[0].c.l / (1 << 15)); + int32 histDecayR = Clamp16((int32)pReverb->nDecayDC[1].c.r * pReverb->LPHistory[1].c.r / (1 << 15)); + int32 histDecayInL = Clamp16(histDecayL + refInL / 4); + int32 histDecayInR = Clamp16(histDecayR + refInR / 4); + int32 histDecayInDiffL = Clamp16(histDecayInL - diff1L * pReverb->nDifCoeffs[0].c.l / 65536); + int32 histDecayInDiffR = Clamp16(histDecayInR - diff1R * pReverb->nDifCoeffs[0].c.r / 65536); + pReverb->Diffusion1[delayPos].c.l = static_cast<int16>(histDecayInDiffL); + pReverb->Diffusion1[delayPos].c.r = static_cast<int16>(histDecayInDiffR); + + int32 delay1L = Clamp16(pReverb->nDifCoeffs[0].c.l * histDecayInDiffL / 65536 + diff1L); + int32 delay1R = Clamp16(pReverb->nDifCoeffs[0].c.r * histDecayInDiffR / 65536 + diff1R); + // Insert the diffusion output in the reverb delay line + pReverb->Delay1[delayPos].c.l = static_cast<int16>(delay1L); + pReverb->Delay1[delayPos].c.r = static_cast<int16>(delay1R); + int32 histDecayInDelayL = Clamp16(histDecayInL + delay1L); + int32 histDecayInDelayR = Clamp16(histDecayInR + delay1R); + + // Input to second diffuser + int32 delay1LL = pReverb->Delay1[DELAY_OFFSET(RVBDLY1L_LEN)].c.l, delay1LR = pReverb->Delay1[DELAY_OFFSET(RVBDLY1L_LEN)].c.r; + int32 delay1RL = pReverb->Delay1[DELAY_OFFSET(RVBDLY1R_LEN)].c.l, delay1RR = pReverb->Delay1[DELAY_OFFSET(RVBDLY1R_LEN)].c.r; + + int32 delay1GainsL = Clamp16((delay1LL * pReverb->Dif2InGains[0].c.l + delay1LR * pReverb->Dif2InGains[0].c.r) / (1 << 15)); + int32 delay1GainsR = Clamp16((delay1RL * pReverb->Dif2InGains[1].c.l + delay1RR * pReverb->Dif2InGains[1].c.r) / (1 << 15)); + + // accumulate with reverb output + int32 histDelay1LL = Clamp16(Clamp16(histDecayInDelayL + delay1LL) - delay1GainsL); + int32 histDelay1LR = Clamp16(Clamp16(histDecayInDelayR + delay1LR) - delay1GainsR); + int32 histDelay1RL = Clamp16(Clamp16(histDecayInDelayL + delay1RL) - delay1GainsL); + int32 histDelay1RR = Clamp16(Clamp16(histDecayInDelayR + delay1RR) - delay1GainsR); + int32 diff2outL = Clamp16(delay1GainsL - diff2L * pReverb->nDifCoeffs[0].c.l / 65536); + int32 diff2outR = Clamp16(delay1GainsR - diff2R * pReverb->nDifCoeffs[0].c.r / 65536); + int32 diff2outCoeffsL = pReverb->nDifCoeffs[0].c.l * diff2outL / 65536; + int32 diff2outCoeffsR = pReverb->nDifCoeffs[0].c.r * diff2outR / 65536; + pReverb->Diffusion2[delayPos].c.l = static_cast<int16>(diff2outL); + pReverb->Diffusion2[delayPos].c.r = static_cast<int16>(diff2outR); + + int32 delay2outL = Clamp16(diff2outCoeffsL + diff2L); + int32 delay2outR = Clamp16(diff2outCoeffsR + diff2R); + pReverb->Delay2[delayPos].c.l = static_cast<int16>(delay2outL); + pReverb->Delay2[delayPos].c.r = static_cast<int16>(delay2outR); + delayPos = (delayPos + 1) & RVBDLY_MASK; + // Accumulate with reverb output + pMixOut[0] += Clamp16(histDelay1LL + delay2outL) * pReverb->RvbOutGains[0].c.l + Clamp16(histDelay1LR + delay2outR) * pReverb->RvbOutGains[0].c.r; + pMixOut[1] += Clamp16(histDelay1RL + Clamp16(diff2outCoeffsL)) * pReverb->RvbOutGains[1].c.l + Clamp16(histDelay1RR + Clamp16(diff2outCoeffsR)) * pReverb->RvbOutGains[1].c.r; + pMixOut += 2; + } + pReverb->nDelayPos = delayPos; + + #undef DELAY_OFFSET +} + + +#else + + +MPT_MSVC_WORKAROUND_LNK4221(Reverb) + + +#endif // NO_REVERB + + +OPENMPT_NAMESPACE_END + diff --git a/Src/external_dependencies/openmpt-trunk/sounddsp/Reverb.h b/Src/external_dependencies/openmpt-trunk/sounddsp/Reverb.h new file mode 100644 index 00000000..205fe3b0 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/sounddsp/Reverb.h @@ -0,0 +1,220 @@ +/* + * Reverb.h + * -------- + * Purpose: Mixing code for reverb. + * Notes : Ugh... This should really be removed at some point. + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#ifndef NO_REVERB + +#include "../soundlib/Mixer.h" // For MIXBUFFERSIZE + +OPENMPT_NAMESPACE_BEGIN + +//////////////////////////////////////////////////////////////////////// +// Reverberation + +///////////////////////////////////////////////////////////////////////////// +// +// SW Reverb structures +// + +// Length-1 (in samples) of the reflections delay buffer: 32K, 371ms@22kHz +#define SNDMIX_REFLECTIONS_DELAY_MASK 0x1fff +#define SNDMIX_PREDIFFUSION_DELAY_MASK 0x7f // 128 samples +#define SNDMIX_REVERB_DELAY_MASK 0xfff // 4K samples (92ms @ 44kHz) + +union LR16 +{ + struct { int16 l, r; } c; + int32 lr; +}; + +struct SWRvbReflection +{ + uint32 Delay, DelayDest; + LR16 Gains[2]; // g_ll, g_rl, g_lr, g_rr +}; + +struct SWRvbRefDelay +{ + uint32 nDelayPos, nPreDifPos, nRefOutPos; + int32 lMasterGain; // reflections linear master gain + LR16 nCoeffs; // room low-pass coefficients + LR16 History; // room low-pass history + LR16 nPreDifCoeffs; // prediffusion coefficients + LR16 ReflectionsGain; // master reflections gain + SWRvbReflection Reflections[8]; // Up to 8 SW Reflections + LR16 RefDelayBuffer[SNDMIX_REFLECTIONS_DELAY_MASK + 1]; // reflections delay buffer + LR16 PreDifBuffer[SNDMIX_PREDIFFUSION_DELAY_MASK + 1]; // pre-diffusion + LR16 RefOut[SNDMIX_REVERB_DELAY_MASK + 1]; // stereo output of reflections +}; + +struct SNDMIX_REVERB_PROPERTIES; + + +// Late reverberation +// Tank diffusers lengths +#define RVBDIF1L_LEN (149*2) // 6.8ms +#define RVBDIF1R_LEN (223*2) // 10.1ms +#define RVBDIF2L_LEN (421*2) // 19.1ms +#define RVBDIF2R_LEN (647*2) // 29.3ms +// Tank delay lines lengths +#define RVBDLY1L_LEN (683*2) // 30.9ms +#define RVBDLY1R_LEN (811*2) // 36.7ms +#define RVBDLY2L_LEN (773*2) // 35.1ms +#define RVBDLY2R_LEN (1013*2) // 45.9ms +// Tank delay lines mask +#define RVBDLY_MASK 2047 + +// Min/Max reflections delay +#define RVBMINREFDELAY 96 // 96 samples +#define RVBMAXREFDELAY 7500 // 7500 samples +// Min/Max reverb delay +#define RVBMINRVBDELAY 128 // 256 samples (11.6ms @ 22kHz) +#define RVBMAXRVBDELAY 3800 // 1900 samples (86ms @ 24kHz) + +struct SWLateReverb +{ + uint32 nReverbDelay; // Reverb delay (in samples) + uint32 nDelayPos; // Delay line position + LR16 nDifCoeffs[2]; // Reverb diffusion + LR16 nDecayDC[2]; // Reverb DC decay + LR16 nDecayLP[2]; // Reverb HF decay + LR16 LPHistory[2]; // Low-pass history + LR16 Dif2InGains[2]; // 2nd diffuser input gains + LR16 RvbOutGains[2]; // 4x2 Reverb output gains + int32 lMasterGain; // late reverb master gain + int32 lDummyAlign; + // Tank Delay lines + LR16 Diffusion1[RVBDLY_MASK + 1]; // {dif1_l, dif1_r} + LR16 Diffusion2[RVBDLY_MASK + 1]; // {dif2_l, dif2_r} + LR16 Delay1[RVBDLY_MASK + 1]; // {dly1_l, dly1_r} + LR16 Delay2[RVBDLY_MASK + 1]; // {dly2_l, dly2_r} +}; + +#define ENVIRONMENT_NUMREFLECTIONS 8 + +struct EnvironmentReflection +{ + int16 GainLL, GainRR, GainLR, GainRL; // +/- 32K scale + uint32 Delay; // In samples +}; + +struct EnvironmentReverb +{ + int32 ReverbLevel; // Late reverb gain (mB) + int32 ReflectionsLevel; // Master reflections gain (mB) + int32 RoomHF; // Room gain HF (mB) + uint32 ReverbDecay; // Reverb tank decay (0-7fff scale) + int32 PreDiffusion; // Reverb pre-diffusion amount (+/- 32K scale) + int32 TankDiffusion; // Reverb tank diffusion (+/- 32K scale) + uint32 ReverbDelay; // Reverb delay (in samples) + float flReverbDamping; // HF tank gain [0.0, 1.0] + int32 ReverbDecaySamples; // Reverb decay time (in samples) + EnvironmentReflection Reflections[ENVIRONMENT_NUMREFLECTIONS]; +}; + + +class CReverbSettings +{ +public: + uint32 m_nReverbDepth = 8; // 50% + uint32 m_nReverbType = 0; +}; + + +class CReverb +{ +public: + CReverbSettings m_Settings; + +private: + const SNDMIX_REVERB_PROPERTIES *m_currentPreset = nullptr; + + bool gnReverbSend = false; + + uint32 gnReverbSamples = 0; + uint32 gnReverbDecaySamples = 0; + + // Internal reverb state + bool g_bLastInPresent = 0; + bool g_bLastOutPresent = 0; + int g_nLastRvbIn_xl = 0; + int g_nLastRvbIn_xr = 0; + int g_nLastRvbIn_yl = 0; + int g_nLastRvbIn_yr = 0; + int g_nLastRvbOut_xl = 0; + int g_nLastRvbOut_xr = 0; + int32 gnDCRRvb_Y1[2] = { 0, 0 }; + int32 gnDCRRvb_X1[2] = { 0, 0 }; + + // Reverb mix buffers + SWRvbRefDelay g_RefDelay; + SWLateReverb g_LateReverb; + +public: + CReverb(); +public: + void Initialize(bool bReset, MixSampleInt &gnRvbROfsVol, MixSampleInt &gnRvbLOfsVol, uint32 MixingFreq); + + // can be called multiple times or never (if no data is sent to reverb) + void TouchReverbSendBuffer(MixSampleInt *MixReverbBuffer, MixSampleInt &gnRvbROfsVol, MixSampleInt &gnRvbLOfsVol, uint32 nSamples); + + // call once after all data has been sent. + void Process(MixSampleInt *MixSoundBuffer, MixSampleInt *MixReverbBuffer, MixSampleInt &gnRvbROfsVol, MixSampleInt &gnRvbLOfsVol, uint32 nSamples); + +private: + void Shutdown(MixSampleInt &gnRvbROfsVol, MixSampleInt &gnRvbLOfsVol); + // Pre/Post resampling and filtering + uint32 ReverbProcessPreFiltering1x(int32 *pWet, uint32 nSamples); + uint32 ReverbProcessPreFiltering2x(int32 *pWet, uint32 nSamples); + void ReverbProcessPostFiltering1x(const int32 *pRvb, int32 *pDry, uint32 nSamples); + void ReverbProcessPostFiltering2x(const int32 *pRvb, int32 *pDry, uint32 nSamples); + void ReverbDCRemoval(int32 *pBuffer, uint32 nSamples); + void ReverbDryMix(int32 *pDry, int32 *pWet, int lDryVol, uint32 nSamples); + // Process pre-diffusion and pre-delay + static void ProcessPreDelay(SWRvbRefDelay *pPreDelay, const int32 *pIn, uint32 nSamples); + // Process reflections + static void ProcessReflections(SWRvbRefDelay *pPreDelay, LR16 *pRefOut, int32 *pMixOut, uint32 nSamples); + // Process Late Reverb (SW Reflections): stereo reflections output, 32-bit reverb output, SW reverb gain + static void ProcessLateReverb(SWLateReverb *pReverb, LR16 *pRefOut, int32 *pMixOut, uint32 nSamples); +}; + + +///////////////////////////////////////////////////////////////////////////////// +// +// I3DL2 reverb presets +// + +struct SNDMIX_REVERB_PROPERTIES +{ + int32 lRoom; // [-10000, 0] default: -10000 mB + int32 lRoomHF; // [-10000, 0] default: 0 mB + float flDecayTime; // [0.1, 20.0] default: 1.0 s + float flDecayHFRatio; // [0.1, 2.0] default: 0.5 + int32 lReflections; // [-10000, 1000] default: -10000 mB + float flReflectionsDelay; // [0.0, 0.3] default: 0.02 s + int32 lReverb; // [-10000, 2000] default: -10000 mB + float flReverbDelay; // [0.0, 0.1] default: 0.04 s + float flDiffusion; // [0.0, 100.0] default: 100.0 % + float flDensity; // [0.0, 100.0] default: 100.0 % +}; + +enum : uint32 +{ + NUM_REVERBTYPES = 29 +}; +mpt::ustring GetReverbPresetName(uint32 preset); +const SNDMIX_REVERB_PROPERTIES *GetReverbPreset(uint32 preset); + +OPENMPT_NAMESPACE_END + +#endif // NO_REVERB |