/*
 * 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 "LVCS.h"
#include "LVCS_Private.h"
#include "LVCS_BypassMix.h"
#include "VectorArithmetic.h"
#include "LVCS_Tables.h"

/****************************************************************************************/
/*                                                                                      */
/*  Function Prototypes                                                                 */
/*                                                                                      */
/****************************************************************************************/
LVM_INT32 LVCS_MixerCallback(LVCS_Handle_t hInstance, void* pGeneralPurpose,
                             LVM_INT16 CallbackParam);

/************************************************************************************/
/*                                                                                  */
/* FUNCTION:                LVCS_BypassMixInit                                      */
/*                                                                                  */
/* DESCRIPTION:                                                                     */
/*  Initialises the bypass mixer module                                             */
/*                                                                                  */
/*  The overall gain of the processed path is set by the gains in the individual    */
/*  processing blocks and by the effect level gain.                                 */
/*                                                                                  */
/*  The unprocessed path must have matching gain for the processed path to ensure   */
/*  as they are mixed together the correct effect is achieved, this is the value    */
/*  UnprocLoss.                                                                     */
/*                                                                                  */
/*  The overall gain is corrected by a combination of a shift with saturation and a */
/*  linear scaler, loss. The loss ensures the sum in the mixer does not saturate    */
/*  and also corrects for any excess gain in the shift.                             */
/*                                                                                  */
/* PARAMETERS:                                                                      */
/*  hInstance               Instance Handle                                         */
/*  pParams                 Initialisation parameters                               */
/*                                                                                  */
/* RETURNS:                                                                         */
/*  LVCS_Success            Always succeeds                                         */
/*                                                                                  */
/* NOTES:                                                                           */
/*                                                                                  */
/************************************************************************************/

LVCS_ReturnStatus_en LVCS_BypassMixInit(LVCS_Handle_t hInstance, LVCS_Params_t* pParams) {
    LVM_UINT16 Offset;
    LVM_FLOAT Gain;
    LVM_FLOAT Current;
    LVCS_Instance_t* pInstance = (LVCS_Instance_t*)hInstance;
    LVCS_BypassMix_t* pConfig = (LVCS_BypassMix_t*)&pInstance->BypassMix;
    const Gain_t* pOutputGainTable;

    /*
     * Set the transition gain
     */
    if ((pParams->OperatingMode == LVCS_ON) && (pInstance->bTimerDone == LVM_TRUE) &&
        (pInstance->MSTarget1 != 0x7FFF) /* this indicates an off->on transition */
    ) {
        pInstance->TransitionGain = ((LVM_FLOAT)pParams->EffectLevel / 32767);
    } else {
        /* Select no effect level */
        pInstance->TransitionGain = 0;
    }

    /*
     * Calculate the output gain table offset
     */
    Offset =
            (LVM_UINT16)(pParams->SpeakerType + (pParams->SourceFormat * (1 + LVCS_EX_HEADPHONES)));
    pOutputGainTable = (Gain_t*)&LVCS_OutputGainTable[0];

    /*
     * Setup the mixer gain for the processed path
     */
    Gain = (LVM_FLOAT)(pOutputGainTable[Offset].Loss * pInstance->TransitionGain);

    pConfig->Mixer_Instance.MixerStream[0].CallbackParam = 0;
    pConfig->Mixer_Instance.MixerStream[0].pCallbackHandle = LVM_NULL;
    pConfig->Mixer_Instance.MixerStream[0].pCallBack = LVM_NULL;
    pConfig->Mixer_Instance.MixerStream[0].CallbackSet = 1;

    Current = LVC_Mixer_GetCurrent(&pConfig->Mixer_Instance.MixerStream[0]);
    LVC_Mixer_Init(&pConfig->Mixer_Instance.MixerStream[0], (LVM_FLOAT)(Gain), Current);
    LVC_Mixer_VarSlope_SetTimeConstant(&pConfig->Mixer_Instance.MixerStream[0],
                                       LVCS_BYPASS_MIXER_TC, pParams->SampleRate, 2);

    /*
     * Setup the mixer gain for the unprocessed path
     */
    Gain = (LVM_FLOAT)(pOutputGainTable[Offset].Loss *
                       (1.0 - (LVM_FLOAT)pInstance->TransitionGain));
    Gain = (LVM_FLOAT)pOutputGainTable[Offset].UnprocLoss * Gain;
    Current = LVC_Mixer_GetCurrent(&pConfig->Mixer_Instance.MixerStream[1]);
    LVC_Mixer_Init(&pConfig->Mixer_Instance.MixerStream[1], (LVM_FLOAT)(Gain), Current);
    LVC_Mixer_VarSlope_SetTimeConstant(&pConfig->Mixer_Instance.MixerStream[1],
                                       LVCS_BYPASS_MIXER_TC, pParams->SampleRate, 2);
    pConfig->Mixer_Instance.MixerStream[1].CallbackParam = 0;
    pConfig->Mixer_Instance.MixerStream[1].pCallbackHandle = hInstance;
    pConfig->Mixer_Instance.MixerStream[1].CallbackSet = 1;
    pConfig->Mixer_Instance.MixerStream[1].pCallBack = LVCS_MixerCallback;

    /*
     * Setup the output gain shift
     */
    pConfig->Output_Shift = pOutputGainTable[Offset].Shift;

    /*
     * Correct gain for the effect level
     */
    {
        LVM_FLOAT GainCorrect;
        LVM_FLOAT Gain1;
        LVM_FLOAT Gain2;

        Gain1 = LVC_Mixer_GetTarget(&pConfig->Mixer_Instance.MixerStream[0]);
        Gain2 = LVC_Mixer_GetTarget(&pConfig->Mixer_Instance.MixerStream[1]);
        /*
         * Calculate the gain correction
         */
        if (pInstance->Params.CompressorMode == LVM_MODE_ON) {
            GainCorrect = (LVM_FLOAT)(pInstance->VolCorrect.GainMin -
                                      (((LVM_FLOAT)pInstance->VolCorrect.GainMin *
                                        ((LVM_FLOAT)pInstance->TransitionGain))) +
                                      (((LVM_FLOAT)pInstance->VolCorrect.GainFull *
                                        ((LVM_FLOAT)pInstance->TransitionGain))));

            /*
             * Apply the gain correction
             */
            Gain1 = (Gain1 * GainCorrect);
            Gain2 = (Gain2 * GainCorrect);
        }

        /*
         * Set the gain values
         */
        pConfig->Output_Shift = pConfig->Output_Shift;
        LVC_Mixer_SetTarget(&pConfig->Mixer_Instance.MixerStream[0], Gain1);
        LVC_Mixer_VarSlope_SetTimeConstant(&pConfig->Mixer_Instance.MixerStream[0],
                                           LVCS_BYPASS_MIXER_TC, pParams->SampleRate, 2);
        LVC_Mixer_SetTarget(&pConfig->Mixer_Instance.MixerStream[1], Gain2);
        LVC_Mixer_VarSlope_SetTimeConstant(&pConfig->Mixer_Instance.MixerStream[1],
                                           LVCS_BYPASS_MIXER_TC, pParams->SampleRate, 2);
    }

    return (LVCS_SUCCESS);
}

