/*----------------------------------------------------------------------------
 *
 * File:
 * eas_mixer.c
 *
 * Contents and purpose:
 * This file contains the critical components of the mix engine that
 * must be optimized for best performance.
 *
 * Copyright Sonic Network Inc. 2005

 * 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.
 *
 *----------------------------------------------------------------------------
 * Revision Control:
 *   $Revision: 706 $
 *   $Date: 2007-05-31 17:22:51 -0700 (Thu, 31 May 2007) $
 *----------------------------------------------------------------------------
*/

//3 dls: This module is in the midst of being converted from a synth
//3 specific module to a general purpose mix engine

/*------------------------------------
 * includes
 *------------------------------------
*/
#include "eas_data.h"
#include "eas_host.h"
#include "eas_math.h"
#include "eas_mixer.h"
#include "eas_config.h"
#include "eas_report.h"

#ifdef _MAXIMIZER_ENABLED
EAS_I32 MaximizerProcess (EAS_VOID_PTR pInstData, EAS_I32 *pSrc, EAS_I32 *pDst, EAS_I32 numSamples);
#endif

/*------------------------------------
 * defines
 *------------------------------------
*/

/* need to boost stereo by ~3dB to compensate for the panner */
#define STEREO_3DB_GAIN_BOOST       512

/*----------------------------------------------------------------------------
 * EAS_MixEngineInit()
 *----------------------------------------------------------------------------
 * Purpose:
 * Prepares the mix engine for work, allocates buffers, locates effects modules, etc.
 *
 * Inputs:
 * pEASData         - instance data
 * pInstData        - pointer to variable to receive instance data handle
 *
 * Outputs:
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
EAS_RESULT EAS_MixEngineInit (S_EAS_DATA *pEASData)
{

    /* check Configuration Module for mix buffer allocation */
    if (pEASData->staticMemoryModel)
        pEASData->pMixBuffer = EAS_CMEnumData(EAS_CM_MIX_BUFFER);
    else
        pEASData->pMixBuffer = EAS_HWMalloc(pEASData->hwInstData, BUFFER_SIZE_IN_MONO_SAMPLES * NUM_OUTPUT_CHANNELS * sizeof(EAS_I32));
    if (pEASData->pMixBuffer == NULL)
    {
        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_FATAL, "Failed to allocate mix buffer memory\n"); */ }
        return EAS_ERROR_MALLOC_FAILED;
    }
    EAS_HWMemSet((void *)(pEASData->pMixBuffer), 0, BUFFER_SIZE_IN_MONO_SAMPLES * NUM_OUTPUT_CHANNELS * sizeof(EAS_I32));

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * EAS_MixEnginePrep()
 *----------------------------------------------------------------------------
 * Purpose:
 * Performs prep before synthesize a buffer of audio, such as clearing
 * audio buffers, etc.
 *
 * Inputs:
 * psEASData - pointer to overall EAS data structure
 *
 * Outputs:
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
void EAS_MixEnginePrep (S_EAS_DATA *pEASData, EAS_I32 numSamples)
{

    /* clear the mix buffer */
#if (NUM_OUTPUT_CHANNELS == 2)
    EAS_HWMemSet(pEASData->pMixBuffer, 0, numSamples * (EAS_I32) sizeof(EAS_I32) * 2);
#else
    EAS_HWMemSet(pEASData->pMixBuffer, 0, (EAS_I32) numSamples * (EAS_I32) sizeof(EAS_I32));
#endif

    /* need to clear other side-chain effect buffers (chorus & reverb) */
}

