/*
 * This source code is public domain.
 *
 * Authors: Olivier Lapicque <olivierl@jps.net>
*/

#include "libmodplug.h"

static UINT GetMaskFromSize(UINT len)
//-----------------------------------
{
	UINT n = 2;
	while (n <= len) n <<= 1;
	return ((n >> 1) - 1);
}


void CSoundFile_InitializeDSP(CSoundFile *_this, BOOL bReset)
//-----------------------------------------
{
	BOOL bResetBass = FALSE;
	if (!_this->m_nReverbDelay) _this->m_nReverbDelay = 100;
	if (!_this->m_nXBassRange) _this->m_nXBassRange = XBASS_DELAY;
	if (!_this->m_nProLogicDelay) _this->m_nProLogicDelay = 20;
	if (_this->m_nXBassDepth > 8) _this->m_nXBassDepth = 8;
	if (_this->m_nXBassDepth < 2) _this->m_nXBassDepth = 2;
	if (bReset)
	{
		// Noise Reduction
		_this->nLeftNR = _this->nRightNR = 0;
	}
	// Pro-Logic Surround
	_this->nSurroundPos = _this->nSurroundSize = 0;
	_this->nDolbyLoFltPos = _this->nDolbyLoFltSum = _this->nDolbyLoDlyPos = 0;
	_this->nDolbyHiFltPos = _this->nDolbyHiFltSum = 0;
	if (_this->gdwSoundSetup & SNDMIX_SURROUND)
	{
		SDL_memset(_this->DolbyLoFilterBuffer, 0, sizeof(_this->DolbyLoFilterBuffer));
		SDL_memset(_this->DolbyHiFilterBuffer, 0, sizeof(_this->DolbyHiFilterBuffer));
		SDL_memset(_this->DolbyLoFilterDelay, 0, sizeof(_this->DolbyLoFilterDelay));
		SDL_memset(_this->SurroundBuffer, 0, sizeof(_this->SurroundBuffer));
		_this->nSurroundSize = (_this->gdwMixingFreq * _this->m_nProLogicDelay) / 1000;
		if (_this->nSurroundSize > SURROUNDBUFFERSIZE) _this->nSurroundSize = SURROUNDBUFFERSIZE;
		if (_this->m_nProLogicDepth < 8) _this->nDolbyDepth = (32 >> _this->m_nProLogicDepth) + 32;
		else _this->nDolbyDepth = (_this->m_nProLogicDepth < 16) ? (8 + (_this->m_nProLogicDepth - 8) * 7) : 64;
		_this->nDolbyDepth >>= 2;
	}
	// Reverb Setup
#ifndef MODPLUG_NO_REVERB
	if (_this->gdwSoundSetup & SNDMIX_REVERB)
	{
		UINT nrs = (_this->gdwMixingFreq * _this->m_nReverbDelay) / 1000;
		UINT nfa = _this->m_nReverbDepth+1;
		if (nrs > REVERBBUFFERSIZE) nrs = REVERBBUFFERSIZE;
		if ((bReset) || (nrs != (UINT)_this->nReverbSize) || (nfa != (UINT)_this->nFilterAttn))
		{
			_this->nFilterAttn = nfa;
			_this->nReverbSize = nrs;
			_this->nReverbBufferPos = _this->nReverbBufferPos2 = _this->nReverbBufferPos3 = _this->nReverbBufferPos4 = 0;
			_this->nReverbLoFltSum = _this->nReverbLoFltPos = _this->nReverbLoDlyPos = 0;
			_this->gRvbLPSum = _this->gRvbLPPos = 0;
			_this->nReverbSize2 = (_this->nReverbSize * 13) / 17;
			if (_this->nReverbSize2 > REVERBBUFFERSIZE2) _this->nReverbSize2 = REVERBBUFFERSIZE2;
			_this->nReverbSize3 = (_this->nReverbSize * 7) / 13;
			if (_this->nReverbSize3 > REVERBBUFFERSIZE3) _this->nReverbSize3 = REVERBBUFFERSIZE3;
			_this->nReverbSize4 = (_this->nReverbSize * 7) / 19;
			if (_this->nReverbSize4 > REVERBBUFFERSIZE4) _this->nReverbSize4 = REVERBBUFFERSIZE4;
			SDL_memset(_this->ReverbLoFilterBuffer, 0, sizeof(_this->ReverbLoFilterBuffer));
			SDL_memset(_this->ReverbLoFilterDelay, 0, sizeof(_this->ReverbLoFilterDelay));
			SDL_memset(_this->ReverbBuffer, 0, sizeof(_this->ReverbBuffer));
			SDL_memset(_this->ReverbBuffer2, 0, sizeof(_this->ReverbBuffer2));
			SDL_memset(_this->ReverbBuffer3, 0, sizeof(_this->ReverbBuffer3));
			SDL_memset(_this->ReverbBuffer4, 0, sizeof(_this->ReverbBuffer4));
			SDL_memset(_this->gRvbLowPass, 0, sizeof(_this->gRvbLowPass));
		}
	} else _this->nReverbSize = 0;
#endif
	// Bass Expansion Reset
	if (_this->gdwSoundSetup & SNDMIX_MEGABASS)
	{
		UINT nXBassSamples = (_this->gdwMixingFreq * _this->m_nXBassRange) / 10000;
		UINT mask;
		if (nXBassSamples > XBASSBUFFERSIZE) nXBassSamples = XBASSBUFFERSIZE;
		mask = GetMaskFromSize(nXBassSamples);
		if ((bReset) || (mask != (UINT)_this->nXBassMask))
		{
			_this->nXBassMask = mask;
			bResetBass = TRUE;
		}
	} else
	{
		_this->nXBassMask = 0;
		bResetBass = TRUE;
	}
	if (bResetBass)
	{
		_this->nXBassSum = _this->nXBassBufferPos = _this->nXBassDlyPos = 0;
		SDL_memset(_this->XBassBuffer, 0, sizeof(_this->XBassBuffer));
		SDL_memset(_this->XBassDelay, 0, sizeof(_this->XBassDelay));
	}
}


