package net.sf.fmj.media.codec.audio.ulaw;

import com.lti.utils.UnsignedUtils;



/** 
 * Turns 16-bit linear PCM values into 8-bit mu-law bytes.
 * Adapted from code by Marc Sweetgall at http://www.codeproject.com/csharp/g711audio.asp
 */
public class MuLawEncoderUtil
{
    public static final int BIAS = 0x84; //aka 132, or 1000 0100
    public static final int MAX = 32635; //32767 (max 15-bit integer) minus BIAS

    /**
     * Sets whether or not all-zero bytes are encoded as 2 instead.
     * The pcm values this concerns are in the range [32768,33924] (unsigned).
     */
    public boolean getZeroTrap() { return (pcmToMuLawMap[33000] != 0); }
    public void setZeroTrap(boolean value)
        {
            byte val = (byte)(value ? 2 : 0);
            for (int i = 32768; i <= 33924; i++)
                pcmToMuLawMap[i] = val;
        }
    
    /**
     * An array where the index is the 16-bit PCM input, and the value is the mu-law result.
     */
    private static byte[] pcmToMuLawMap;

    static
    {
        pcmToMuLawMap = new byte[65536];
        for (int i = Short.MIN_VALUE; i <= Short.MAX_VALUE; i++)
            pcmToMuLawMap[UnsignedUtils.uShortToInt((short) i)] = encode(i);
    }

    /**
     * Encode one mu-law byte from a 16-bit signed integer. Internal use only.
     * 
     * @param pcm A 16-bit signed pcm value
     * @return A mu-law encoded byte
    */
    private static byte encode(int pcm)
    {
        //Get the sign bit.  Shift it for later use without further modification
        int sign = (pcm & 0x8000) >> 8;
        //If the number is negative, make it positive (now it's a magnitude)
        if (sign != 0)
            pcm = -pcm;
        //The magnitude must be less than 32635 to avoid overflow
        if (pcm > MAX) pcm = MAX;
        //Add 132 to guarantee a 1 in the eight bits after the sign bit
        pcm += BIAS;

        /* Finding the "exponent"
         * Bits:
         * 1 2 3 4 5 6 7 8 9 A B C D E F G
         * S 7 6 5 4 3 2 1 0 . . . . . . .
         * We want to find where the first 1 after the sign bit is.
         * We take the corresponding value from the second row as the exponent value.
         * (i.e. if first 1 at position 7 -> exponent = 2) */
        int exponent = 7;
        //Move to the right and decrement exponent until we hit the 1
        for (int expMask = 0x4000; (pcm & expMask) == 0; exponent--, expMask >>= 1) { }

        /* The last part - the "mantissa"
         * We need to take the four bits after the 1 we just found.
         * To get it, we shift 0x0f :
         * 1 2 3 4 5 6 7 8 9 A B C D E F G
         * S 0 0 0 0 0 1 . . . . . . . . . (meaning exponent is 2)
         * . . . . . . . . . . . . 1 1 1 1
         * We shift it 5 times for an exponent of two, meaning
         * we will shift our four bits (exponent + 3) bits.
         * For convenience, we will actually just shift the number, then and with 0x0f. */
        int mantissa = (pcm >> (exponent + 3)) & 0x0f;

        //The mu-law byte bit arrangement is SEEEMMMM (Sign, Exponent, and Mantissa.)
        byte mulaw = (byte)(sign | exponent << 4 | mantissa);

        //Last is to flip the bits
        return (byte)~mulaw;
    }

    /**
     * Encode a pcm value into a mu-law byte
     * 
     * @param pcm A 16-bit pcm value
     * @return A mu-law encoded byte
    */
    public static byte muLawEncode(int pcm)
    {
        return pcmToMuLawMap[pcm & 0xffff];
    }

    /**
     * Encode a pcm value into a mu-law byte
     * 
     * @param pcm A 16-bit pcm value
     * @return A mu-law encoded byte
    */
    public static byte muLawEncode(short pcm)
    {
        return pcmToMuLawMap[UnsignedUtils.uShortToInt(pcm)];
    }

//    /**
//     * Encode an array of pcm values
//     * 
//     * @param data An array of 16-bit pcm values
//     * @return An array of mu-law bytes containing the results
//    */
//    public static void muLawEncode(int[] data, byte[] encoded)
//    {
//        int size = data.length;
//       // byte[] encoded = new byte[size];
//        for (int i = 0; i < size; i++)
//            encoded[i] = muLawEncode(data[i]);
//        //return encoded;
//    }
//
//    /**
//     * Encode an array of pcm values
//     * 
//     * @param data`An array of 16-bit pcm values
//     * @return An array of mu-law bytes containing the results
//    */
//    public static void muLawEncode(short[] data, byte[] encoded)
//    {
//        int size = data.length;
//        //byte[] encoded = new byte[size];
//        for (int i = 0; i < size; i++)
//            encoded[i] = muLawEncode(data[i]);
//        //return encoded;
//    }
//
//    /**
//     * Encode an array of pcm values
//     * 
//     * @param data An array of bytes in Little-Endian format
//     * @return An array of mu-law bytes containing the results
//     */
//    public static byte[] muLawEncode(byte[] data)
//    {
//        int size = data.length / 2;
//        byte[] encoded = new byte[size];
//        for (int i = 0; i < size; i++)
//            encoded[i] = muLawEncode((data[2 * i + 1] << 8) | data[2 * i]);
//        return encoded;
//    }

    /**
     * Encode an array of pcm values into a pre-allocated target array
     * 
     * @param data An array of bytes in Little-Endian format
     * @param target A pre-allocated array to receive the mu-law bytes.  This array must be at least half the size of the source.
     */
    public static void muLawEncodeLittleEndian(byte[] data, int offset, int len, byte[] target)
    {
        final int size = len / 2;
        for (int i = 0; i < size; i++)
            target[i] = muLawEncode(((data[offset + 2 * i + 1] & 0xff) << 8) | (data[offset + 2 * i] & 0xff));
    }
    
    public static void muLawEncodeBigEndian(byte[] data, int offset, int len, byte[] target)
    {
        final int size = len / 2;
        for (int i = 0; i < size; i++)
            target[i] = muLawEncode(((data[offset + 2 * i + 1]) & 0xff) | ((data[offset + 2 * i] & 0xff)  << 8));
    }

    public static void muLawEncode(boolean bigEndian, byte[] data, int offset, int len, byte[] target)
    {
    	if (bigEndian)
    		muLawEncodeBigEndian(data, offset, len, target);
    	else
    		muLawEncodeLittleEndian(data, offset, len, target);
    		
    }
    
}