/*----------------------------------------------------------------------------
 * EAS_MixEnginePost
 *----------------------------------------------------------------------------
 * Purpose:
 * This routine does the post-processing after all voices have been
 * synthesized. It calls any sweeteners and does the final mixdown to
 * the output buffer.
 *
 * Inputs:
 *
 * Outputs:
 *
 * Notes:
 *----------------------------------------------------------------------------
*/
void EAS_MixEnginePost (S_EAS_DATA *pEASData, EAS_I32 numSamples)
{
    EAS_U16 gain;

//3 dls: Need to restore the mix engine metrics

    /* calculate the gain multiplier */
#ifdef _MAXIMIZER_ENABLED
    if (pEASData->effectsModules[EAS_MODULE_MAXIMIZER].effect)
    {
        EAS_I32 temp;
        temp = MaximizerProcess(pEASData->effectsModules[EAS_MODULE_MAXIMIZER].effectData, pEASData->pMixBuffer, pEASData->pMixBuffer, numSamples);
        temp = (temp * pEASData->masterGain) >> 15;
        if (temp > 32767)
            gain = 32767;
        else
            gain = (EAS_U16) temp;
    }
    else
        gain = (EAS_U16) pEASData->masterGain;
#else
    gain = (EAS_U16) pEASData->masterGain;
#endif

    /* Not using all the gain bits for now
     * Reduce the input to the compressor by 6dB to prevent saturation
     */
#ifdef _COMPRESSOR_ENABLED
    if (pEASData->effectsModules[EAS_MODULE_COMPRESSOR].effectData)
        gain = gain >> 5;
    else
        gain = gain >> 4;
#else
    gain = gain >> 4;
#endif

    /* convert 32-bit mix buffer to 16-bit output format */
#if (NUM_OUTPUT_CHANNELS == 2)
    SynthMasterGain(pEASData->pMixBuffer, pEASData->pOutputAudioBuffer, gain, (EAS_U16) ((EAS_U16) numSamples * 2));
#else
    SynthMasterGain(pEASData->pMixBuffer, pEASData->pOutputAudioBuffer, gain, (EAS_U16) numSamples);
#endif

#ifdef _ENHANCER_ENABLED
    /* enhancer effect */
    if (pEASData->effectsModules[EAS_MODULE_ENHANCER].effectData)
        (*pEASData->effectsModules[EAS_MODULE_ENHANCER].effect->pfProcess)
            (pEASData->effectsModules[EAS_MODULE_ENHANCER].effectData,
            pEASData->pOutputAudioBuffer,
            pEASData->pOutputAudioBuffer,
            numSamples);
#endif

#ifdef _GRAPHIC_EQ_ENABLED
    /* graphic EQ effect */
    if (pEASData->effectsModules[EAS_MODULE_GRAPHIC_EQ].effectData)
        (*pEASData->effectsModules[EAS_MODULE_GRAPHIC_EQ].effect->pfProcess)
            (pEASData->effectsModules[EAS_MODULE_GRAPHIC_EQ].effectData,
            pEASData->pOutputAudioBuffer,
            pEASData->pOutputAudioBuffer,
            numSamples);
#endif

#ifdef _COMPRESSOR_ENABLED
    /* compressor effect */
    if (pEASData->effectsModules[EAS_MODULE_COMPRESSOR].effectData)
        (*pEASData->effectsModules[EAS_MODULE_COMPRESSOR].effect->pfProcess)
            (pEASData->effectsModules[EAS_MODULE_COMPRESSOR].effectData,
            pEASData->pOutputAudioBuffer,
            pEASData->pOutputAudioBuffer,
            numSamples);
#endif

#ifdef _WOW_ENABLED
    /* WOW requires a 32-bit buffer, borrow the mix buffer and
     * pass it as the destination buffer
     */
    /*lint -e{740} temporarily passing a parameter through an existing I/F */
    if (pEASData->effectsModules[EAS_MODULE_WOW].effectData)
        (*pEASData->effectsModules[EAS_MODULE_WOW].effect->pfProcess)
            (pEASData->effectsModules[EAS_MODULE_WOW].effectData,
            pEASData->pOutputAudioBuffer,
            (EAS_PCM*) pEASData->pMixBuffer,
            numSamples);
#endif

#ifdef _TONECONTROLEQ_ENABLED
    /* ToneControlEQ effect */
    if (pEASData->effectsModules[EAS_MODULE_TONECONTROLEQ].effectData)
        (*pEASData->effectsModules[EAS_MODULE_TONECONTROLEQ].effect->pfProcess)
            (pEASData->effectsModules[EAS_MODULE_TONECONTROLEQ].effectData,
            pEASData->pOutputAudioBuffer,
            pEASData->pOutputAudioBuffer,
            numSamples);
#endif

#ifdef _REVERB_ENABLED
    /* Reverb effect */
    if (pEASData->effectsModules[EAS_MODULE_REVERB].effectData)
        (*pEASData->effectsModules[EAS_MODULE_REVERB].effect->pfProcess)
            (pEASData->effectsModules[EAS_MODULE_REVERB].effectData,
            pEASData->pOutputAudioBuffer,
            pEASData->pOutputAudioBuffer,
            numSamples);
#endif

#ifdef _CHORUS_ENABLED
    /* Chorus effect */
    if (pEASData->effectsModules[EAS_MODULE_CHORUS].effectData)
        (*pEASData->effectsModules[EAS_MODULE_CHORUS].effect->pfProcess)
            (pEASData->effectsModules[EAS_MODULE_CHORUS].effectData,
            pEASData->pOutputAudioBuffer,
            pEASData->pOutputAudioBuffer,
            numSamples);
#endif

}

