/*
 * Copyright (C) 2004-2010 NXP Software
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/****************************************************************************************/
/*                                                                                      */
/*  Includes                                                                            */
/*                                                                                      */
/****************************************************************************************/

#include "LVEQNB.h"
#include "LVEQNB_Private.h"
#include "VectorArithmetic.h"
#include "BIQUAD.h"


/****************************************************************************************/
/*                                                                                      */
/*  Defines                                                                             */
/*                                                                                      */
/****************************************************************************************/

#define     LOW_FREQ            298             /* 32768/110 for low test frequency */
#define     HIGH_FREQ           386             /* 32768/85 for high test frequency */

/****************************************************************************************/
/*                                                                                      */
/* FUNCTION:                 LVEQNB_GetParameters                                       */
/*                                                                                      */
/* DESCRIPTION:                                                                         */
/*  Request the N-Band equaliser parameters. The current parameter set is returned via  */
/*  the parameter pointer.                                                              */
/*                                                                                      */
/* PARAMETERS:                                                                          */
/*  hInstance                Instance handle                                            */
/*  pParams                  Pointer to an empty parameter structure                    */
/*                                                                                      */
/* RETURNS:                                                                             */
/*  LVEQNB_SUCCESS          Succeeds                                                    */
/*  LVEQNB_NULLADDRESS      Instance or pParams  is NULL pointer                        */
/*                                                                                      */
/* NOTES:                                                                               */
/*  1.  This function may be interrupted by the LVEQNB_Process function                 */
/*                                                                                      */
/****************************************************************************************/

LVEQNB_ReturnStatus_en LVEQNB_GetParameters(LVEQNB_Handle_t     hInstance,
                                            LVEQNB_Params_t     *pParams)
{

    LVEQNB_Instance_t    *pInstance =(LVEQNB_Instance_t  *)hInstance;

   /*
     * Check for error conditions
     */
    if((hInstance == LVM_NULL) || (pParams == LVM_NULL))
    {
        return LVEQNB_NULLADDRESS;
    }

    *pParams = pInstance->Params;

    return(LVEQNB_SUCCESS);
}


/************************************************************************************/
/*                                                                                  */
/* FUNCTION:                 LVEQNB_GetCapabilities                                 */
/*                                                                                  */
/* DESCRIPTION:                                                                     */
/*  Get the N-Band equaliser capabilities. The current capabilities are returned    */
/*  via the pointer.                                                                */
/*                                                                                  */
/* PARAMETERS:                                                                      */
/*  hInstance                Instance handle                                        */
/*  pCapabilities            Pointer to an empty capability structure               */
/*                                                                                  */
/* RETURNS:                                                                         */
/*  LVEQNB_Success           Succeeds                                               */
/*  LVEQNB_NULLADDRESS       hInstance or pCapabilities is NULL                     */
/*                                                                                  */
/* NOTES:                                                                           */
/*  1.  This function may be interrupted by the LVEQNB_Process function             */
/*                                                                                  */
/************************************************************************************/

LVEQNB_ReturnStatus_en LVEQNB_GetCapabilities(LVEQNB_Handle_t           hInstance,
                                              LVEQNB_Capabilities_t     *pCapabilities)
{

    LVEQNB_Instance_t    *pInstance =(LVEQNB_Instance_t  *)hInstance;

    if((hInstance == LVM_NULL) || (pCapabilities == LVM_NULL))
    {
        return LVEQNB_NULLADDRESS;
    }

    *pCapabilities = pInstance->Capabilities;

    return(LVEQNB_SUCCESS);
}


/************************************************************************************/
/*                                                                                  */
/* FUNCTION:            LVEQNB_SetFilters                                           */
/*                                                                                  */
/* DESCRIPTION:                                                                     */
/*  Sets the filter type based on the definition.                                   */
/*                                                                                  */
/* PARAMETERS:                                                                      */
/*  pInstance           Pointer to the instance                                     */
/*  pParams             Initialisation parameters                                   */
/*                                                                                  */
/* RETURNS:                                                                         */
/*  void                Nothing                                                     */
/*                                                                                  */
/* NOTES:                                                                           */
/*  1. To select the biquad type the follow rules are applied:                      */
/*          Double precision    if (fc <= fs/110)                                   */
/*          Double precision    if (fs/110 < fc < fs/85) & (Q>3)                    */
/*          Single precision    otherwise                                           */
/*                                                                                  */
/************************************************************************************/

