#include <windows.h>

#include "compressp.h"
#include "clone.h"

STD_CREATE(Compressor)

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::QueryInterface
//
// Subclass can override if it wants to implement more interfaces.
//
STDMETHODIMP CDirectSoundCompressorDMO::NDQueryInterface(THIS_ REFIID riid, LPVOID *ppv)
{
    IMP_DSDMO_QI(riid,ppv);

    if (riid == IID_IPersist)
    {
        return GetInterface((IPersist*)this, ppv);
    }
    else if (riid == IID_IMediaObject)
    {
        return GetInterface((IMediaObject*)this, ppv);
    }
    else if (riid == IID_IDirectSoundFXCompressor)
    {
        return GetInterface((IDirectSoundFXCompressor*)this, ppv);
    }
    else if (riid == IID_ISpecifyPropertyPages)
    {
        return GetInterface((ISpecifyPropertyPages*)this, ppv);
    }
    else if (riid == IID_IMediaParams)
    {
        return GetInterface((IMediaParams*)this, ppv);
    }
    else if (riid == IID_IMediaParamInfo)
    {
        return GetInterface((IMediaParamInfo*)this, ppv);
    }
    else
        return CComBase::NDQueryInterface(riid, ppv);
}

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::CDirectSoundCompressorDMO
//
CDirectSoundCompressorDMO::CDirectSoundCompressorDMO( IUnknown *pUnk, HRESULT *phr ) 
  : CComBase( pUnk, phr),
    m_fDirty(false)
// { EAX: put init data here if any (otherwise use Discontinuity).
// } EAX
{
	m_EaxSamplesPerSec = 22050;
	m_LeftDelay. Init(0);
	m_RightDelay.Init(0);
}

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::Init()
//
HRESULT CDirectSoundCompressorDMO::Init()
{
    DSFXCompressor compress;
    HRESULT hr;

    // Force recalc of all internal parameters
    //
    hr = GetAllParameters(&compress);
    if (SUCCEEDED(hr)) hr = SetAllParameters(&compress);

    if (SUCCEEDED(hr)) hr = m_LeftDelay. Init(m_EaxSamplesPerSec);
	if (SUCCEEDED(hr) && m_cChannels == 2) {
		hr = m_RightDelay.Init(m_EaxSamplesPerSec);
	}

    if (SUCCEEDED(hr)) hr = Discontinuity();
    return hr;
}

const MP_CAPS g_capsAll = MP_CAPS_CURVE_JUMP | MP_CAPS_CURVE_LINEAR | MP_CAPS_CURVE_SQUARE | MP_CAPS_CURVE_INVSQUARE | MP_CAPS_CURVE_SINE;
static ParamInfo g_params[] =
{
//  index                   type        caps        min,                                max,                                neutral,                unit text,  label,              pwchText
    CPFP_Gain,              MPT_FLOAT,  g_capsAll,  DSFXCOMPRESSOR_GAIN_MIN,            DSFXCOMPRESSOR_GAIN_MAX,            0,                      L"",        L"Gain",            L"",
    CPFP_Attack,            MPT_FLOAT,  g_capsAll,  DSFXCOMPRESSOR_ATTACK_MIN,          DSFXCOMPRESSOR_ATTACK_MAX,          10,                     L"",        L"Attack",          L"",
    CPFP_Release,           MPT_FLOAT,  g_capsAll,  DSFXCOMPRESSOR_RELEASE_MIN,         DSFXCOMPRESSOR_RELEASE_MAX,         200,                    L"",        L"Release",         L"",
    CPFP_Threshold,         MPT_FLOAT,  g_capsAll,  DSFXCOMPRESSOR_THRESHOLD_MIN,       DSFXCOMPRESSOR_THRESHOLD_MAX,       -20,                    L"",        L"Threshold",       L"",
    CPFP_Ratio,             MPT_FLOAT,  g_capsAll,  DSFXCOMPRESSOR_RATIO_MIN,           DSFXCOMPRESSOR_RATIO_MAX,           3,                      L"",        L"Ratio",           L"",
    CPFP_Predelay,          MPT_FLOAT,  g_capsAll,  DSFXCOMPRESSOR_PREDELAY_MIN,        DSFXCOMPRESSOR_PREDELAY_MAX,        4,                      L"",        L"Predelay",        L"",
    };