#ifndef NATIVE_EAS_KERNEL
/*----------------------------------------------------------------------------
 * SynthMasterGain
 *----------------------------------------------------------------------------
 * Purpose:
 * Mixes down audio from 32-bit to 16-bit target buffer
 *
 * Inputs:
 *
 * Outputs:
 *
 *----------------------------------------------------------------------------
*/
void SynthMasterGain(EAS_I32 *pInputBuffer,
                     EAS_PCM *pOutputBuffer,
                     EAS_U16 nGain,
                     EAS_U16 numSamples)
{
    /* loop through the buffer */
    while (numSamples) {
        EAS_I32 s;

        numSamples--;
        /* read a sample from the input buffer and add some guard bits */
        s = *pInputBuffer++;

        /* add some guard bits */
        /*lint -e{704} <avoid divide for performance>*/
        s = s >> 7;

        /* apply master gain */
        s *= (EAS_I32) nGain;

        /* shift to lower 16-bits */
        /*lint -e{704} <avoid divide for performance>*/
        s = s >> 9;

        /* saturate */
        s = SATURATE(s);

        *pOutputBuffer++ = (EAS_PCM)s;
    }
}
#endif

/*----------------------------------------------------------------------------
 * EAS_MixEngineShutdown()
 *----------------------------------------------------------------------------
 * Purpose:
 * Shuts down effects modules and deallocates memory
 *
 * Inputs:
 * pEASData         - instance data
 * pInstData        - instance data handle
 *
 * Outputs:
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
EAS_RESULT EAS_MixEngineShutdown (S_EAS_DATA *pEASData)
{

    /* check Configuration Module for static memory allocation */
    if (!pEASData->staticMemoryModel && (pEASData->pMixBuffer != NULL))
        EAS_HWFree(pEASData->hwInstData, pEASData->pMixBuffer);

    return EAS_SUCCESS;
}