void    LVEQNB_SetFilters(LVEQNB_Instance_t     *pInstance,
                          LVEQNB_Params_t       *pParams)
{
#ifdef HIGHER_FS
    extern const LVM_UINT32   LVEQNB_SampleRateTab[];           /* Sample rate table */
#else
    extern const LVM_UINT16   LVEQNB_SampleRateTab[];           /* Sample rate table */
#endif

    LVM_UINT16          i;                                      /* Filter band index */
    LVM_UINT32          fs = (LVM_UINT32)LVEQNB_SampleRateTab[(LVM_UINT16)pParams->SampleRate];  /* Sample rate */
    LVM_UINT32          fc;                                     /* Filter centre frequency */
    LVM_INT16           QFactor;                                /* Filter Q factor */


    pInstance->NBands = pParams->NBands;

    for (i=0; i<pParams->NBands; i++)
    {
        /*
         * Get the filter settings
         */
        fc = (LVM_UINT32)pParams->pBandDefinition[i].Frequency;     /* Get the band centre frequency */
        QFactor = (LVM_INT16)pParams->pBandDefinition[i].QFactor;   /* Get the band Q factor */

#ifdef BUILD_FLOAT
        pInstance->pBiquadType[i] = LVEQNB_SinglePrecision_Float; /* Default to single precision */
#else
        /*
         * For each filter set the type of biquad required
         */
        pInstance->pBiquadType[i] = LVEQNB_SinglePrecision;         /* Default to single precision */
#endif
#ifndef BUILD_FLOAT
        if ((fc << 15) <= (LOW_FREQ * fs))
        {
            /*
             * fc <= fs/110
             */
            pInstance->pBiquadType[i] = LVEQNB_DoublePrecision;
        }
        else if (((fc << 15) <= (HIGH_FREQ * fs)) && (QFactor > 300))
        {
            /*
             * (fs/110 < fc < fs/85) & (Q>3)
             */
            pInstance->pBiquadType[i] = LVEQNB_DoublePrecision;
        }
#endif

        /*
         * Check for out of range frequencies
         */
        if (fc > (fs >> 1))
        {
            pInstance->pBiquadType[i] = LVEQNB_OutOfRange;
        }


        /*
         * Copy the filter definition to persistant memory
         */
        pInstance->pBandDefinitions[i] = pParams->pBandDefinition[i];

    }
}


/************************************************************************************/
/*                                                                                  */
/* FUNCTION:            LVEQNB_SetCoefficients                                      */
/*                                                                                  */
/* DESCRIPTION:                                                                     */
/*  Sets the filter coefficients. This uses the type to select single or double     */
/*  precision coefficients.                                                         */
/*                                                                                  */
/* PARAMETERS:                                                                      */
/*  pInstance           Pointer to the instance                                     */
/*  pParams             Initialisation parameters                                   */
/*                                                                                  */
/************************************************************************************/