void CSoundFile_ProcessStereoDSP(CSoundFile *_this, int count)
//------------------------------------------
{
#ifndef MODPLUG_NO_REVERB
	// Reverb
	if (_this->gdwSoundSetup & SNDMIX_REVERB)
	{
		int *pr = _this->MixSoundBuffer, *pin = _this->MixReverbBuffer, rvbcount = count;
		do
		{
			int echo, echodly, n, tmp, v, vlp;

			echo = _this->ReverbBuffer[_this->nReverbBufferPos] + _this->ReverbBuffer2[_this->nReverbBufferPos2]
					+ _this->ReverbBuffer3[_this->nReverbBufferPos3] + _this->ReverbBuffer4[_this->nReverbBufferPos4];	// echo = reverb signal
			// Delay line and remove Low Frequencies			// v = original signal
			echodly = _this->ReverbLoFilterDelay[_this->nReverbLoDlyPos];	// echodly = delayed signal
			_this->ReverbLoFilterDelay[_this->nReverbLoDlyPos] = echo >> 1;
			_this->nReverbLoDlyPos++;
			_this->nReverbLoDlyPos &= 0x1F;
			n = _this->nReverbLoFltPos;
			_this->nReverbLoFltSum -= _this->ReverbLoFilterBuffer[n];
			tmp = echo / 128;
			_this->ReverbLoFilterBuffer[n] = tmp;
			_this->nReverbLoFltSum += tmp;
			echodly -= _this->nReverbLoFltSum;
			_this->nReverbLoFltPos = (n + 1) & 0x3F;
			// Reverb
			v = (pin[0]+pin[1]) >> _this->nFilterAttn;
			pr[0] += pin[0] + echodly;
			pr[1] += pin[1] + echodly;
			v += echodly >> 2;
			_this->ReverbBuffer3[_this->nReverbBufferPos3] = v;
			_this->ReverbBuffer4[_this->nReverbBufferPos4] = v;
			v += echodly >> 4;
			v >>= 1;
			_this->gRvbLPSum -= _this->gRvbLowPass[_this->gRvbLPPos];
			_this->gRvbLPSum += v;
			_this->gRvbLowPass[_this->gRvbLPPos] = v;
			_this->gRvbLPPos++;
			_this->gRvbLPPos &= 7;
			vlp = _this->gRvbLPSum >> 2;
			_this->ReverbBuffer[_this->nReverbBufferPos] = vlp;
			_this->ReverbBuffer2[_this->nReverbBufferPos2] = vlp;
			if (++_this->nReverbBufferPos >= _this->nReverbSize) _this->nReverbBufferPos = 0;
			if (++_this->nReverbBufferPos2 >= _this->nReverbSize2) _this->nReverbBufferPos2 = 0;
			if (++_this->nReverbBufferPos3 >= _this->nReverbSize3) _this->nReverbBufferPos3 = 0;
			if (++_this->nReverbBufferPos4 >= _this->nReverbSize4) _this->nReverbBufferPos4 = 0;
			pr += 2;
			pin += 2;
		} while (--rvbcount);
	}
#endif
	// Dolby Pro-Logic Surround
	if (_this->gdwSoundSetup & SNDMIX_SURROUND)
	{
		int *pr = _this->MixSoundBuffer, n = _this->nDolbyLoFltPos;
		int   r = count;
		for (; r; r--)
		{
			int v, secho, tmp;
			v = (pr[0]+pr[1]+DOLBYATTNROUNDUP) >> (nDolbyHiFltAttn+1);
			v *= (int)_this->nDolbyDepth;
			// Low-Pass Filter
			_this->nDolbyHiFltSum -= _this->DolbyHiFilterBuffer[_this->nDolbyHiFltPos];
			_this->DolbyHiFilterBuffer[_this->nDolbyHiFltPos] = v;
			_this->nDolbyHiFltSum += v;
			v = _this->nDolbyHiFltSum;
			_this->nDolbyHiFltPos++;
			_this->nDolbyHiFltPos &= nDolbyHiFltMask;
			// Surround
			secho = _this->SurroundBuffer[_this->nSurroundPos];
			_this->SurroundBuffer[_this->nSurroundPos] = v;
			// Delay line and remove low frequencies
			v = _this->DolbyLoFilterDelay[_this->nDolbyLoDlyPos];		// v = delayed signal
			_this->DolbyLoFilterDelay[_this->nDolbyLoDlyPos] = secho;	// secho = signal
			_this->nDolbyLoDlyPos++;
			_this->nDolbyLoDlyPos &= 0x1F;
			_this->nDolbyLoFltSum -= _this->DolbyLoFilterBuffer[n];
			tmp = secho / 64;
			_this->DolbyLoFilterBuffer[n] = tmp;
			_this->nDolbyLoFltSum += tmp;
			v -= _this->nDolbyLoFltSum;
			n++;
			n &= 0x3F;
			// Add echo
			pr[0] += v;
			pr[1] -= v;
			if (++_this->nSurroundPos >= _this->nSurroundSize) _this->nSurroundPos = 0;
			pr += 2;
		}
		_this->nDolbyLoFltPos = n;
	}
	// Bass Expansion
	if (_this->gdwSoundSetup & SNDMIX_MEGABASS)
	{
		int *px = _this->MixSoundBuffer;
		int xba = _this->m_nXBassDepth+1, xbamask = (1 << xba) - 1;
		int n = _this->nXBassBufferPos;
		int x = count;
		for (; x; x--)
		{
			int tmp0, tmp, v;
			_this->nXBassSum -= _this->XBassBuffer[n];
			tmp0 = px[0] + px[1];
			tmp = (tmp0 + ((tmp0 >> 31) & xbamask)) >> xba;
			_this->XBassBuffer[n] = tmp;
			_this->nXBassSum += tmp;
			v = _this->XBassDelay[_this->nXBassDlyPos];
			_this->XBassDelay[_this->nXBassDlyPos] = px[0];
			px[0] = v + _this->nXBassSum;
			v = _this->XBassDelay[_this->nXBassDlyPos+1];
			_this->XBassDelay[_this->nXBassDlyPos+1] = px[1];
			px[1] = v + _this->nXBassSum;
			_this->nXBassDlyPos = (_this->nXBassDlyPos + 2) & _this->nXBassMask;
			px += 2;
			n++;
			n &= _this->nXBassMask;
		}
		_this->nXBassBufferPos = n;
	}
	// Noise Reduction
	if (_this->gdwSoundSetup & SNDMIX_NOISEREDUCTION)
	{
		int n1 = _this->nLeftNR, n2 = _this->nRightNR;
		int *pnr = _this->MixSoundBuffer;
		int  nr = count;
		for (; nr; nr--)
		{
			int vnr = pnr[0] >> 1;
			pnr[0] = vnr + n1;
			n1 = vnr;
			vnr = pnr[1] >> 1;
			pnr[1] = vnr + n2;
			n2 = vnr;
			pnr += 2;
		}
		_this->nLeftNR = n1;
		_this->nRightNR = n2;
	}
}


