#include "fmod_settings.h"

#ifdef FMOD_SUPPORT_COMPRESSOR

#include "fmod.h"
#include "fmod_dspi.h"
#include "fmod_dsp_compressor.h"

#ifdef PLATFORM_PS3_SPU
    #include "fmod_systemi_spu.h"
#else
    #include "fmod_systemi.h"
#endif

#include <stdio.h>

#ifdef PLATFORM_PS3
extern unsigned int _binary_spu_fmod_dsp_compressor_pic_start[];
#endif

namespace FMOD
{

FMOD_DSP_DESCRIPTION_EX dspcompressor;

#ifdef PLUGIN_EXPORTS

#ifdef __cplusplus
extern "C" {
#endif

    /*
        FMODGetDSPDescription is mandantory for every fmod plugin.  This is the symbol the registerplugin function searches for.
        Must be declared with F_API to make it export as stdcall.
    */
    F_DECLSPEC F_DLLEXPORT FMOD_DSP_DESCRIPTION_EX * F_API FMODGetDSPDescriptionEx()
    {
        return DSPCompressor::getDescriptionEx();
    }

#ifdef __cplusplus
}
#endif

#endif  /* PLUGIN_EXPORTS */

#define hold_constant (0.0025f)

#ifndef PLATFORM_PS3_SPU

FMOD_DSP_PARAMETERDESC dspcompressor_param[4] =
{
    { -60.0f, 0.0f,  0.0f, "Threshold", "dB", "Compressor threshold [-60dB, 0dB]  Default = 0dB." },
    { 10.0f, 200.0f,  50.0f, "Attack", "ms", "Compressor attack time. [10ms,200ms]  Default = 50ms." },
    { 20.0f, 1000.0f,  50.0f, "Release", "ms", "Compressor release time. [10ms,1000ms]  Default = 50ms." },
    { 0.0f, 30.0f,  0.0f, "Make up gain", "dB", "Compressor make up gain [0dB, 30dB]  Default = 0dB." }
};

#endif // PLATFORM_PS3_SPU

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_DSP_DESCRIPTION_EX *DSPCompressor::getDescriptionEx()
{
#ifndef PLATFORM_PS3_SPU
    FMOD_memset(&dspcompressor, 0, sizeof(FMOD_DSP_DESCRIPTION_EX));
    
    FMOD_strcpy(dspcompressor.name, "FMOD Compressor");
    dspcompressor.version       = 0x00010100;
    dspcompressor.create        = DSPCompressor::createCallback;

    #ifdef PLATFORM_PS3
    dspcompressor.read          = (FMOD_DSP_READCALLBACK)_binary_spu_fmod_dsp_compressor_pic_start;    /* SPU PIC entry address */
    #else
    dspcompressor.read          = DSPCompressor::readCallback;
    #endif

    dspcompressor.numparameters = sizeof(dspcompressor_param) / sizeof(dspcompressor_param[0]);
    dspcompressor.paramdesc     = dspcompressor_param;
    dspcompressor.setparameter  = DSPCompressor::setParameterCallback;
    dspcompressor.getparameter  = DSPCompressor::getParameterCallback;
#ifdef FMOD_SUPPORT_MEMORYTRACKER
    dspcompressor.getmemoryused = &DSPCompressor::getMemoryUsedCallback;
#endif

    dspcompressor.mType         = FMOD_DSP_TYPE_COMPRESSOR;
    dspcompressor.mCategory     = FMOD_DSP_CATEGORY_FILTER;
    dspcompressor.mSize         = sizeof(DSPCompressor);
#else
    dspcompressor.read          = DSPCompressor::readCallback;                  /* We only care about read function on SPU */
#endif
    return &dspcompressor;
}

#ifndef PLATFORM_PS3_SPU

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPCompressor::createInternal()
{
    init();

    int count;
    /*
        Setup ...
    */
    for (count = 0; count < mDescription.numparameters; count++)
    {
        setParameter(count, mDescription.paramdesc[count].defaultval);
    }

    for(count = 0; count < DSP_MAXLEVELS_MAX; count++)
    {
        mMaxChannelIn[count] = 0.0f;
    }
    mGain = 1.0f;

    return FMOD_OK;
}

#endif //!PLATFORM_PS3_SPU

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPCompressor::readInternal(float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int outchannels)
{
    unsigned int sample;
    int channel;
    float in_abs, x;
    float holdconstant     = mHoldConstant;     /* Copy members for better thread safety. */
    float threshold_lin    = mThreshold_lin;
    float attack_constant  = mAttack_constant;
    float release_constant = mRelease_constant;
    float gainmakeup_lin   = mGainMakeup_lin;

    if (!inbuffer)
    {
        return FMOD_OK;
    }

	if (!(speakermask & ((1 << inchannels)-1))) /*No speaker channels are active, copy in buffer to out buffer and skip the DSP*/
	{
		FMOD_memcpy(outbuffer, inbuffer, sizeof(float)*length*inchannels);
		return FMOD_OK;
	}
    /*
		if ( (++peakTimer_ >= peakHold_) || (keyLink > maxPeak_) ) {
			// if either condition is met:
			peakTimer_ = 0;		// reset peak timer
			maxPeak_ = keyLink;	// assign new peak to max peak
		}
		// attack/release
		if ( maxPeak_ > env_ )
			att_.run( maxPeak_, env_ );		// run attack phase
		else
			rel_.run( maxPeak_, env_ );		// run release phase
    */

    if ((speakermask & ((1 << inchannels)-1)) != ((1 << inchannels)-1))
    {
        for (sample = 0; sample < length; sample++)
        {
            unsigned int  base       = sample * inchannels;
            float        *sample_in  = &(inbuffer[base]);
            float        *sample_out = &(outbuffer[base]);
            float         in_max     = 0.0f;
            float         over;

            for (channel = 0; channel < inchannels; channel++)
            {
                if ((1 << channel) & speakermask)
				{
                    float *m;

                    x = sample_in[channel];
                    m = &(mMaxChannelIn[channel]);

                    in_abs = (x<0.0f) ? -x : x;          // ABS input

                    *m -= holdconstant;                 // Leak hold

                    if (in_abs > *m)                     // Hold new max
                    {
                        *m = in_abs;
                    }

                    if (*m > in_max)                     // Update total max
                    {
                        in_max = *m;
                    }
                }
            }

            // Note: This uses linear gain curves, not dB
            // dB is better but more costly

            // Over = linear ratio of input:threshold
            over = in_max/threshold_lin;
            
            // Apply attack or release curve to over
            // to obtain gain reduction
            if (over > 1.0f)
            {
                mGain = over + attack_constant * (mGain-over);
            }
            else
            {
                mGain = over + release_constant * (mGain-over);
            }

            // Apply gain reduction to output if reduction exists.
            if (mGain > 1.0f)
            {
                for (channel = 0; channel < inchannels; channel++)
                {
				    if (!((1 << channel) & speakermask))
				    {
					    sample_out[channel] = sample_in[channel];
				    }
				    else
				    {
					    sample_out[channel] = gainmakeup_lin * sample_in[channel] / mGain;
				    }
                }
            }
            else
            {
                for (channel = 0; channel < inchannels; channel++)
                {
				    if (!((1 << channel) & speakermask))
				    {
					    sample_out[channel] = sample_in[channel];
				    }
				    else
				    {
					    sample_out[channel] = gainmakeup_lin * sample_in[channel];
				    }
                }
            }
        }
    }
    else
    {
        for (sample = 0; sample < length; sample++)
        {
            unsigned int  base       = sample * inchannels;
            float        *sample_in  = &(inbuffer[base]);
            float        *sample_out = &(outbuffer[base]);
            float         in_max     = 0.0f;
            float         over;

            for (channel = 0; channel < inchannels; channel++)
            {
                float *m;

                x = sample_in[channel];
                m = &(mMaxChannelIn[channel]);

                in_abs = (x<0.0f) ? -x : x;          // ABS input

                *m -= holdconstant;                 // Leak hold

                if (in_abs > *m)                     // Hold new max
                {
                    *m = in_abs;
                }

                if (*m > in_max)                     // Update total max
                {
                    in_max = *m;
                }
            }

            // Note: This uses linear gain curves, not dB
            // dB is better but more costly

            // Over = linear ratio of input:threshold
            over = in_max/threshold_lin;
            
            // Apply attack or release curve to over
            // to obtain gain reduction
            if (over > 1.0f)
            {
                mGain = over + attack_constant * (mGain-over);
            }
            else
            {
                mGain = over + release_constant * (mGain-over);
            }

            // Apply gain reduction to output if reduction exists.
            if (mGain > 1.0f)
            {
                for (channel = 0; channel < inchannels; channel++)
                {
					sample_out[channel] = gainmakeup_lin * sample_in[channel] / mGain;
                }
            }
            else
            {
                for (channel = 0; channel < inchannels; channel++)
                {
					sample_out[channel] = gainmakeup_lin * sample_in[channel];
                }
            }
        }
    }

    return FMOD_OK;
}

#ifndef PLATFORM_PS3_SPU

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPCompressor::setParameterInternal(int index, float value)
{
    FMOD_RESULT result;
    int         outputrate;

    result = mSystem->getSoftwareFormat(&outputrate, 0, 0, 0, 0, 0);
    if (result != FMOD_OK)
    {
        return result;
    }

    mHoldConstant = 10.0f / (float)outputrate;  // 10Hz

    switch (index)
    {  
 
        case FMOD_DSP_COMPRESSOR_THRESHOLD:
        {
            mThreshold_dB = value;
            mThreshold_lin = FMOD_POW(10.0f, mThreshold_dB/20.0f);
            break;
        }
        case FMOD_DSP_COMPRESSOR_ATTACK:
        {
           mAttack_ms = value;
           mAttack_constant = FMOD_EXP(-1000.0f / (mAttack_ms * outputrate) );
           break;
        }
        case FMOD_DSP_COMPRESSOR_RELEASE:
        {
           mRelease_ms = value;
           mRelease_constant = FMOD_EXP(-1000.0f / (mRelease_ms * outputrate) );
           break;
        }
        case FMOD_DSP_COMPRESSOR_GAINMAKEUP:
        {
           mGainMakeup_dB = value;
           mGainMakeup_lin = FMOD_POW(10.0f, mGainMakeup_dB/20.0f);
           break;
        }
    }
/*
    if (mResonance >= 1.0f)
    {
        unsigned int  nInd;
        float         a0, a1, a2, b0, b1, b2;
        float         fs;                  // Sampling frequency, cutoff frequency 
        float         k[2];                // overall gain factor 
        float         ktotal;
        float        *coef;

	    k[0] = 1.0f;                       // Set overall filter gain 
        k[1] = 1.0f;
        ktotal = 1.0f;
	    coef = mCoefficients + 1;       // Skip k, or gain 
	    fs = (float)outputrate;         // Sampling frequency (Hz) 



        // Update overall filter gain in coef array 
        mCoefficients[0] = ktotal;
    }
*/

    return FMOD_OK;
}

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPCompressor::getParameterInternal(int index, float *value, char *valuestr)
{

    switch (index)
    {
        case FMOD_DSP_COMPRESSOR_THRESHOLD:
        {
            *value = mThreshold_dB;
            sprintf(valuestr, "%.02f", mThreshold_dB);
            break;
        }
        case FMOD_DSP_COMPRESSOR_ATTACK:
        {
            *value = mAttack_ms;
            sprintf(valuestr, "%.02f", mAttack_ms);
            break;
        }
        case FMOD_DSP_COMPRESSOR_RELEASE:
        {
            *value = mRelease_ms;
            sprintf(valuestr, "%.02f", mRelease_ms);
            break;
        }
        case FMOD_DSP_COMPRESSOR_GAINMAKEUP:
        {
            *value = mGainMakeup_dB;
            sprintf(valuestr, "%.02f", mGainMakeup_dB);
            break;
        }
    }

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
 
	[REMARKS]

    [PLATFORMS]
 
	[SEE_ALSO]
]
*/

#ifdef FMOD_SUPPORT_MEMORYTRACKER

FMOD_RESULT DSPCompressor::getMemoryUsedImpl(MemoryTracker *tracker)
{
    // Size of this class is already accounted for (via description.mSize). Just add extra allocated memory here.

    return FMOD_OK;
}

#endif


/*
    ==============================================================================================================

    CALLBACK INTERFACE

    ==============================================================================================================
*/


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPCompressor::createCallback(FMOD_DSP_STATE *dsp)
{
    DSPCompressor *compressor = (DSPCompressor *)dsp;

    return compressor->createInternal();
}

#endif //!PLATFORM_PS3_SPU

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPCompressor::readCallback(FMOD_DSP_STATE *dsp, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int outchannels)
{
    DSPCompressor *compressor = (DSPCompressor *)dsp;

    return compressor->readInternal(inbuffer, outbuffer, length, inchannels, outchannels);
}

#ifndef PLATFORM_PS3_SPU

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPCompressor::setParameterCallback(FMOD_DSP_STATE *dsp, int index, float value)
{
    DSPCompressor *compressor = (DSPCompressor *)dsp;

    return compressor->setParameterInternal(index, value);
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPCompressor::getParameterCallback(FMOD_DSP_STATE *dsp, int index, float *value, char *valuestr)
{
    DSPCompressor *compressor = (DSPCompressor *)dsp;

    return compressor->getParameterInternal(index, value, valuestr);
}


#ifdef FMOD_SUPPORT_MEMORYTRACKER
/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPCompressor::getMemoryUsedCallback(FMOD_DSP_STATE *dsp, MemoryTracker *tracker)
{
    DSPCompressor *compressor = (DSPCompressor *)dsp;

    return compressor->DSPCompressor::getMemoryUsed(tracker);
}
#endif

#endif //!PLATFORM_PS3_SPU

}

#endif