void    LVEQNB_SetCoefficients(LVEQNB_Instance_t     *pInstance)
{

    LVM_UINT16              i;                          /* Filter band index */
    LVEQNB_BiquadType_en    BiquadType;                 /* Filter biquad type */


    /*
     * Set the coefficients for each band by the init function
     */
    for (i=0; i<pInstance->Params.NBands; i++)
    {

        /*
         * Check band type for correct initialisation method and recalculate the coefficients
         */
        BiquadType = pInstance->pBiquadType[i];
        switch  (BiquadType)
        {
#ifdef BUILD_FLOAT
            case    LVEQNB_SinglePrecision_Float:
            {
                PK_FLOAT_Coefs_t      Coefficients;
                /*
                 * Calculate the single precision coefficients
                 */
                LVEQNB_SinglePrecCoefs((LVM_UINT16)pInstance->Params.SampleRate,
                                       &pInstance->pBandDefinitions[i],
                                       &Coefficients);
                /*
                 * Set the coefficients
                 */
                PK_2I_D32F32CssGss_TRC_WRA_01_Init(&pInstance->pEQNB_FilterState_Float[i],
                                                   &pInstance->pEQNB_Taps_Float[i],
                                                   &Coefficients);
                break;
            }
#else
            case    LVEQNB_DoublePrecision:
            {
                PK_C32_Coefs_t      Coefficients;

                /*
                 * Calculate the double precision coefficients
                 */
                LVEQNB_DoublePrecCoefs((LVM_UINT16)pInstance->Params.SampleRate,
                                       &pInstance->pBandDefinitions[i],
                                       &Coefficients);

                /*
                 * Set the coefficients
                 */
                PK_2I_D32F32CllGss_TRC_WRA_01_Init(&pInstance->pEQNB_FilterState[i],
                                                   &pInstance->pEQNB_Taps[i],
                                                   &Coefficients);
                break;
            }

            case    LVEQNB_SinglePrecision:
            {
                PK_C16_Coefs_t      Coefficients;

                /*
                 * Calculate the single precision coefficients
                 */
                LVEQNB_SinglePrecCoefs((LVM_UINT16)pInstance->Params.SampleRate,
                                       &pInstance->pBandDefinitions[i],
                                       &Coefficients);

                /*
                 * Set the coefficients
                 */
                PK_2I_D32F32CssGss_TRC_WRA_01_Init(&pInstance->pEQNB_FilterState[i],
                                                   &pInstance->pEQNB_Taps[i],
                                                   &Coefficients);
                break;
            }
#endif
            default:
                break;
        }
    }

}


/************************************************************************************/
/*                                                                                  */
/* FUNCTION:            LVEQNB_ClearFilterHistory                                   */
/*                                                                                  */
/* DESCRIPTION:                                                                     */
/*  Clears the filter data history                                                  */
/*                                                                                  */
/* PARAMETERS:                                                                      */
/*  pInstance           Pointer to the instance                                     */
/*                                                                                  */
/************************************************************************************/
#ifndef BUILD_FLOAT
void    LVEQNB_ClearFilterHistory(LVEQNB_Instance_t     *pInstance)
{
    LVM_INT16       *pTapAddress;
    LVM_INT16       NumTaps;


    pTapAddress = (LVM_INT16 *)pInstance->pEQNB_Taps;
    NumTaps     = (LVM_INT16)((pInstance->Capabilities.MaxBands * sizeof(Biquad_2I_Order2_Taps_t))/sizeof(LVM_INT16));

    if (NumTaps != 0)
    {
        LoadConst_16(0,                                 /* Clear the history, value 0 */
                     pTapAddress,                       /* Destination */
                     NumTaps);                          /* Number of words */
    }
}
#else
void    LVEQNB_ClearFilterHistory(LVEQNB_Instance_t     *pInstance)
{
    LVM_FLOAT       *pTapAddress;
    LVM_INT16       NumTaps;

    pTapAddress = (LVM_FLOAT *)pInstance->pEQNB_Taps_Float;
    NumTaps     = (LVM_INT16)((pInstance->Capabilities.MaxBands * \
                                    sizeof(Biquad_2I_Order2_FLOAT_Taps_t)) / sizeof(LVM_FLOAT));

    if (NumTaps != 0)
    {
        LoadConst_Float(0,                                 /* Clear the history, value 0 */
                        pTapAddress,                       /* Destination */
                        NumTaps);                          /* Number of words */
    }
}
#endif
/****************************************************************************************/
/*                                                                                      */
/* FUNCTION:                LVEQNB_Control                                              */
/*                                                                                      */
/* DESCRIPTION:                                                                         */
/*  Sets or changes the LifeVibes module parameters.                                    */
/*                                                                                      */
/* PARAMETERS:                                                                          */
/*  hInstance               Instance handle                                             */
/*  pParams                 Pointer to a parameter structure                            */
/*                                                                                      */
/* RETURNS:                                                                             */
/*  LVEQNB_Success          Always succeeds                                             */
/*  LVEQNB_NULLADDRESS      Instance or pParams  is NULL pointer                        */
/*  LVEQNB_NULLADDRESS      NULL address for the equaliser filter definitions and the   */
/*                          number of bands is non-zero                                 */
/*                                                                                      */
/* NOTES:                                                                               */
/*  1.  This function may be interrupted by the LVEQNB_Process function                 */
/*                                                                                      */
/****************************************************************************************/