/////////////////////////////////////////////////////////////////
// Clean DSP Effects interface

// [Reverb level 0(quiet)-100(loud)], [delay in ms, usually 40-200ms]
BOOL CSoundFile_SetReverbParameters(CSoundFile *_this, UINT nDepth, UINT nDelay)
//------------------------------------------------------------
{
	UINT gain;
	if (nDepth > 100) nDepth = 100;
	gain = nDepth / 20;
	if (gain > 4) gain = 4;
	_this->m_nReverbDepth = 4 - gain;
	if (nDelay < 40) nDelay = 40;
	if (nDelay > 250) nDelay = 250;
	_this->m_nReverbDelay = nDelay;
	return TRUE;
}


// [XBass level 0(quiet)-100(loud)], [cutoff in Hz 20-100]
BOOL CSoundFile_SetXBassParameters(CSoundFile *_this, UINT nDepth, UINT nRange)
//-----------------------------------------------------------
{
	UINT gain, range;
	if (nDepth > 100) nDepth = 100;
	gain = nDepth / 20;
	if (gain > 4) gain = 4;
	_this->m_nXBassDepth = 8 - gain;	// filter attenuation 1/256 .. 1/16
	range = nRange / 5;
	if (range > 5) range -= 5; else range = 0;
	if (nRange > 16) nRange = 16;
	_this->m_nXBassRange = 21 - range;	// filter average on 0.5-1.6ms
	return TRUE;
}


