/*
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * 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.
 */
package org.jitsi.impl.neomedia.codec.audio.silk;

import static org.jitsi.impl.neomedia.codec.audio.silk.Macros.*;
import static org.jitsi.impl.neomedia.codec.audio.silk.Typedef.*;

/**
 * Variable order MA filter.
 *
 * @author Jing Dai
 * @author Dingxin Xu
 */
public class MA
{
    /**
     * Variable order MA filter.
     * @param in input signal.
     * @param in_offset offset of valid data.
     * @param B MA coefficients, Q13 [order+1].
     * @param S state vector [order].
     * @param out output signal.
     * @param out_offset offset of valid data.
     * @param len signal length.
     * @param order filter order.
     */
    static void SKP_Silk_MA(
            short[]    in,            /* I:   input signal                                */
            int        in_offset,
            short[]    B,             /* I:   MA coefficients, Q13 [order+1]              */
            int[]      S,             /* I/O: state vector [order]                        */
            short[]    out,           /* O:   output signal                               */
            int        out_offset,
            final int  len,            /* I:   signal length                               */
            final int  order           /* I:   filter order                                */
    )
    {
        int   k, d, in16;
        int out32;

        for( k = 0; k < len; k++ ) {
            in16 = in[ in_offset + k ];
            out32 = SKP_SMLABB(S[0], in16, B[ 0 ]);
            out32 = SigProcFIX.SKP_RSHIFT_ROUND( out32, 13 );

            for( d = 1; d < order; d++ ) {
                S[ d - 1 ] = SKP_SMLABB( S[ d ], in16, B[ d ] );
            }
            S[ order - 1 ] = SKP_SMULBB( in16, B[ order ] );

            /* Limit */
            out[ out_offset + k ] = (short)SigProcFIX.SKP_SAT16( out32 );
        }
    }

    /**
     * Variable order MA prediction error filter.
     * @param in Input signal.
     * @param in_offset offset of valid data.
     * @param B MA prediction coefficients, Q12 [order].
     * @param B_offset
     * @param S State vector [order].
     * @param out Output signal.
     * @param out_offset offset of valid data.
     * @param len Signal length.
     * @param order Filter order.
     */
    static void SKP_Silk_MA_Prediction(
            short[]      in,            /* I:   Input signal                                */
            int          in_offset,
            short[]      B,             /* I:   MA prediction coefficients, Q12 [order]     */
            int          B_offset,
            int[]        S,             /* I/O: State vector [order]                        */
            short[]      out,           /* O:   Output signal                               */
            int          out_offset,
            final int    len,            /* I:   Signal length                               */
            final int    order           /* I:   Filter order                                */
        )
    {
        int   k, d, in16;
        int out32;

        for( k = 0; k < len; k++ ) {
            in16 = in[ in_offset + k ];
            out32 = ( in16 << 12 ) - S[ 0 ];
            out32 = SigProcFIX.SKP_RSHIFT_ROUND( out32, 12 );

            for( d = 0; d < order - 1; d++ ) {
                S[ d ] = SigProcFIX.SKP_SMLABB_ovflw( S[ d + 1 ], in16, B[ B_offset + d ] );
            }
            S[ order - 1 ] = SKP_SMULBB( in16, B[ B_offset + order - 1 ] );

            /* Limit */
            out[ out_offset + k ] = (short)SigProcFIX.SKP_SAT16( out32 );
        }
    }

    /**
     *
     * @param in input signal.
     * @param in_offset offset of valid data.
     * @param B MA prediction coefficients, Q13 [order].
     * @param S state vector [order].
     * @param out output signal.
     * @param out_offset offset of valid data.
     * @param len signal length.
     * @param order filter order.
     */
    static void SKP_Silk_MA_Prediction_Q13(
            short      []in,            /* I:   input signal                                */
            int          in_offset,
            short      []B,             /* I:   MA prediction coefficients, Q13 [order]     */
            int        []S,             /* I/O: state vector [order]                        */
            short      []out,           /* O:   output signal                               */
            int          out_offset,
            int          len,            /* I:   signal length                               */
            int          order           /* I:   filter order                                */
        )
    {
        int   k, d, in16;
        int out32;
        for( k = 0; k < len; k++ ) {
            in16 = in[ in_offset + k ];
            out32 = ( in16 << 13 ) - S[ 0 ];
            out32 = SigProcFIX.SKP_RSHIFT_ROUND( out32, 13 );

            for( d = 0; d < order - 1; d++ ) {
                S[ d ] = SKP_SMLABB( S[ d + 1 ], in16, B[ d ] );
            }
            S[ order - 1 ] = SKP_SMULBB( in16, B[ order - 1 ] );

            /* Limit */
            out[ out_offset + k ] = ( short )SigProcFIX.SKP_SAT16( out32 );
        }
    }

    /**
     *
     * @param in Input signal.
     * @param in_offset offset of valid data.
     * @param B MA prediction coefficients, Q12 [order].
     * @param S State vector [order].
     * @param out Output signal.
     * @param out_offset offset of valid data.
     * @param len Signal length.
     * @param Order Filter order.
     */
    static void SKP_Silk_LPC_analysis_filter(
            short      []in,            /* I:   Input signal                                */
            int        in_offset,
            short      []B,             /* I:   MA prediction coefficients, Q12 [order]     */
            short      []S,             /* I/O: State vector [order]                        */
            short      []out,           /* O:   Output signal                               */
            int        out_offset,
            final int  len,            /* I:   Signal length                               */
            final int  Order           /* I:   Filter order                                */
        )
    {
        int   k, j, idx, Order_half = ( Order >> 1 );
        int out32_Q12, out32;
        short SA, SB;
        /* Order must be even */
        SKP_assert( 2 * Order_half == Order );

        /* S[] values are in Q0 */
        for( k = 0; k < len; k++ ) {
            SA = S[ 0 ];
            out32_Q12 = 0;
            for( j = 0; j < ( Order_half - 1 ); j++ ) {
                idx = SKP_SMULBB( 2, j ) + 1;
                /* Multiply-add two prediction coefficients for each loop */
                SB = S[ idx ];
                S[ idx ] = SA;
                out32_Q12 = SKP_SMLABB( out32_Q12, SA, B[ idx - 1 ] );
                out32_Q12 = SKP_SMLABB( out32_Q12, SB, B[ idx ] );
                SA = S[ idx + 1 ];
                S[ idx + 1 ] = SB;
            }

            /* Unrolled loop: epilog */
            SB = S[ Order - 1 ];
            S[ Order - 1 ] = SA;
            out32_Q12 = SKP_SMLABB( out32_Q12, SA, B[ Order - 2 ] );
            out32_Q12 = SKP_SMLABB( out32_Q12, SB, B[ Order - 1 ] );

            /* Subtract prediction */
            out32_Q12 = SKP_SUB_SAT32( ( in[ in_offset + k ] << 12 ), out32_Q12 );

            /* Scale to Q0 */
            out32 = SigProcFIX.SKP_RSHIFT_ROUND( out32_Q12, 12 );

            /* Saturate output */
            out[ out_offset + k ] = ( short )SigProcFIX.SKP_SAT16( out32 );

            /* Move input line */
            S[ 0 ] = in[ in_offset + k ];
        }
    }
}
