/*
 ** Copyright 2003-2010, VisualOn, Inc.
 **
 ** 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.
 */
/*******************************************************************************
	File:		block_switch.c

	Content:	Block switching functions

*******************************************************************************/

#include "typedef.h"
#include "basic_op.h"
#include "oper_32b.h"
#include "psy_const.h"
#include "block_switch.h"


#define ENERGY_SHIFT (8 - 1)

/**************** internal function prototypes ***********/
static Word32
SrchMaxWithIndex(const Word32 *in, Word16 *index, Word16 n);


Word32
CalcWindowEnergy(BLOCK_SWITCHING_CONTROL *blockSwitchingControl,
                 Word16 *timeSignal,
                 Word16 chIncrement,
                 Word16 windowLen);



/****************** Constants *****************************/


/*
  IIR high pass coeffs
*/
const Word32 hiPassCoeff[BLOCK_SWITCHING_IIR_LEN] = {
  0xbec8b439, 0x609d4952  /* -0.5095f, 0.7548f */
};

static const Word32 accWindowNrgFac = 0x26666666;                   /* factor for accumulating filtered window energies 0.3 */
static const Word32 oneMinusAccWindowNrgFac = 0x5999999a;			/* 0.7 */
static const Word32 invAttackRatioHighBr = 0x0ccccccd;              /* inverted lower ratio limit for attacks 0.1*/
static const Word32 invAttackRatioLowBr =  0x072b020c;              /* 0.056 */
static const Word32 minAttackNrg = 0x00001e84;                      /* minimum energy for attacks 1e+6 */


/****************** Routines ****************************/


/*****************************************************************************
*
* function name: InitBlockSwitching
* description:  init Block Switching parameter.
* returns:      TRUE if success
*
**********************************************************************************/
Word16 InitBlockSwitching(BLOCK_SWITCHING_CONTROL *blockSwitchingControl,
                          const Word32 bitRate, const Word16 nChannels)
{
  /* select attackRatio */

  if ((sub(nChannels,1)==0 && L_sub(bitRate, 24000) > 0) ||
      (sub(nChannels,1)>0 && bitRate > (nChannels * 16000))) {
    blockSwitchingControl->invAttackRatio = invAttackRatioHighBr;
  }
  else  {
    blockSwitchingControl->invAttackRatio = invAttackRatioLowBr;
  }

  return(TRUE);
}

static Word16 suggestedGroupingTable[TRANS_FAC][MAX_NO_OF_GROUPS] = {
  /* Attack in Window 0 */ {1,  3,  3,  1},
  /* Attack in Window 1 */ {1,  1,  3,  3},
  /* Attack in Window 2 */ {2,  1,  3,  2},
  /* Attack in Window 3 */ {3,  1,  3,  1},
  /* Attack in Window 4 */ {3,  1,  1,  3},
  /* Attack in Window 5 */ {3,  2,  1,  2},
  /* Attack in Window 6 */ {3,  3,  1,  1},
  /* Attack in Window 7 */ {3,  3,  1,  1}
};