#ifdef UNIFIED_MIXER
#ifndef NATIVE_MIX_STREAM
/*----------------------------------------------------------------------------
 * EAS_MixStream
 *----------------------------------------------------------------------------
 * Mix a 16-bit stream into a 32-bit buffer
 *
 * pInputBuffer 16-bit input buffer
 * pMixBuffer   32-bit mix buffer
 * numSamples   number of samples to mix
 * gainLeft     initial gain left or mono
 * gainRight    initial gain right
 * gainLeft     left gain increment per sample
 * gainRight    right gain increment per sample
 * flags        bit 0 = stereo source
 *              bit 1 = stereo output
 *----------------------------------------------------------------------------
*/
void EAS_MixStream (EAS_PCM *pInputBuffer, EAS_I32 *pMixBuffer, EAS_I32 numSamples, EAS_I32 gainLeft, EAS_I32 gainRight, EAS_I32 gainIncLeft, EAS_I32 gainIncRight, EAS_I32 flags)
{
    EAS_I32 temp;
    EAS_INT src, dest;

    /* NOTE: There are a lot of optimizations that can be done
     * in the native implementations based on register
     * availability, etc. For example, it may make sense to
     * break this down into 8 separate routines:
     *
     * 1. Mono source to mono output
     * 2. Mono source to stereo output
     * 3. Stereo source to mono output
     * 4. Stereo source to stereo output
     * 5. Mono source to mono output - no gain change
     * 6. Mono source to stereo output - no gain change
     * 7. Stereo source to mono output - no gain change
     * 8. Stereo source to stereo output - no gain change
     *
     * Other possibilities include loop unrolling, skipping
     * a gain calculation every 2 or 4 samples, etc.
     */

    /* no gain change, use fast loops */
    if ((gainIncLeft == 0) && (gainIncRight == 0))
    {
        switch (flags & (MIX_FLAGS_STEREO_SOURCE | MIX_FLAGS_STEREO_OUTPUT))
        {
            /* mono to mono */
            case 0:
                gainLeft >>= 15;
                for (src = dest = 0; src < numSamples; src++, dest++)
                {

                    pMixBuffer[dest] += (pInputBuffer[src] * gainLeft) >> NUM_MIXER_GUARD_BITS;
                }
                break;

            /* mono to stereo */
            case MIX_FLAGS_STEREO_OUTPUT:
                gainLeft >>= 15;
                gainRight >>= 15;
                for (src = dest = 0; src < numSamples; src++, dest+=2)
                {
                    pMixBuffer[dest] += (pInputBuffer[src] * gainLeft) >> NUM_MIXER_GUARD_BITS;
                    pMixBuffer[dest+1] += (pInputBuffer[src] * gainRight) >> NUM_MIXER_GUARD_BITS;
                }
                break;

            /* stereo to mono */
            case MIX_FLAGS_STEREO_SOURCE:
                gainLeft >>= 15;
                gainRight >>= 15;
                for (src = dest = 0; src < numSamples; src+=2, dest++)
                {
                    temp = (pInputBuffer[src] * gainLeft) >> NUM_MIXER_GUARD_BITS;
                    temp += ((pInputBuffer[src+1] * gainRight) >> NUM_MIXER_GUARD_BITS);
                    pMixBuffer[dest] += temp;
                }
                break;

            /* stereo to stereo */
            case MIX_FLAGS_STEREO_SOURCE | MIX_FLAGS_STEREO_OUTPUT:
                gainLeft >>= 15;
                gainRight >>= 15;
                for (src = dest = 0; src < numSamples; src+=2, dest+=2)
                {
                    pMixBuffer[dest] += (pInputBuffer[src] * gainLeft) >> NUM_MIXER_GUARD_BITS;
                    pMixBuffer[dest+1] += (pInputBuffer[src+1] * gainRight) >> NUM_MIXER_GUARD_BITS;
                }
                break;
        }
    }

    /* gain change - do gain increment */
    else
    {
        switch (flags & (MIX_FLAGS_STEREO_SOURCE | MIX_FLAGS_STEREO_OUTPUT))
        {
            /* mono to mono */
            case 0:
                for (src = dest = 0; src < numSamples; src++, dest++)
                {
                    gainLeft += gainIncLeft;
                    pMixBuffer[dest] += (pInputBuffer[src] * (gainLeft >> 15)) >> NUM_MIXER_GUARD_BITS;
                }
                break;

            /* mono to stereo */
            case MIX_FLAGS_STEREO_OUTPUT:
                for (src = dest = 0; src < numSamples; src++, dest+=2)
                {
                    gainLeft += gainIncLeft;
                    gainRight += gainIncRight;
                    pMixBuffer[dest] += (pInputBuffer[src] * (gainLeft >> 15)) >> NUM_MIXER_GUARD_BITS;
                    pMixBuffer[dest+1] += (pInputBuffer[src] * (gainRight >> 15)) >> NUM_MIXER_GUARD_BITS;
                }
                break;

            /* stereo to mono */
            case MIX_FLAGS_STEREO_SOURCE:
                for (src = dest = 0; src < numSamples; src+=2, dest++)
                {
                    gainLeft += gainIncLeft;
                    gainRight += gainIncRight;
                    temp = (pInputBuffer[src] * (gainLeft >> 15)) >> NUM_MIXER_GUARD_BITS;
                    temp += ((pInputBuffer[src+1] * (gainRight >> 15)) >> NUM_MIXER_GUARD_BITS);
                    pMixBuffer[dest] += temp;
                }
                break;

            /* stereo to stereo */
            case MIX_FLAGS_STEREO_SOURCE | MIX_FLAGS_STEREO_OUTPUT:
                for (src = dest = 0; src < numSamples; src+=2, dest+=2)
                {
                    gainLeft += gainIncLeft;
                    gainRight += gainIncRight;
                    pMixBuffer[dest] += (pInputBuffer[src] * (gainLeft >> 15)) >> NUM_MIXER_GUARD_BITS;
                    pMixBuffer[dest+1] += (pInputBuffer[src+1] * (gainRight >> 15)) >> NUM_MIXER_GUARD_BITS;
                }
                break;
        }
    }
}
#endif
#endif