LVEQNB_ReturnStatus_en LVEQNB_Control(LVEQNB_Handle_t        hInstance,
                                      LVEQNB_Params_t        *pParams)
{

    LVEQNB_Instance_t    *pInstance = (LVEQNB_Instance_t  *)hInstance;
    LVM_INT16            bChange    = LVM_FALSE;
    LVM_INT16            i = 0;
    LVEQNB_Mode_en       OperatingModeSave ;

    /*
     * Check for error conditions
     */
    if((hInstance == LVM_NULL) || (pParams == LVM_NULL))
    {
        return LVEQNB_NULLADDRESS;
    }

    if((pParams->NBands !=0) && (pParams->pBandDefinition==LVM_NULL))
    {
        return LVEQNB_NULLADDRESS;
    }

    OperatingModeSave = pInstance->Params.OperatingMode;

    /* Set the alpha factor of the mixer */
    if (pParams->SampleRate != pInstance->Params.SampleRate)
    {
        LVC_Mixer_VarSlope_SetTimeConstant(&pInstance->BypassMixer.MixerStream[0],LVEQNB_BYPASS_MIXER_TC,(LVM_Fs_en)pParams->SampleRate,2);
        LVC_Mixer_VarSlope_SetTimeConstant(&pInstance->BypassMixer.MixerStream[1],LVEQNB_BYPASS_MIXER_TC,(LVM_Fs_en)pParams->SampleRate,2);
    }


    if( (pInstance->Params.NBands            !=  pParams->NBands          ) ||
        (pInstance->Params.OperatingMode     !=  pParams->OperatingMode   ) ||
        (pInstance->Params.pBandDefinition   !=  pParams->pBandDefinition ) ||
        (pInstance->Params.SampleRate        !=  pParams->SampleRate      ) ||
        (pInstance->Params.SourceFormat      !=  pParams->SourceFormat    ))
    {

        bChange = LVM_TRUE;
    }
    else
    {
        for(i = 0; i < pParams->NBands; i++)
        {

            if((pInstance->pBandDefinitions[i].Frequency  != pParams->pBandDefinition[i].Frequency )||
                (pInstance->pBandDefinitions[i].Gain       != pParams->pBandDefinition[i].Gain      )||
                (pInstance->pBandDefinitions[i].QFactor    != pParams->pBandDefinition[i].QFactor   ))
            {

                bChange = LVM_TRUE;
            }
        }
    }


    // During operating mode transition, there is a race condition where the mode
    // is still LVEQNB_ON, but the effect is considered disabled in the upper layers.
    // modeChange handles this special race condition.
    const int /* bool */ modeChange = pParams->OperatingMode != OperatingModeSave
            || (OperatingModeSave == LVEQNB_ON
                    && pInstance->bInOperatingModeTransition
                    && LVC_Mixer_GetTarget(&pInstance->BypassMixer.MixerStream[0]) == 0);

    if (bChange || modeChange) {

        /*
         * If the sample rate has changed clear the history
         */
        if (pInstance->Params.SampleRate != pParams->SampleRate)
        {
            LVEQNB_ClearFilterHistory(pInstance);           /* Clear the history */
        }

        /*
         * Update the instance parameters
         */
        pInstance->Params = *pParams;


        /*
         * Reset the filters except if the algo is switched off
         */
        if(pParams->OperatingMode != LVEQNB_BYPASS){
            /*
             * Reset the filters as all parameters could have changed
             */
            LVEQNB_SetFilters(pInstance,                        /* Instance pointer */
                              pParams);                         /* New parameters */

            /*
             * Update the filters
             */
            LVEQNB_SetCoefficients(pInstance);                  /* Instance pointer */
        }

        if (modeChange) {
            if(pParams->OperatingMode == LVEQNB_ON)
            {
#ifdef BUILD_FLOAT
                LVC_Mixer_SetTarget(&pInstance->BypassMixer.MixerStream[0], 1.0f);
                LVC_Mixer_SetTarget(&pInstance->BypassMixer.MixerStream[1], 0.0f);
#else
                LVC_Mixer_SetTarget(&pInstance->BypassMixer.MixerStream[0],LVM_MAXINT_16);
                LVC_Mixer_SetTarget(&pInstance->BypassMixer.MixerStream[1],0);
#endif
                pInstance->BypassMixer.MixerStream[0].CallbackSet        = 1;
                pInstance->BypassMixer.MixerStream[1].CallbackSet        = 1;
            }
            else
            {
                /* Stay on the ON operating mode until the transition is done */
                // This may introduce a state race condition if the effect is enabled again
                // while in transition.  This is fixed in the modeChange logic.
                pInstance->Params.OperatingMode = LVEQNB_ON;
#ifdef BUILD_FLOAT
                LVC_Mixer_SetTarget(&pInstance->BypassMixer.MixerStream[0], 0.0f);
                LVC_Mixer_SetTarget(&pInstance->BypassMixer.MixerStream[1], 1.0f);
#else
                LVC_Mixer_SetTarget(&pInstance->BypassMixer.MixerStream[0],0);
                LVC_Mixer_SetTarget(&pInstance->BypassMixer.MixerStream[1],LVM_MAXINT_16);
#endif
                pInstance->BypassMixer.MixerStream[0].CallbackSet        = 1;
                pInstance->BypassMixer.MixerStream[1].CallbackSet        = 1;
            }
            LVC_Mixer_VarSlope_SetTimeConstant(&pInstance->BypassMixer.MixerStream[0],LVEQNB_BYPASS_MIXER_TC,(LVM_Fs_en)pParams->SampleRate,2);
            LVC_Mixer_VarSlope_SetTimeConstant(&pInstance->BypassMixer.MixerStream[1],LVEQNB_BYPASS_MIXER_TC,(LVM_Fs_en)pParams->SampleRate,2);
            pInstance->bInOperatingModeTransition = LVM_TRUE;
        }

    }
    return(LVEQNB_SUCCESS);
}