/*****************************************************************************
*
* function name: BlockSwitching
* description:  detect this frame whether there is an attack
* returns:      TRUE if success
*
**********************************************************************************/
Word16 BlockSwitching(BLOCK_SWITCHING_CONTROL *blockSwitchingControl,
                      Word16 *timeSignal,
					  Word32 sampleRate,
                      Word16 chIncrement)
{
  Word32 i, w;
  Word32 enM1, enMax;

  /* Reset grouping info */
  for (i=0; i<TRANS_FAC; i++) {
    blockSwitchingControl->groupLen[i] = 0;
  }


  /* Search for position and amplitude of attack in last frame (1 windows delay) */
  blockSwitchingControl->maxWindowNrg = SrchMaxWithIndex( &blockSwitchingControl->windowNrg[0][BLOCK_SWITCH_WINDOWS-1],
                                                          &blockSwitchingControl->attackIndex,
                                                          BLOCK_SWITCH_WINDOWS);

  blockSwitchingControl->attackIndex = blockSwitchingControl->lastAttackIndex;

  /* Set grouping info */
  blockSwitchingControl->noOfGroups = MAX_NO_OF_GROUPS;

  for (i=0; i<MAX_NO_OF_GROUPS; i++) {
    blockSwitchingControl->groupLen[i] = suggestedGroupingTable[blockSwitchingControl->attackIndex][i];
  }

  /* if the samplerate is less than 16000, it should be all the short block, avoid pre&post echo */
  if(sampleRate >= 16000) {
	  /* Save current window energy as last window energy */
	  for (w=0; w<BLOCK_SWITCH_WINDOWS; w++) {
		  blockSwitchingControl->windowNrg[0][w] = blockSwitchingControl->windowNrg[1][w];
		  blockSwitchingControl->windowNrgF[0][w] = blockSwitchingControl->windowNrgF[1][w];
	  }


	  /* Calculate unfiltered and filtered energies in subwindows and combine to segments */
	  CalcWindowEnergy(blockSwitchingControl, timeSignal, chIncrement, BLOCK_SWITCH_WINDOW_LEN);

	  /* reset attack */
	  blockSwitchingControl->attack = FALSE;

	  enMax = 0;
	  enM1 = blockSwitchingControl->windowNrgF[0][BLOCK_SWITCH_WINDOWS-1];

	  for (w=0; w<BLOCK_SWITCH_WINDOWS; w++) {
		  Word32 enM1_Tmp, accWindowNrg_Tmp, windowNrgF_Tmp;
		  Word16 enM1_Shf, accWindowNrg_Shf, windowNrgF_Shf;

		  accWindowNrg_Shf = norm_l(blockSwitchingControl->accWindowNrg);
		  enM1_Shf = norm_l(enM1);
		  windowNrgF_Shf = norm_l(blockSwitchingControl->windowNrgF[1][w]);

		  accWindowNrg_Tmp = blockSwitchingControl->accWindowNrg << accWindowNrg_Shf;
		  enM1_Tmp = enM1 << enM1_Shf;
		  windowNrgF_Tmp = blockSwitchingControl->windowNrgF[1][w] << windowNrgF_Shf;

		  /* a sliding average of the previous energies */
		  blockSwitchingControl->accWindowNrg = (fixmul(oneMinusAccWindowNrgFac, accWindowNrg_Tmp) >> accWindowNrg_Shf) +
			  (fixmul(accWindowNrgFac, enM1_Tmp) >> enM1_Shf);


		  /* if the energy with the ratio is bigger than the average, and the attack and short block  */
		  if ((fixmul(windowNrgF_Tmp, blockSwitchingControl->invAttackRatio) >> windowNrgF_Shf) >
			  blockSwitchingControl->accWindowNrg ) {
				  blockSwitchingControl->attack = TRUE;
				  blockSwitchingControl->lastAttackIndex = w;
		  }
		  enM1 = blockSwitchingControl->windowNrgF[1][w];
		  enMax = max(enMax, enM1);
	  }

	  if (enMax < minAttackNrg) {
		  blockSwitchingControl->attack = FALSE;
	  }
  }
  else
  {
	  blockSwitchingControl->attack = TRUE;
  }

  /* Check if attack spreads over frame border */
  if ((!blockSwitchingControl->attack) && (blockSwitchingControl->lastattack)) {

    if (blockSwitchingControl->attackIndex == TRANS_FAC-1) {
      blockSwitchingControl->attack = TRUE;
    }

    blockSwitchingControl->lastattack = FALSE;
  }
  else {
    blockSwitchingControl->lastattack = blockSwitchingControl->attack;
  }

  blockSwitchingControl->windowSequence =  blockSwitchingControl->nextwindowSequence;


  if (blockSwitchingControl->attack) {
    blockSwitchingControl->nextwindowSequence = SHORT_WINDOW;
  }
  else {
    blockSwitchingControl->nextwindowSequence = LONG_WINDOW;
  }

  /* update short block group */
  if (blockSwitchingControl->nextwindowSequence == SHORT_WINDOW) {

    if (blockSwitchingControl->windowSequence== LONG_WINDOW) {
      blockSwitchingControl->windowSequence = START_WINDOW;
    }

    if (blockSwitchingControl->windowSequence == STOP_WINDOW) {
      blockSwitchingControl->windowSequence = SHORT_WINDOW;
      blockSwitchingControl->noOfGroups = 3;
      blockSwitchingControl->groupLen[0] = 3;
      blockSwitchingControl->groupLen[1] = 3;
      blockSwitchingControl->groupLen[2] = 2;
    }
  }

  /* update block type */
  if (blockSwitchingControl->nextwindowSequence == LONG_WINDOW) {

    if (blockSwitchingControl->windowSequence == SHORT_WINDOW) {
      blockSwitchingControl->nextwindowSequence = STOP_WINDOW;
    }
  }

  return(TRUE);
}


/*****************************************************************************
*
* function name: SrchMaxWithIndex
* description:  search for the biggest value in an array
* returns:      the max value
*
**********************************************************************************/
static Word32 SrchMaxWithIndex(const Word32 in[], Word16 *index, Word16 n)
{
  Word32 max;
  Word32 i, idx;

  /* Search maximum value in array and return index and value */
  max = 0;
  idx = 0;

  for (i = 0; i < n; i++) {

    if (in[i+1]  > max) {
      max = in[i+1];
      idx = i;
    }
  }
  *index = idx;

  return(max);
}