// [Surround level 0(quiet)-100(heavy)] [delay in ms, usually 5-50ms]
BOOL CSoundFile_SetSurroundParameters(CSoundFile *_this, UINT nDepth, UINT nDelay)
//--------------------------------------------------------------
{
	UINT gain = (nDepth * 16) / 100;
	if (gain > 16) gain = 16;
	if (gain < 1) gain = 1;
	_this->m_nProLogicDepth = gain;
	if (nDelay < 4) nDelay = 4;
	if (nDelay > 50) nDelay = 50;
	_this->m_nProLogicDelay = nDelay;
	return TRUE;
}

BOOL CSoundFile_SetWaveConfigEx(CSoundFile *_this, BOOL bSurround,BOOL bNoOverSampling,BOOL bReverb,BOOL hqido,BOOL bMegaBass,BOOL bNR,BOOL bEQ)
//----------------------------------------------------------------------------------------------------------------------------
{
	DWORD d = _this->gdwSoundSetup & ~(SNDMIX_SURROUND | SNDMIX_NORESAMPLING | SNDMIX_REVERB | SNDMIX_HQRESAMPLER | SNDMIX_MEGABASS | SNDMIX_NOISEREDUCTION | SNDMIX_EQ);
	if (bSurround) d |= SNDMIX_SURROUND;
	if (bNoOverSampling) d |= SNDMIX_NORESAMPLING;
	if (bReverb) d |= SNDMIX_REVERB;
	if (hqido) d |= SNDMIX_HQRESAMPLER;
	if (bMegaBass) d |= SNDMIX_MEGABASS;
	if (bNR) d |= SNDMIX_NOISEREDUCTION;
	if (bEQ) d |= SNDMIX_EQ;
	_this->gdwSoundSetup = d;
	CSoundFile_InitPlayer(_this, FALSE);
	return TRUE;
}