/****************************************************************************************/
/*                                                                                      */
/* FUNCTION:                LVEQNB_BypassMixerCallBack                                  */
/*                                                                                      */
/* DESCRIPTION:                                                                         */
/*  CallBack function of the mixer                                                      */
/*  transition                                                                          */
/*                                                                                      */
/****************************************************************************************/
LVM_INT32 LVEQNB_BypassMixerCallBack (void* hInstance,
                                      void *pGeneralPurpose,
                                      LVM_INT16 CallbackParam)
{
    LVEQNB_Instance_t      *pInstance =(LVEQNB_Instance_t  *)hInstance;
    LVM_Callback            CallBack  = pInstance->Capabilities.CallBack;

    (void) pGeneralPurpose;

     /*
      * Send an ALGOFF event if the ON->OFF switch transition is finished
      */
#ifdef BUILD_FLOAT
    if((LVC_Mixer_GetTarget(&pInstance->BypassMixer.MixerStream[0]) == 0) &&
       (CallbackParam == 0)){
#else
    if((LVC_Mixer_GetTarget(&pInstance->BypassMixer.MixerStream[0]) == 0x00000000) &&
       (CallbackParam == 0)){
#endif
        pInstance->Params.OperatingMode = LVEQNB_BYPASS;
        if (CallBack != LVM_NULL){
            CallBack(pInstance->Capabilities.pBundleInstance, LVM_NULL, ALGORITHM_EQNB_ID|LVEQNB_EVENT_ALGOFF);
        }
    }

    /*
     *  Exit transition state
     */
    pInstance->bInOperatingModeTransition = LVM_FALSE;

    return 1;
}