/*
 * Rpe port to Java.
 * Copyright (C) 1999  Christopher Edwards
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package	org.tritonus.lowlevel.gsm;



public class Rpe
{
	private short 	exp_in;      /* IN   */
	private short 	mant_in;     /* IN   */
	private short 	exp_out;     /* OUT  */
	private short 	mant_out;    /* OUT  */
	private int 	xMp_point = 0;

	private static final int ENCODE = 0;
	private static final int DECODE = 1;
        
	private short[] x = new short[40];	/* signal [0..39]         OUT */   

	public void Gsm_RPE_Encoding(
		short[]	e,	/* -5..-1][0..39][40..44            IN/OUT  */
		short[]	xmaxc,	/* [0..3] Coded maximum amplitude   OUT     */
		short[]	Mc,	/* [0..3] RPE grid selection        OUT     */
		int	xmaxc_Mc_index,	/* Ref. point for xmaxc and Mc            */
		short[]	xMc,	/* [0..12]                          OUT     */
		int	xMc_index	/* Ref. for xmc, '+=13' */ )
	{
		short[]	xM  = new short[13]; 
		short[]	xMp = new short[13];

		Weighting_filter( e );  /* Sets up the private data member 'x[40]' */
		RPE_grid_selection( xM, Mc, xmaxc_Mc_index );    /* Sets up xM[13] */

		/* Sets up xmc (13 array locations starting from xMc_index),
		 * xmaxc (one array location at xmaxc_Mc_index), 
		 * exp_in and mant_in 
		 */
		APCM_quantization( xM, xMc, xMc_index, xmaxc, xmaxc_Mc_index );
		/* Sets up xMp[13] */
		APCM_inverse_quantization( xMc, xMp, xMc_index, ENCODE );

		RPE_grid_positioning( Mc[xmaxc_Mc_index], xMp, e, ENCODE );
	}



	/*
	 *  The coefficients of the weighting filter are stored in a table
	 *  (see table 4.4).  The following scaling is used:
	 *
	 *      H[0..10] = integer( real_H[ 0..10] * 8192 );
	 */
	private void Weighting_filter(
		short[] e)	/* signal [-5..0.39.44] IN  */ 
	{
		int     L_result = 0;
		int     i = 0;

		/*
		 *  (e[-5..-1] and e[40..44] are allocated by the caller,
		 *  are initially zero and are not written anywhere.)
		 *
		 *  e -= 5;
		 * 
		 *  The above case is true with the C code, although java
		 *  does not have pointers so e[0..49] is all set for this 
		 *  method.
		 */

		/*  Compute the signal x[0..39]
		 */
		for (int k = 0; k <= 39; k++)
		{
			L_result = 8192 >> 1;

			/*  Every one of these multiplications is done twice --
			 *  but I don't see an elegant way to optimize this.
			 *  Do you?
			 */

		        /* #define STEP( i, H )    (e[ k + i ] * H) */
			i = 0;
                
			L_result +=( e[ k + 0 ] * -134 )
				+ ( e[ k + 1 ] * -374 )
				/* + STEP( 2,      0    ) no sense in adding zero */
				+ ( e[ k + 3 ] * 2054 )
				+ ( e[ k + 4 ] * 5741 )
				+ ( e[ k + 5 ] * 8192 )
				+ ( e[ k + 6 ] * 5741 )
				+ ( e[ k + 7 ] * 2054 )
				/* + STEP( 8,      0    ) no sense in adding zero */
				+ ( e[ k + 9 ] * -374 )
				+ ( e[ k + 10 ] * -134 );

			/* 2 adds vs. >>16 => 14, minus one shift to compensate for
			 * those we lost when replacing L_MULT by '*'.
			 */
 
			L_result = Add.SASR( L_result, 13 );
			x[k] = (short) ((L_result < Gsm_Def.MIN_WORD ? Gsm_Def.MIN_WORD
					 : (L_result > Gsm_Def.MAX_WORD ? Gsm_Def.MAX_WORD 
					    : L_result )));
		}
	}



	/*
	 *  The signal x[0..39] is used to select the RPE grid which is
	 *  represented by Mc.
	 */
	private void RPE_grid_selection(
		short[]    xM,           /* [0..12]              OUT */
		short[]    Mc_out,       /*                      OUT */   
		int	   Mc_index)
	{
		int     m = 0;
		int     L_result = 0;
		int     EM = 0;     /* xxx should be L_EM? */
		short   Mc = 0;

		int     L_common_0_3;

		/* common part of 0 and 3 */

		L_result = 0;
        
		L_result += STEP( 0, 1 ) +  STEP( 0, 2 ) +  STEP( 0, 3 ) +  STEP( 0, 4 ) 
			+  STEP( 0, 5 ) +  STEP( 0, 6 ) +  STEP( 0, 7 ) +  STEP( 0, 8 ) 
			+  STEP( 0, 9 ) +  STEP( 0, 10) +  STEP( 0, 11) +  STEP( 0, 12);
                 
		L_common_0_3 = L_result;

		/* i = 0 */
		L_result += STEP( 0, 0 );
		L_result <<= 1; /* implicit in L_MULT */
		EM = L_result;

		/* i = 1 */
		L_result = 0;
		L_result += STEP( 1, 0 ) + STEP( 1, 1 ) + STEP( 1, 2 ) + STEP( 1, 3 )
			+  STEP( 1, 4 ) + STEP( 1, 5 ) + STEP( 1, 6 ) + STEP( 1, 7 )
			+  STEP( 1, 8 ) + STEP( 1, 9 ) + STEP( 1, 10) + STEP( 1, 11)
			+  STEP( 1, 12);
        
		L_result <<= 1;
		if (L_result > EM) {
			Mc = 1;
			EM = L_result;
		}

		/* i = 2 */
		L_result = 0;
		L_result += STEP( 2, 0 ) + STEP( 2, 1 ) + STEP( 2, 2 ) + STEP( 2, 3 )
			+  STEP( 2, 4 ) + STEP( 2, 5 ) + STEP( 2, 6 ) + STEP( 2, 7 )
			+  STEP( 2, 8 ) + STEP( 2, 9 ) + STEP( 2, 10) + STEP( 2, 11)
			+  STEP( 2, 12);
       
		L_result <<= 1;
		if (L_result > EM) {
			Mc = 2;
			EM = L_result;
		}

		/* i = 3 */
		L_result = L_common_0_3;
		L_result += STEP( 3, 12 );
		L_result <<= 1;
		if (L_result > EM)
		{
			Mc = 3;
			EM = L_result;
		}

		/*  Down-sampling by a factor 3 to get the selected xM[0..12]
		 *  RPE sequence.
		 */
		for (int i = 0; i <= 12; i ++)
		{
			xM[i] = x[Mc + 3*i];
		}	    
		Mc_out[Mc_index] = Mc;
	}



	private int STEP(int m, int i )
	{
		int L_temp;
		L_temp = Add.SASR( x[m + 3 * i], 2 );       
		return ( L_temp * L_temp );
	}
 


	private void APCM_quantization (
		short[]         xM,           /* [0..12]              IN      */
		short[]         xMc,          /* [0..12]              OUT     */
		int		xMc_index,
		short[]         xmaxc_out,    /*                      OUT     */
		int 		xmaxc_index)
		throws IllegalArgumentException {

		int     itest = 0;
		short   xmax = 0, xmaxc = 0, temp = 0, temp1 = 0, temp2 = 0;
		short   exp = 0, mant = 0;

		/*  Find the maximum absolute value xmax of xM[0..12].
		 */
		for (int i = 0; i <= 12; i++)
		{
			temp = xM[i];
			temp = Add.GSM_ABS(temp);
			if (temp > xmax)
			{
				xmax = temp;
			}
		}

		/*  Qantizing and coding of xmax to get xmaxc.
		 */
		exp   = 0;
		temp  = Add.SASR( xmax, 9 );
		itest = 0;

		for (int i = 0; i <= 5; i++)
		{
			if(temp <= 0)
			{
				itest |= 1;
			}
			else
			{
				itest |= 0;
			}
			temp = Add.SASR( temp, 1 );

			if( ! (exp <= 5))
			{
				throw new IllegalArgumentException
					("APCM_quantization: exp = "
					 +exp+" is out of range. Should be <= 5");
			}

			if (itest == 0)
			{
				exp++;          /* exp = add (exp, 1) */
			}
		}

		if( ! (exp <= 6 && exp >= 0))
		{
			throw new IllegalArgumentException
				("APCM_quantization: exp = "
				 +exp+" is out of range. Should be >= -4 and <= 6");
		}

		temp = (short) (exp + 5);

		if( ! (temp <= 11 && temp >= 0))
		{
			throw new IllegalArgumentException
				("APCM_quantization: temp = "
				 +temp+" is out of range. Should be >= 0 and <= 11");
		}

		xmaxc = Add.GSM_ADD( Add.SASR(xmax, temp), (short) (exp << 3) );

		/*   Quantizing and coding of the xM[0..12] RPE sequence
		 *   to get the xMc[0..12]
		 */
        
		APCM_quantization_xmaxc_to_exp_mant( xmaxc, ENCODE );
		exp = exp_in;
		mant = mant_in;
        
		/*  This computation uses the fact that the decoded version of xmaxc
		 *  can be calculated by using the exponent and the mantissa part of
		 *  xmaxc (logarithmic table).
		 *  So, this method avoids any division and uses only a scaling
		 *  of the RPE samples by a function of the exponent.  A direct
		 *  multiplication by the inverse of the mantissa (NRFAC[0..7]
		 *  found in table 4.5) gives the 3 bit coded version xMc[0..12]
		 *  of the RPE samples.
		 */

		/* Direct computation of xMc[0..12] using table 4.5
		 */

		if( ! (exp <= 4096 && exp >= -4096))
		{
			throw new IllegalArgumentException
				("APCM_quantization: exp = "
				 +exp+" is out of range. Should be >= -4096 and <= 4096");
		}
		if( ! (mant >= 0 && mant <= 7 ))
		{
			throw new IllegalArgumentException
				("APCM_quantization: mant = "
				 +mant+" is out of range. Should be >= 0 and <= 7");
		}

		temp1 = (short) (6 - exp);          /* normalization by the exponent */
		temp2 = Gsm_Def.gsm_NRFAC[ mant ];  /* inverse mantissa          */

		for (int i = 0; i <= 12; i++)
		{
			if( ! (temp1 >= 0 && temp1 < 16))
			{
				throw new IllegalArgumentException
					("APCM_quantization: temp = "
					 +temp+" is out of range. Should be >= 0 and < 16");
			}

			temp = (short) (xM[i] << temp1);
			temp = Add.GSM_MULT( temp, temp2 );
			temp = Add.SASR(temp, 12);
			xMc[i + xMc_index] = (short) (temp + 4);    /* see note below */
		}

		/*  NOTE: This equation is used to make all the xMc[i] positive.
		 */
		mant_in   = mant;
		exp_in    = exp;
		xmaxc_out[xmaxc_index] = xmaxc;
	}


	public void APCM_quantization_xmaxc_to_exp_mant(
		short xmaxc_elem,
		int METHOD_ID)
		throws IllegalArgumentException
	{
		short    exp = 0, mant = 0;
		/* Compute exponent and mantissa of the decoded version of xmaxc
		 */

		if (xmaxc_elem > 15)
		{
			exp = (short) (Add.SASR(xmaxc_elem, 3) - 1);
		}
		mant = (short) (xmaxc_elem - (exp << 3));

		if (mant == 0)
		{
			exp = (short) -4;
			mant = (short) 7;
		}
		else
		{
			while (mant <= 7)
			{
				mant = (short) (mant << 1 | 1);
				exp--;
			}
			mant -= (short) 8;
		}
		if( exp < -4 || exp > 6 )
		{
			throw new IllegalArgumentException
				("APCM_quantization_xmaxc_to_exp_mant: exp = "
				 +exp+" is out of range. Should be >= -4 and <= 6");
		}
		if( mant < 0 || mant > 7 )
		{
			throw new IllegalArgumentException
				("APCM_quantization_xmaxc_to_exp_mant: mant = "
				 +mant+" is out of range. Should be >= 0 and <= 7");
		}
		if ( METHOD_ID == ENCODE )
		{
			exp_in  = exp;
			mant_in = mant;
		}
		else
		{ /* DECODE */
			exp_out  = exp;
			mant_out = mant;
		}
	}



	public void Gsm_RPE_Decoding_java(
		short xmaxc_elem,  /* From Gsm_Decoder xmaxc short array */
		short Mc_elem,     /* From Gsm_Decoder Mc short array */
		int	xmc_start,	/* Starting point for the three bit part of xmc */
		short[] xmc,       /* [0..12], 3 bits     IN      */
		short[] erp        /* [0..39]             OUT     */  )
	{
		short    xMp[] = new short[13];

		/* exp_out and mant_out are modified in this method */
		APCM_quantization_xmaxc_to_exp_mant(xmaxc_elem, DECODE);

		APCM_inverse_quantization(xmc, xMp, xmc_start, DECODE);

		RPE_grid_positioning(Mc_elem, xMp, erp, DECODE);
	}



	/*
	 *  This part is for decoding the RPE sequence of coded xMc[0..12]
	 *  samples to obtain the xMp[0..12] array.  Table 4.6 is used to get
	 *  the mantissa of xmaxc (FAC[0..7]).
	 */
	public void APCM_inverse_quantization(
		short[] xmc,   /* [0..12]                      IN      */
		short[] xMp,   /* [0..12]                      OUT     */
		int 	xmc_start,
		int 	METHOD_ID)
		throws IllegalArgumentException
	{
		short      temp, temp1, temp2, temp3;

		if ( METHOD_ID == ENCODE )
		{
			temp1 = Gsm_Def.gsm_FAC[ mant_in ];
			temp2 = Add.GSM_SUB( (short)6, exp_in );
		}
		else
		{ /* DECODE */
			temp1 = Gsm_Def.gsm_FAC[ mant_out ];   
			temp2 = Add.GSM_SUB( (short)6, exp_out ); 
		}
		temp3 = Add.gsm_asl( (short)1, Add.GSM_SUB( temp2, (short)1 ));
        
		xMp_point = 0;
        
		for (int i = 0; i < 13; i++)
		{
			/* restore sign   */
			temp = (short) ((xmc[xmc_start++] << 1) - 7);  
			if( ! (temp <= 7 && temp >= -7) )
			{    /* 4 bit signed   */
				throw new IllegalArgumentException
					("APCM_inverse_quantization: temp = "
					 +temp+" is out of range. Should be >= -7 and <= 7");
			}
			temp <<= 12;                        /* 16 bit signed  */
			temp = Add.GSM_MULT_R( temp1, temp );
			temp = Add.GSM_ADD( temp, temp3 );
			xMp[ xMp_point++ ] = Add.gsm_asr( temp, temp2 );
		}
	}



	/*
	 *  This method computes the reconstructed long term residual signal
	 *  ep[0..39] for the LTP analysis filter.  The inputs are the Mc
	 *  which is the grid position selection and the xMp[0..12] decoded
	 *  RPE samples which are upsampled by a factor of 3 by inserting zero
	 *  values.
	 */
	public static void RPE_grid_positioning(
		short   Mc,            /* grid position        IN      */
		short[] xMp,           /* [0..12]              IN      */
		short[] ep,            /* [0..39]              OUT     */
		int     METHOD_ID)
		throws IllegalArgumentException
	{
    		int     i = 13;	     
		int     xMp_index = 0;
		int 	ep_index;
        
		if (METHOD_ID == ENCODE)
		{
			ep_index = 5;
		}
		else
		{ /* Decode */
			ep_index = 0;
		}
        
		if( ! (0 <= Mc && Mc <= 3) )
		{
			throw new IllegalArgumentException
				("RPE_grid_positioning: Mc = "
				 +Mc+" is out of range. Should be >= 0 and <= 3");
		}

		switch (Mc)
		{
                case 3:
			ep[ep_index++] = 0;
		        do
			{
				ep[ep_index++] = 0;
				ep[ep_index++] = 0;
				ep[ep_index++] = xMp[xMp_index++];
				--i;
                        }
			while (i != 0);
			break;

                case 2:
			do
			{
				ep[ep_index++] = 0;
				ep[ep_index++] = 0;
				ep[ep_index++] = xMp[xMp_index++];
				--i;
			}
			while (i != 0);
			break;

                case 1:
			do
			{
				ep[ep_index++] = 0;
				ep[ep_index++] = xMp[xMp_index++];
				ep[ep_index++] = 0;
				--i;
			}
			while (i != 0);
			break;

                case 0:
			do
			{
				ep[ep_index++] = xMp[xMp_index++];
				ep[ep_index++] = 0;
				ep[ep_index++] = 0;
				--i;
			}
			while (i != 0);
			break;
		}

		if (METHOD_ID == ENCODE)
		{
			ep[ep_index++] = 0;
		}
	}
}