/*****************************************************************************
*
* function name: CalcWindowEnergy
* description:  calculate the energy before iir-filter and after irr-filter
* returns:      TRUE if success
*
**********************************************************************************/
#ifndef ARMV5E
Word32 CalcWindowEnergy(BLOCK_SWITCHING_CONTROL *blockSwitchingControl,
                        Word16 *timeSignal,
                        Word16 chIncrement,
                        Word16 windowLen)
{
  Word32 w, i, tidx;
  Word32 accuUE, accuFE;
  Word32 tempUnfiltered;
  Word32 tempFiltered;
  Word32 states0, states1;
  Word32 Coeff0, Coeff1;


  states0 = blockSwitchingControl->iirStates[0];
  states1 = blockSwitchingControl->iirStates[1];
  Coeff0 = hiPassCoeff[0];
  Coeff1 = hiPassCoeff[1];
  tidx = 0;
  for (w=0; w < BLOCK_SWITCH_WINDOWS; w++) {

    accuUE = 0;
    accuFE = 0;

    for(i=0; i<windowLen; i++) {
	  Word32 accu1, accu2, accu3;
	  Word32 out;
	  tempUnfiltered = timeSignal[tidx];
      tidx = tidx + chIncrement;

	  accu1 = L_mpy_ls(Coeff1, tempUnfiltered);
	  accu2 = fixmul( Coeff0, states1 );
	  accu3 = accu1 - states0;
	  out = accu3 - accu2;

	  states0 = accu1;
	  states1 = out;

      tempFiltered = extract_h(out);
      accuUE += (tempUnfiltered * tempUnfiltered) >> ENERGY_SHIFT;
      accuFE += (tempFiltered * tempFiltered) >> ENERGY_SHIFT;
    }

    blockSwitchingControl->windowNrg[1][w] = accuUE;
    blockSwitchingControl->windowNrgF[1][w] = accuFE;

  }

  blockSwitchingControl->iirStates[0] = states0;
  blockSwitchingControl->iirStates[1] = states1;

  return(TRUE);
}
#endif

static Word16 synchronizedBlockTypeTable[4][4] = {
  /*                 LONG_WINDOW   START_WINDOW  SHORT_WINDOW  STOP_WINDOW */
  /* LONG_WINDOW  */{LONG_WINDOW,  START_WINDOW, SHORT_WINDOW, STOP_WINDOW},
  /* START_WINDOW */{START_WINDOW, START_WINDOW, SHORT_WINDOW, SHORT_WINDOW},
  /* SHORT_WINDOW */{SHORT_WINDOW, SHORT_WINDOW, SHORT_WINDOW, SHORT_WINDOW},
  /* STOP_WINDOW  */{STOP_WINDOW,  SHORT_WINDOW, SHORT_WINDOW, STOP_WINDOW}
};


/*****************************************************************************
*
* function name: SyncBlockSwitching
* description:  update block type and group value
* returns:      TRUE if success
*
**********************************************************************************/
Word16 SyncBlockSwitching(BLOCK_SWITCHING_CONTROL *blockSwitchingControlLeft,
                          BLOCK_SWITCHING_CONTROL *blockSwitchingControlRight,
                          const Word16 nChannels)
{
  Word16 i;
  Word16 patchType = LONG_WINDOW;


  if (nChannels == 1) { /* Mono */
    if (blockSwitchingControlLeft->windowSequence != SHORT_WINDOW) {
      blockSwitchingControlLeft->noOfGroups = 1;
      blockSwitchingControlLeft->groupLen[0] = 1;

      for (i=1; i<TRANS_FAC; i++) {
        blockSwitchingControlLeft->groupLen[i] = 0;
      }
    }
  }
  else { /* Stereo common Window */
    patchType = synchronizedBlockTypeTable[patchType][blockSwitchingControlLeft->windowSequence];
    patchType = synchronizedBlockTypeTable[patchType][blockSwitchingControlRight->windowSequence];

    /* Set synchronized Blocktype */
    blockSwitchingControlLeft->windowSequence = patchType;
    blockSwitchingControlRight->windowSequence = patchType;

    /* Synchronize grouping info */
    if(patchType != SHORT_WINDOW) { /* Long Blocks */
      /* Set grouping info */
      blockSwitchingControlLeft->noOfGroups = 1;
      blockSwitchingControlRight->noOfGroups = 1;
      blockSwitchingControlLeft->groupLen[0] = 1;
      blockSwitchingControlRight->groupLen[0] = 1;

      for (i=1; i<TRANS_FAC; i++) {
        blockSwitchingControlLeft->groupLen[i] = 0;
        blockSwitchingControlRight->groupLen[i] = 0;
      }
    }
    else {

      if (blockSwitchingControlLeft->maxWindowNrg > blockSwitchingControlRight->maxWindowNrg) {
        /* Left Channel wins */
        blockSwitchingControlRight->noOfGroups = blockSwitchingControlLeft->noOfGroups;
        for (i=0; i<TRANS_FAC; i++) {
          blockSwitchingControlRight->groupLen[i] = blockSwitchingControlLeft->groupLen[i];
        }
      }
      else {
        /* Right Channel wins */
        blockSwitchingControlLeft->noOfGroups = blockSwitchingControlRight->noOfGroups;
        for (i=0; i<TRANS_FAC; i++) {
          blockSwitchingControlLeft->groupLen[i] = blockSwitchingControlRight->groupLen[i];
        }
      }
    }
  } /*endif Mono or Stereo */

  return(TRUE);
}