/************************************************************************************/
/*                                                                                  */
/* FUNCTION:                LVCS_BypassMixer                                        */
/*                                                                                  */
/* DESCRIPTION:                                                                     */
/*  Apply Bypass Mix.                                                               */
/*                                                                                  */
/*  This mixes the processed and unprocessed data streams together to correct the   */
/*  overall system gain and allow progressive control of the Concert Sound effect.  */
/*                                                                                  */
/*  When the bypass mixer is enabled the output is the processed signal only and    */
/*  without gain correction.                                                        */
/*                                                                                  */
/* PARAMETERS:                                                                      */
/*  hInstance               Instance Handle                                         */
/*  pProcessed              Pointer to the processed data                           */
/*  pUnprocessed            Pointer to the unprocessed data                         */
/*  pOutData                Pointer to the output data                              */
/*  NumSamples              Number of samples to process                            */
/*                                                                                  */
/* RETURNS:                                                                         */
/*  LVCS_Success            Always succeeds                                         */
/*                                                                                  */
/* NOTES:                                                                           */
/*                                                                                  */
/************************************************************************************/

LVCS_ReturnStatus_en LVCS_BypassMixer(LVCS_Handle_t hInstance, const LVM_FLOAT* pProcessed,
                                      const LVM_FLOAT* pUnprocessed, LVM_FLOAT* pOutData,
                                      LVM_UINT16 NumSamples) {
    LVCS_Instance_t* pInstance = (LVCS_Instance_t*)hInstance;
    LVCS_BypassMix_t* pConfig = (LVCS_BypassMix_t*)&pInstance->BypassMix;
    LVM_UINT16 destNumSamples =
            (pInstance->Params.NrChannels == FCC_1) ? NumSamples : FCC_2 * NumSamples;

    /*
     * Check if the bypass mixer is enabled
     */
    if ((pInstance->Params.OperatingMode & LVCS_BYPASSMIXSWITCH) != 0) {
        /*
         * Apply the bypass mix
         */
        LVC_MixSoft_2St_D16C31_SAT(&pConfig->Mixer_Instance, pProcessed, (LVM_FLOAT*)pUnprocessed,
                                   pOutData, (LVM_INT16)destNumSamples);
        /*
         * Apply output gain correction shift
         */
        Shift_Sat_Float((LVM_INT16)pConfig->Output_Shift, (LVM_FLOAT*)pOutData,
                        (LVM_FLOAT*)pOutData, (LVM_INT16)destNumSamples);
    }

    return (LVCS_SUCCESS);
}

/************************************************************************************/
/*                                                                                  */
/* FUNCTION:                LVCS_MixerCallback                                      */
/*                                                                                  */
/************************************************************************************/
LVM_INT32 LVCS_MixerCallback(LVCS_Handle_t hInstance, void* pGeneralPurpose,
                             LVM_INT16 CallbackParam) {
    LVCS_Instance_t* pInstance = (LVCS_Instance_t*)hInstance;

    (void)pGeneralPurpose;

    /*
     * Off transition has completed in Headphone mode
     */
    if ((pInstance->OutputDevice == LVCS_HEADPHONE) && (pInstance->bInOperatingModeTransition) &&
        (pInstance->MSTarget0 == 0x0000) && /* this indicates an on->off transition */
        (CallbackParam == 0)) {
        /* Set operating mode to OFF */
        pInstance->Params.OperatingMode = LVCS_OFF;

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

        /* Signal to the bundle */
        if ((*pInstance->Capabilities.CallBack) != LVM_NULL) {
            (*pInstance->Capabilities.CallBack)(pInstance->Capabilities.pBundleInstance, LVM_NULL,
                                                (ALGORITHM_CS_ID | LVCS_EVENT_ALGOFF));
        }
    }

    if ((pInstance->OutputDevice == LVCS_HEADPHONE) && (pInstance->MSTarget0 == 1) &&
        (pInstance->bTimerDone == LVM_TRUE)) {
        /* Exit transition state */
        pInstance->bInOperatingModeTransition = LVM_FALSE;
    }

    return 1;
}