HRESULT CDirectSoundCompressorDMO::InitOnCreation()
{
    HRESULT hr = InitParams(1, &GUID_TIME_REFERENCE, 0, 0, sizeof(g_params)/sizeof(*g_params), g_params);
    return hr;
}

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::~CDirectSoundCompressorDMO
//
CDirectSoundCompressorDMO::~CDirectSoundCompressorDMO() 
{
	m_LeftDelay. Init(-1);
	m_RightDelay.Init(-1);
}

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::Clone
//
STDMETHODIMP CDirectSoundCompressorDMO::Clone(IMediaObjectInPlace **pp) 
{
    return StandardDMOClone<CDirectSoundCompressorDMO, DSFXCompressor>(this, pp);
}

//
//	Bump - bump the delay pointers.
//
void CDirectSoundCompressorDMO::Bump(void)
{
// EAX {
	m_LeftDelay.Bump();
	m_RightDelay.Bump();
// }
}


HRESULT CDirectSoundCompressorDMO::Discontinuity() 
{
// { EAX

	m_LeftDelay.ZeroBuffer();
	if (m_cChannels == 2) {
		m_RightDelay.ZeroBuffer();
	}

	m_Envelope = m_CompGain = 0;

// } EAX
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////


float myexp( float finput, unsigned long maxexponent)
{
	
	unsigned long mantissa, exponent, exponentwidth ;
	long sign;
	long input;
	

#ifdef DONTUSEi386
	_asm {
		fld finput
		fistp input
	}
#else
	input    = (int)(finput);
#endif
	mantissa = input & 0x7FFFFFFFL ;
	sign     = input & 0x80000000L ; /* Preserve sign */            
	
	exponentwidth = 5;

	if ((0x80000000L & input) != 0) { /* Take absolute value of input */
		input = -input ;
	}
	
	/* Left-justify the mantissa and right-justify the exponent to separate */

	mantissa = input <<      exponentwidth ;
	exponent = input >> ( 31-exponentwidth ) ;
	
	/* 
	* Insert the implied '1' at the mantissa MSB if not a zero exponent and
	* adjust it.
	*/
	if( exponent != 0 ) {
		mantissa = mantissa | 0x80000000L ;
		exponent-- ;
	}
	
	mantissa = mantissa >> ( maxexponent-exponent ) ; 
	
	if( sign != 0  ) 
		  mantissa = ~mantissa ;
	
	float x = (float)mantissa;

	return(x);
} 

__forceinline void CDirectSoundCompressorDMO::DoOneSampleMono(int *l)
{
	int		Pos0, PosX;
	float	inPortL = (float)*l;
	float	outPortL;
	float	temp1, temp2;

	temp1			= inPortL;

//	left_delay[]	= temp1;

	Pos0 = m_LeftDelay.Pos(0);
	m_LeftDelay[Pos0] = temp1;

	temp1			= (float)fabs(temp1);

	// Take the log
#define LOG(x,y) mylog(x,y)
	temp1			= (float)fabs(LOG(temp1 * 0x8000,31));
	temp1                  /= 0x80000000;
	// Sidechain level meter
#ifndef MAX
#define MAX(x,y)	((x > y) ? x : y)
#endif

	m_EaxCompInputPeak	= MAX(temp1, m_EaxCompInputPeak);

	// Envelope follower

	temp2			= temp1 >= m_Envelope ? m_EaxAttackCoef : -m_EaxAttackCoef;
	temp2			= temp2 <= 0 ? m_EaxReleaseCoef : temp2;

//	m_Envelope		= temp2 : temp1 < m_Envelope;

	m_Envelope      = Interpolate(temp1, m_Envelope, temp2);

	m_CompGain		= MAX(m_Envelope, m_EaxCompThresh);

	// Log Difference between signal level and threshold level

	m_CompGain		= m_EaxCompThresh - m_CompGain;

#define cPOSFSCALE (float)0.9999999
	m_CompGain		= cPOSFSCALE + m_CompGain * m_EaxCompressionRatio;

	// Compressor gain reduction meter

#ifndef MIN
#define MIN(x,y)	((x < y) ? x : y)
#endif

#define EXP(x,y)	myexp(x,y)
	m_EaxCompGainMin= MIN(m_CompGain, m_EaxCompGainMin);
	m_CompGain		= (float)EXP(m_CompGain * 0x80000000, 31);
	m_CompGain	       /= 0x80000000;

//	outPortL		= left_point[@] * compGain;

	PosX     = m_LeftDelay.LastPos((int)m_EaxLeftPoint);
	outPortL = m_LeftDelay[PosX] * m_CompGain;

	temp1			= outPortL * m_EaxGainBiasIP;
	outPortL		= temp1 + outPortL * m_EaxGainBiasFP;

	*l = Saturate(outPortL);

	//Bump();
	m_LeftDelay.Bump();
}
__forceinline void CDirectSoundCompressorDMO::DoOneSample(int *l, int *r)
{
	int		Pos0, PosX;
	float	inPortL = (float)*l;
	float	inPortR = (float)*r;
	float	outPortL, outPortR;
	float	temp1, temp2;

	temp1			= inPortL;
	temp2			= inPortR;

//	left_delay[]	= temp1;

	Pos0 = m_LeftDelay.Pos(0);
	m_LeftDelay[Pos0] = temp1;

//	right_delay[]	= temp2;

	Pos0 = m_RightDelay.Pos(0);
	m_RightDelay[Pos0] = temp2;

	//Take the magnitude

	temp1			= (float)fabs(temp1);
	temp2			= (float)fabs(temp2);

	// Take the average 

//	temp1			= 0.5 : temp1 < temp2;

	temp1			= (temp1 + temp2) / 2;

	// Take the log
#define LOG(x,y) mylog(x,y)
	temp1			= (float)fabs(LOG(temp1 * 0x8000,31));
	temp1                  /= 0x80000000;
	// Sidechain level meter
#ifndef MAX
#define MAX(x,y)	((x > y) ? x : y)
#endif

	m_EaxCompInputPeak	= MAX(temp1, m_EaxCompInputPeak);

	// Envelope follower

	temp2			= temp1 >= m_Envelope ? m_EaxAttackCoef : -m_EaxAttackCoef;
	temp2			= temp2 <= 0 ? m_EaxReleaseCoef : temp2;

//	m_Envelope		= temp2 : temp1 < m_Envelope;

	m_Envelope      = Interpolate(temp1, m_Envelope, temp2);

	m_CompGain		= MAX(m_Envelope, m_EaxCompThresh);

	// Log Difference between signal level and threshold level

	m_CompGain		= m_EaxCompThresh - m_CompGain;

#define cPOSFSCALE (float)0.9999999
	m_CompGain		= cPOSFSCALE + m_CompGain * m_EaxCompressionRatio;

	// Compressor gain reduction meter

#ifndef MIN
#define MIN(x,y)	((x < y) ? x : y)
#endif

#define EXP(x,y)	myexp(x,y)
	m_EaxCompGainMin= MIN(m_CompGain, m_EaxCompGainMin);
	m_CompGain		= (float)EXP(m_CompGain * 0x80000000, 31);
	m_CompGain	       /= 0x80000000;

//	outPortL		= left_point[@] * compGain;

	PosX     = m_LeftDelay.LastPos((int)m_EaxLeftPoint);
	outPortL = m_LeftDelay[PosX] * m_CompGain;

//	outPortR		= right_point[@] * compGain;

	PosX     = m_RightDelay.LastPos((int)m_EaxRightPoint);
	outPortR = m_RightDelay[PosX] * m_CompGain;

	temp1			= outPortL * m_EaxGainBiasIP;
	outPortL		= temp1 + outPortL * m_EaxGainBiasFP;

	temp1			= outPortR * m_EaxGainBiasIP;
	outPortR		= temp1 + outPortR * m_EaxGainBiasFP;

	*l = Saturate(outPortL);
	*r = Saturate(outPortR);

	Bump();
}

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::FBRProcess
//
HRESULT CDirectSoundCompressorDMO::FBRProcess(DWORD cCompressors, BYTE *pIn, BYTE *pOut)
{
// { EAX
#define cb cCompressors
#define pin pIn
#define pout pOut

	if (m_cChannels == 1) {
		if (m_b8bit) {
			for (;cb > 0; --cb) {
				int i, j;

				i = *(pin+0)-128;
				i *=256;
//				j  = i;

				DoOneSampleMono(&i);
				
//				i += j;
//				i /= 2;
				
				i /= 256;

				*(pout+0) = (unsigned char)(i + 128);
			
				pin  += sizeof(unsigned char);
				pout += sizeof(unsigned char);
			}
		}
		else if (!m_b8bit) {
			for (;cb > 0; --cb) { // for (;cb > 0; cb -= sizeof(short)) {
               	short int *psi = (short int *)pin;
               	short int *pso = (short int *)pout;
				int i, j;

				i = *psi;
//				j =  i;

				DoOneSampleMono(&i);
				
//				i += j;
//				i /= 2;
				
               	*pso = (short)i;
			
				pin  += sizeof(short);
				pout += sizeof(short);
			}
		}
	}
	else if (m_cChannels == 2) {
		if (m_b8bit) {
			for (;cb > 0; --cb) { // for (;cb > 0; cb -= 2 * sizeof(unsigned char)) {
				int i, j;

				i = *(pin+0)-128;
				j = *(pin+1)-128;

				i *=256; j *=256;

				DoOneSample(&i, &j);
				
				i /= 256; j /= 256;
				
				*(pout+0) = (unsigned char)(i + 128);
				*(pout+1) = (unsigned char)(j + 128);
			
				pin  += 2 * sizeof(unsigned char);
				pout += 2 * sizeof(unsigned char);
			}
		}
		else if (!m_b8bit) {
			for (;cb > 0; --cb) { // for (;cb > 0; cb -= 2 * sizeof(short)) {
               	short int *psi = (short int *)pin;
               	short int *pso = (short int *)pout;
				int i, j;

				i = *(psi+0);
				j = *(psi+1);

				DoOneSample(&i, &j);
				
               	*(pso+0) = (short)i;
               	*(pso+1) = (short)j;
			
				pin  += 2 * sizeof(short);
				pout += 2 * sizeof(short);
			}
		}
	}
// } EAX
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::ProcessInPlace
//
HRESULT CDirectSoundCompressorDMO::ProcessInPlace(ULONG ulQuanta, LPBYTE pcbData, REFERENCE_TIME rtStart, DWORD dwFlags)
{
    // Update parameter values from any curves that may be in effect.
    this->UpdateActiveParams(rtStart, *this);

    return FBRProcess(ulQuanta, pcbData, pcbData);
}

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::SetParam
//
// { EAX
// }

HRESULT CDirectSoundCompressorDMO::SetParamInternal(DWORD dwParamIndex, MP_DATA value, bool fSkipPasssingToParamManager)
{
	float	fVal;

	if (!m_EaxSamplesPerSec) return DMO_E_TYPE_NOT_ACCEPTED;	// NO TYPE!

    switch (dwParamIndex)
    {
// { EAX
	case CPFP_Gain : {
		CHECK_PARAM(DSFXCOMPRESSOR_GAIN_MIN, DSFXCOMPRESSOR_GAIN_MAX);

		fVal = (float)pow(10, value/20);	//Convert from dB to linear

		float _gainBiasIP, _gainBiasFP;
		double d;

		_gainBiasFP = (float)modf((double)fVal, &d);
		_gainBiasIP = (float)d;

		INTERPOLATE (GainBiasFP, TOFRACTION(_gainBiasFP));
		PUT_EAX_FVAL(GainBiasIP, TOFRACTION(_gainBiasIP));
		break;
	}
	case CPFP_Attack :
		CHECK_PARAM(DSFXCOMPRESSOR_ATTACK_MIN, DSFXCOMPRESSOR_ATTACK_MAX);

		m_EaxAttackCoef = (float)pow(10, -1/(value*m_EaxSamplesPerSec/1000));

		PUT_EAX_FVAL(AttackCoef, TOFRACTION(m_EaxAttackCoef));
		break;

	case CPFP_Release :
		CHECK_PARAM(DSFXCOMPRESSOR_RELEASE_MIN, DSFXCOMPRESSOR_RELEASE_MAX);

		m_EaxReleaseCoef = (float)pow(10, -1/(value*m_EaxSamplesPerSec/1000));
		break;

	case CPFP_Threshold : {
		CHECK_PARAM(DSFXCOMPRESSOR_THRESHOLD_MIN, DSFXCOMPRESSOR_THRESHOLD_MAX);

		fVal = (float)pow(10, value/20);	//Convert from dB to linear

		float _compThresh;
		float a, b;

		a = (float)(pow(2, 26) * log(fVal * pow(2, 31))/log(2) + pow(2, 26));
		b = (float)(pow(2, 31) - 1.0);
		_compThresh = a < b ? a : b;

		_compThresh /= (float)0x80000000;

		PUT_EAX_FVAL(CompThresh, _compThresh);
		break;
	}
	case CPFP_Ratio :
		CHECK_PARAM(DSFXCOMPRESSOR_RATIO_MIN, DSFXCOMPRESSOR_RATIO_MAX);

		m_EaxCompressionRatio = (float)(1.0 - 1.0/value);

		PUT_EAX_FVAL(CompressionRatio, TOFRACTION(m_EaxCompressionRatio));
		break;

	case CPFP_Predelay : {
		CHECK_PARAM(DSFXCOMPRESSOR_PREDELAY_MIN, DSFXCOMPRESSOR_PREDELAY_MAX);

		float _length = (float)(value * m_EaxSamplesPerSec/1000.0);

		PUT_EAX_LVAL(LeftPoint,  _length + 2);
		PUT_EAX_LVAL(RightPoint, _length + 2);
		break;
	}
	/*
	** Removed from PropertySet, Processing code left behind so we can resurrect later
	**
	case CPFP_CompMeterReset : {
		CHECK_PARAM(DSFXCOMPRESSOR_COMPMETERRESET_MIN, DSFXCOMPRESSOR_COMPMETERRESET_MAX);

		if(!value)
			break; // return E_FAIL;


		float InputPeak = m_EaxCompInputPeak;
		float GainMin   = m_EaxCompGainMin;

		PUT_EAX_FVAL(CompInputPeak, 0);
		PUT_EAX_FVAL(CompGainMin,   0.999999999);

		InputPeak =   (float)(186.0 * (InputPeak - 0.999999999)/0.999999999);
		GainMin   = - (float)(186.0 * (GainMin   - 0.999999999)/0.999999999);

		CParamsManager::SetParam(CPFP_CompMeterReset , 0);

		if (!fSkipPasssingToParamManager)
			CParamsManager::SetParam(CPFP_CompInputMeter , InputPeak);

		if (!fSkipPasssingToParamManager)
			CParamsManager::SetParam(CPFP_CompGainMeter , GainMin);

		break;
	}
	*/
	
	/*	These values can't be set, only queried.
	 */

	/*
	case CPFP_CompInputMeter :
		CHECK_PARAM(DSFXCOMPRESSOR_COMPINPUTMETER_MIN, DSFXCOMPRESSOR_COMPINPUTMETER_MAX);
		return E_FAIL;

	case CPFP_CompGainMeter :
		CHECK_PARAM(DSFXCOMPRESSOR_COMPGAINMETER_MIN, DSFXCOMPRESSOR_COMPGAINMETER_MAX);
		return E_FAIL;
// } EAX
    */
    default:
        return E_FAIL;
    }

    // Let base class set this so it can handle all the rest of the param calls.
    // Skip the base class if fSkipPasssingToParamManager.  This indicates that we're calling the function
    //    internally using valuds that came from the base class -- thus there's no need to tell it values it
    //    already knows.
    return fSkipPasssingToParamManager ? S_OK : CParamsManager::SetParam(dwParamIndex, value);
}

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::SetAllParameters
//
STDMETHODIMP CDirectSoundCompressorDMO::SetAllParameters(LPCDSFXCompressor pComp)
{
    HRESULT hr = S_OK;
	
	// Check that the pointer is not NULL
    if (pComp == NULL)
    {
        Trace(1,"ERROR: pComp is NULL\n");
        hr = E_POINTER;
    }

	// Set the parameters
    if (SUCCEEDED(hr)) hr = SetParam(CPFP_Gain, pComp->fGain);
    if (SUCCEEDED(hr)) hr = SetParam(CPFP_Attack, pComp->fAttack);   
    if (SUCCEEDED(hr)) hr = SetParam(CPFP_Release, pComp->fRelease);
    if (SUCCEEDED(hr)) hr = SetParam(CPFP_Threshold, pComp->fThreshold);
    if (SUCCEEDED(hr)) hr = SetParam(CPFP_Ratio, pComp->fRatio);
    if (SUCCEEDED(hr)) hr = SetParam(CPFP_Predelay, pComp->fPredelay);
    
	/*	These values can only be queried, not set.  CPFP_CompMeterReset fills
	 *	the values.
	 */
//	if (SUCCEEDED(hr)) hr = SetParam(CPFP_CompInputMeter, pComp->fCompInputMeter);
//	if (SUCCEEDED(hr)) hr = SetParam(CPFP_CompGainMeter, pComp->fCompGainMeter);

    m_fDirty = true;
	return hr;
}

//////////////////////////////////////////////////////////////////////////////
//
// CDirectSoundCompressorDMO::GetAllParameters
//
STDMETHODIMP CDirectSoundCompressorDMO::GetAllParameters(LPDSFXCompressor pCompressor)
{
    HRESULT hr = S_OK;
	MP_DATA mpd;

	if (pCompressor == NULL) return E_POINTER;
	
#define GET_PARAM(x,y) \
	if (SUCCEEDED(hr)) { \
		hr = GetParam(x, &mpd);	\
		if (SUCCEEDED(hr)) pCompressor->y = mpd; \
	}

    GET_PARAM(CPFP_Attack, fAttack);   
    GET_PARAM(CPFP_Release, fRelease);
    GET_PARAM(CPFP_Threshold, fThreshold);
    GET_PARAM(CPFP_Ratio, fRatio);
    GET_PARAM(CPFP_Gain, fGain);
    GET_PARAM(CPFP_Predelay, fPredelay);
    
	return hr;
}

// GetClassID
//
// Part of the persistent file support.  We must supply our class id
// which can be saved in a graph file and used on loading a graph with
// this fx in it to instantiate this filter via CoCreateInstance.
//
HRESULT CDirectSoundCompressorDMO::GetClassID(CLSID *pClsid)
{
    if (pClsid==NULL) {
        return E_POINTER;
    }
    *pClsid = GUID_DSFX_STANDARD_COMPRESSOR;
    return NOERROR;

} // GetClassID