/* Copyright (c) 2007-2008 CSIRO
   Copyright (c) 2007-2011 Xiph.Org Foundation
   Originally written by Jean-Marc Valin, Gregory Maxwell, Koen Vos,
   Timothy B. Terriberry, and the Opus open-source contributors
   Ported to Java by Logan Stromberg

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   - Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

   - Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

   - Neither the name of Internet Society, IETF or IETF Trust, nor the
   names of specific contributors, may be used to endorse or promote
   products derived from this software without specific prior written
   permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
   OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.concentus;

public class CodecHelpers {

    static byte gen_toc(OpusMode mode, int framerate, OpusBandwidth bandwidth, int channels) {
        int period;
        short toc;
        period = 0;
        while (framerate < 400) {
            framerate <<= 1;
            period++;
        }
        if (mode == OpusMode.MODE_SILK_ONLY) {
            toc = (short) ((OpusBandwidthHelpers.GetOrdinal(bandwidth) - OpusBandwidthHelpers.GetOrdinal(OpusBandwidth.OPUS_BANDWIDTH_NARROWBAND)) << 5);
            toc |= (short) ((period - 2) << 3);
        } else if (mode == OpusMode.MODE_CELT_ONLY) {
            int tmp = OpusBandwidthHelpers.GetOrdinal(bandwidth) - OpusBandwidthHelpers.GetOrdinal(OpusBandwidth.OPUS_BANDWIDTH_MEDIUMBAND);
            if (tmp < 0) {
                tmp = 0;
            }
            toc = 0x80;
            toc |= (short) (tmp << 5);
            toc |= (short) (period << 3);
        } else /* Hybrid */ {
            toc = 0x60;
            toc |= (short) ((OpusBandwidthHelpers.GetOrdinal(bandwidth) - OpusBandwidthHelpers.GetOrdinal(OpusBandwidth.OPUS_BANDWIDTH_SUPERWIDEBAND)) << 4);
            toc |= (short) ((period - 2) << 3);
        }
        toc |= (short) ((channels == 2 ? 1 : 0) << 2);
        return (byte) (0xFF & toc);
    }

    static void hp_cutoff(short[] input, int input_ptr, int cutoff_Hz, short[] output, int output_ptr, int[] hp_mem, int len, int channels, int Fs) {
        int[] B_Q28 = new int[3];
        int[] A_Q28 = new int[2];
        int Fc_Q19, r_Q28, r_Q22;

        Inlines.OpusAssert(cutoff_Hz <= Integer.MAX_VALUE / ((int) ((1.5f * 3.14159f / 1000) * ((long) 1 << (19)) + 0.5))/*Inlines.SILK_CONST(1.5f * 3.14159f / 1000, 19)*/);
        Fc_Q19 = Inlines.silk_DIV32_16(Inlines.silk_SMULBB(((int) ((1.5f * 3.14159f / 1000) * ((long) 1 << (19)) + 0.5))/*Inlines.SILK_CONST(1.5f * 3.14159f / 1000, 19)*/, cutoff_Hz), Fs / 1000);
        Inlines.OpusAssert(Fc_Q19 > 0 && Fc_Q19 < 32768);

        r_Q28 = ((int) ((1.0f) * ((long) 1 << (28)) + 0.5))/*Inlines.SILK_CONST(1.0f, 28)*/ - Inlines.silk_MUL(((int) ((0.92f) * ((long) 1 << (9)) + 0.5))/*Inlines.SILK_CONST(0.92f, 9)*/, Fc_Q19);

        /* b = r * [ 1; -2; 1 ]; */
 /* a = [ 1; -2 * r * ( 1 - 0.5 * Fc^2 ); r^2 ]; */
        B_Q28[0] = r_Q28;
        B_Q28[1] = Inlines.silk_LSHIFT(-r_Q28, 1);
        B_Q28[2] = r_Q28;

        /* -r * ( 2 - Fc * Fc ); */
        r_Q22 = Inlines.silk_RSHIFT(r_Q28, 6);
        A_Q28[0] = Inlines.silk_SMULWW(r_Q22, Inlines.silk_SMULWW(Fc_Q19, Fc_Q19) - ((int) ((2.0f) * ((long) 1 << (22)) + 0.5))/*Inlines.SILK_CONST(2.0f, 22)*/);
        A_Q28[1] = Inlines.silk_SMULWW(r_Q22, r_Q22);

        Filters.silk_biquad_alt(input, input_ptr, B_Q28, A_Q28, hp_mem, 0, output, output_ptr, len, channels);
        if (channels == 2) {
            Filters.silk_biquad_alt(input, input_ptr + 1, B_Q28, A_Q28, hp_mem, 2, output, output_ptr + 1, len, channels);
        }
    }

    static void dc_reject(short[] input, int input_ptr, int cutoff_Hz, short[] output, int output_ptr, int[] hp_mem, int len, int channels, int Fs) {
        int c, i;
        int shift;

        /* Approximates -round(log2(4.*cutoff_Hz/Fs)) */
        shift = Inlines.celt_ilog2(Fs / (cutoff_Hz * 3));
        for (c = 0; c < channels; c++) {
            for (i = 0; i < len; i++) {
                int x, tmp, y;
                x = Inlines.SHL32(Inlines.EXTEND32(input[channels * i + c + input_ptr]), 15);
                /* First stage */
                tmp = x - hp_mem[2 * c];
                hp_mem[2 * c] = hp_mem[2 * c] + Inlines.PSHR32(x - hp_mem[2 * c], shift);
                /* Second stage */
                y = tmp - hp_mem[2 * c + 1];
                hp_mem[2 * c + 1] = hp_mem[2 * c + 1] + Inlines.PSHR32(tmp - hp_mem[2 * c + 1], shift);
                output[channels * i + c + output_ptr] = Inlines.EXTRACT16(Inlines.SATURATE(Inlines.PSHR32(y, 15), 32767));
            }
        }
    }

    static void stereo_fade(
            short[] pcm_buf,
            int g1,
            int g2,
            int overlap48,
            int frame_size,
            int channels,
            int[] window,
            int Fs) {
        int i;
        int overlap;
        int inc;
        inc = 48000 / Fs;
        overlap = overlap48 / inc;
        g1 = CeltConstants.Q15ONE - g1;
        g2 = CeltConstants.Q15ONE - g2;
        for (i = 0; i < overlap; i++) {
            int diff;
            int g, w;
            w = Inlines.MULT16_16_Q15(window[i * inc], window[i * inc]);
            g = Inlines.SHR32(Inlines.MAC16_16(Inlines.MULT16_16(w, g2),
                    CeltConstants.Q15ONE - w, g1), 15);
            diff = Inlines.EXTRACT16(Inlines.HALF32((int) pcm_buf[i * channels] - (int) pcm_buf[i * channels + 1]));
            diff = Inlines.MULT16_16_Q15(g, diff);
            pcm_buf[i * channels] = (short) (pcm_buf[i * channels] - diff);
            pcm_buf[i * channels + 1] = (short) (pcm_buf[i * channels + 1] + diff);
        }
        for (; i < frame_size; i++) {
            int diff;
            diff = Inlines.EXTRACT16(Inlines.HALF32((int) pcm_buf[i * channels] - (int) pcm_buf[i * channels + 1]));
            diff = Inlines.MULT16_16_Q15(g2, diff);
            pcm_buf[i * channels] = (short) (pcm_buf[i * channels] - diff);
            pcm_buf[i * channels + 1] = (short) (pcm_buf[i * channels + 1] + diff);
        }
    }

    static void gain_fade(short[] buffer, int buf_ptr, int g1, int g2,
            int overlap48, int frame_size, int channels, int[] window, int Fs) {
        int i;
        int inc;
        int overlap;
        int c;
        inc = 48000 / Fs;
        overlap = overlap48 / inc;
        if (channels == 1) {
            for (i = 0; i < overlap; i++) {
                int g, w;
                w = Inlines.MULT16_16_Q15(window[i * inc], window[i * inc]);
                g = Inlines.SHR32(Inlines.MAC16_16(Inlines.MULT16_16(w, g2),
                        CeltConstants.Q15ONE - w, g1), 15);
                buffer[buf_ptr + i] = (short) Inlines.MULT16_16_Q15(g, buffer[buf_ptr + i]);
            }
        } else {
            for (i = 0; i < overlap; i++) {
                int g, w;
                w = Inlines.MULT16_16_Q15(window[i * inc], window[i * inc]);
                g = Inlines.SHR32(Inlines.MAC16_16(Inlines.MULT16_16(w, g2),
                        CeltConstants.Q15ONE - w, g1), 15);
                buffer[buf_ptr + i * 2] = (short) Inlines.MULT16_16_Q15(g, buffer[buf_ptr + i * 2]);
                buffer[buf_ptr + i * 2 + 1] = (short) Inlines.MULT16_16_Q15(g, buffer[buf_ptr + i * 2 + 1]);
            }
        }
        c = 0;
        do {
            for (i = overlap; i < frame_size; i++) {
                buffer[buf_ptr + i * channels + c] = (short) Inlines.MULT16_16_Q15(g2, buffer[buf_ptr + i * channels + c]);
            }
        } while (++c < channels);
    }

    /* Don't use more than 60 ms for the frame size analysis */
    private final static int MAX_DYNAMIC_FRAMESIZE = 24;

    /* Estimates how much the bitrate will be boosted based on the sub-frame energy */
    static float transient_boost(float[] E, int E_ptr, float[] E_1, int LM, int maxM) {
        int i;
        int M;
        float sumE = 0, sumE_1 = 0;
        float metric;

        M = Inlines.IMIN(maxM, (1 << LM) + 1);
        for (i = E_ptr; i < M + E_ptr; i++) {
            sumE += E[i];
            sumE_1 += E_1[i];
        }
        metric = sumE * sumE_1 / (M * M);
        /*if (LM==3)
           printf("%f\n", metric);*/
 /*return metric>10 ? 1 : 0;*/
 /*return Inlines.MAX16(0,1-exp(-.25*(metric-2.)));*/
        return Inlines.MIN16(1, (float) Math.sqrt(Inlines.MAX16(0, .05f * (metric - 2))));
    }

    /* Viterbi decoding trying to find the best frame size combination using look-ahead

       State numbering:
        0: unused
        1:  2.5 ms
        2:  5 ms (#1)
        3:  5 ms (#2)
        4: 10 ms (#1)
        5: 10 ms (#2)
        6: 10 ms (#3)
        7: 10 ms (#4)
        8: 20 ms (#1)
        9: 20 ms (#2)
       10: 20 ms (#3)
       11: 20 ms (#4)
       12: 20 ms (#5)
       13: 20 ms (#6)
       14: 20 ms (#7)
       15: 20 ms (#8)
     */
    static int transient_viterbi(float[] E, float[] E_1, int N, int frame_cost, int rate) {
        int i;
        float[][] cost = Arrays.InitTwoDimensionalArrayFloat(MAX_DYNAMIC_FRAMESIZE, 16);
        int[][] states = Arrays.InitTwoDimensionalArrayInt(MAX_DYNAMIC_FRAMESIZE, 16);
        float best_cost;
        int best_state;
        float factor;
        /* Take into account that we damp VBR in the 32 kb/s to 64 kb/s range. */
        if (rate < 80) {
            factor = 0;
        } else if (rate > 160) {
            factor = 1;
        } else {
            factor = (rate - 80.0f) / 80.0f;
        }
        /* Makes variable framesize less aggressive at lower bitrates, but I can't
           find any valid theoretical justification for this (other than it seems
           to help) */
        for (i = 0; i < 16; i++) {
            /* Impossible state */
            states[0][i] = -1;
            cost[0][i] = 1e10f;
        }
        for (i = 0; i < 4; i++) {
            cost[0][1 << i] = (frame_cost + rate * (1 << i)) * (1 + factor * transient_boost(E, 0, E_1, i, N + 1));
            states[0][1 << i] = i;
        }
        for (i = 1; i < N; i++) {
            int j;

            /* Follow continuations */
            for (j = 2; j < 16; j++) {
                cost[i][j] = cost[i - 1][j - 1];
                states[i][j] = j - 1;
            }

            /* New frames */
            for (j = 0; j < 4; j++) {
                int k;
                float min_cost;
                float curr_cost;
                states[i][1 << j] = 1;
                min_cost = cost[i - 1][1];
                for (k = 1; k < 4; k++) {
                    float tmp = cost[i - 1][(1 << (k + 1)) - 1];
                    if (tmp < min_cost) {
                        states[i][1 << j] = (1 << (k + 1)) - 1;
                        min_cost = tmp;
                    }
                }
                curr_cost = (frame_cost + rate * (1 << j)) * (1 + factor * transient_boost(E, i, E_1, j, N - i + 1));
                cost[i][1 << j] = min_cost;
                /* If part of the frame is outside the analysis window, only count part of the cost */
                if (N - i < (1 << j)) {
                    cost[i][1 << j] += curr_cost * (float) (N - i) / (1 << j);
                } else {
                    cost[i][1 << j] += curr_cost;
                }
            }
        }

        best_state = 1;
        best_cost = cost[N - 1][1];
        /* Find best end state (doesn't force a frame to end at N-1) */
        for (i = 2; i < 16; i++) {
            if (cost[N - 1][i] < best_cost) {
                best_cost = cost[N - 1][i];
                best_state = i;
            }
        }

        /* Follow transitions back */
        for (i = N - 1; i >= 0; i--) {
            /*printf("%d ", best_state);*/
            best_state = states[i][best_state];
        }
        /*printf("%d\n", best_state);*/
        return best_state;
    }

    static int optimize_framesize(short[] x, int x_ptr, int len, int C, int Fs,
            int bitrate, int tonality, float[] mem, int buffering) {
        int N;
        int i;
        float[] e = new float[MAX_DYNAMIC_FRAMESIZE + 4];
        float[] e_1 = new float[MAX_DYNAMIC_FRAMESIZE + 3];
        int memx;
        int bestLM = 0;
        int subframe;
        int pos;
        int offset;
        int[] sub;

        subframe = Fs / 400;
        sub = new int[subframe];
        e[0] = mem[0];
        e_1[0] = 1.0f / (CeltConstants.EPSILON + mem[0]);
        if (buffering != 0) {
            /* Consider the CELT delay when not in restricted-lowdelay */
 /* We assume the buffering is between 2.5 and 5 ms */
            offset = 2 * subframe - buffering;
            Inlines.OpusAssert(offset >= 0 && offset <= subframe);
            len -= offset;
            e[1] = mem[1];
            e_1[1] = 1.0f / (CeltConstants.EPSILON + mem[1]);
            e[2] = mem[2];
            e_1[2] = 1.0f / (CeltConstants.EPSILON + mem[2]);
            pos = 3;
        } else {
            pos = 1;
            offset = 0;
        }
        N = Inlines.IMIN(len / subframe, MAX_DYNAMIC_FRAMESIZE);
        /* Just silencing a warning, it's really initialized later */
        memx = 0;
        for (i = 0; i < N; i++) {
            float tmp;
            int tmpx;
            int j;
            tmp = CeltConstants.EPSILON;

            Downmix.downmix_int(x, x_ptr, sub, 0, subframe, i * subframe + offset, 0, -2, C);
            if (i == 0) {
                memx = sub[0];
            }
            for (j = 0; j < subframe; j++) {
                tmpx = sub[j];
                tmp += (tmpx - memx) * (float) (tmpx - memx);
                memx = tmpx;
            }
            e[i + pos] = tmp;
            e_1[i + pos] = 1.0f / tmp;
        }
        /* Hack to get 20 ms working with APPLICATION_AUDIO
           The real problem is that the corresponding memory needs to use 1.5 ms
           from this frame and 1 ms from the next frame */
        e[i + pos] = e[i + pos - 1];
        if (buffering != 0) {
            N = Inlines.IMIN(MAX_DYNAMIC_FRAMESIZE, N + 2);
        }
        bestLM = transient_viterbi(e, e_1, N, (int) ((1.0f + .5f * tonality) * (60 * C + 40)), bitrate / 400);
        mem[0] = e[1 << bestLM];
        if (buffering != 0) {
            mem[1] = e[(1 << bestLM) + 1];
            mem[2] = e[(1 << bestLM) + 2];
        }
        return bestLM;
    }

    static int frame_size_select(int frame_size, OpusFramesize variable_duration, int Fs) {
        int new_size;
        if (frame_size < Fs / 400) {
            return -1;
        }
        if (variable_duration == OpusFramesize.OPUS_FRAMESIZE_ARG) {
            new_size = frame_size;
        } else if (variable_duration == OpusFramesize.OPUS_FRAMESIZE_VARIABLE) {
            new_size = Fs / 50;
        } else if (OpusFramesizeHelpers.GetOrdinal(variable_duration) >= OpusFramesizeHelpers.GetOrdinal(OpusFramesize.OPUS_FRAMESIZE_2_5_MS)
                && OpusFramesizeHelpers.GetOrdinal(variable_duration) <= OpusFramesizeHelpers.GetOrdinal(OpusFramesize.OPUS_FRAMESIZE_60_MS)) {
            new_size = Inlines.IMIN(3 * Fs / 50, (Fs / 400) << (OpusFramesizeHelpers.GetOrdinal(variable_duration) - OpusFramesizeHelpers.GetOrdinal(OpusFramesize.OPUS_FRAMESIZE_2_5_MS)));
        } else {
            return -1;
        }
        if (new_size > frame_size) {
            return -1;
        }
        if (400 * new_size != Fs && 200 * new_size != Fs && 100 * new_size != Fs
                && 50 * new_size != Fs && 25 * new_size != Fs && 50 * new_size != 3 * Fs) {
            return -1;
        }
        return new_size;
    }

    static int compute_frame_size(short[] analysis_pcm, int analysis_pcm_ptr, int frame_size,
            OpusFramesize variable_duration, int C, int Fs, int bitrate_bps,
            int delay_compensation, float[] subframe_mem, boolean analysis_enabled
    ) {

        if (analysis_enabled && variable_duration == OpusFramesize.OPUS_FRAMESIZE_VARIABLE && frame_size >= Fs / 200) {
            int LM = 3;
            LM = optimize_framesize(analysis_pcm, analysis_pcm_ptr, frame_size, C, Fs, bitrate_bps,
                    0, subframe_mem, delay_compensation);
            while ((Fs / 400 << LM) > frame_size) {
                LM--;
            }
            frame_size = (Fs / 400 << LM);
        } else {
            frame_size = frame_size_select(frame_size, variable_duration, Fs);
        }

        if (frame_size < 0) {
            return -1;
        }
        return frame_size;
    }

    static int compute_stereo_width(short[] pcm, int pcm_ptr, int frame_size, int Fs, StereoWidthState mem) {
        int corr;
        int ldiff;
        int width;
        int xx, xy, yy;
        int sqrt_xx, sqrt_yy;
        int qrrt_xx, qrrt_yy;
        int frame_rate;
        int i;
        int short_alpha;

        frame_rate = Fs / frame_size;
        short_alpha = CeltConstants.Q15ONE - (25 * CeltConstants.Q15ONE / Inlines.IMAX(50, frame_rate));
        xx = xy = yy = 0;
        for (i = 0; i < frame_size - 3; i += 4) {
            int pxx = 0;
            int pxy = 0;
            int pyy = 0;
            int x, y;
            int p2i = pcm_ptr + (2 * i);
            x = pcm[p2i];
            y = pcm[p2i + 1];
            pxx = Inlines.SHR32(Inlines.MULT16_16(x, x), 2);
            pxy = Inlines.SHR32(Inlines.MULT16_16(x, y), 2);
            pyy = Inlines.SHR32(Inlines.MULT16_16(y, y), 2);
            x = pcm[p2i + 2];
            y = pcm[p2i + 3];
            pxx += Inlines.SHR32(Inlines.MULT16_16(x, x), 2);
            pxy += Inlines.SHR32(Inlines.MULT16_16(x, y), 2);
            pyy += Inlines.SHR32(Inlines.MULT16_16(y, y), 2);
            x = pcm[p2i + 4];
            y = pcm[p2i + 5];
            pxx += Inlines.SHR32(Inlines.MULT16_16(x, x), 2);
            pxy += Inlines.SHR32(Inlines.MULT16_16(x, y), 2);
            pyy += Inlines.SHR32(Inlines.MULT16_16(y, y), 2);
            x = pcm[p2i + 6];
            y = pcm[p2i + 7];
            pxx += Inlines.SHR32(Inlines.MULT16_16(x, x), 2);
            pxy += Inlines.SHR32(Inlines.MULT16_16(x, y), 2);
            pyy += Inlines.SHR32(Inlines.MULT16_16(y, y), 2);

            xx += Inlines.SHR32(pxx, 10);
            xy += Inlines.SHR32(pxy, 10);
            yy += Inlines.SHR32(pyy, 10);
        }

        mem.XX += Inlines.MULT16_32_Q15(short_alpha, xx - mem.XX);
        mem.XY += Inlines.MULT16_32_Q15(short_alpha, xy - mem.XY);
        mem.YY += Inlines.MULT16_32_Q15(short_alpha, yy - mem.YY);
        mem.XX = Inlines.MAX32(0, mem.XX);
        mem.XY = Inlines.MAX32(0, mem.XY);
        mem.YY = Inlines.MAX32(0, mem.YY);
        if (Inlines.MAX32(mem.XX, mem.YY) > ((short) (0.5 + (8e-4f) * (((int) 1) << (18))))/*Inlines.QCONST16(8e-4f, 18)*/) {
            sqrt_xx = Inlines.celt_sqrt(mem.XX);
            sqrt_yy = Inlines.celt_sqrt(mem.YY);
            qrrt_xx = Inlines.celt_sqrt(sqrt_xx);
            qrrt_yy = Inlines.celt_sqrt(sqrt_yy);
            /* Inter-channel correlation */
            mem.XY = Inlines.MIN32(mem.XY, sqrt_xx * sqrt_yy);
            corr = Inlines.SHR32(Inlines.frac_div32(mem.XY, CeltConstants.EPSILON + Inlines.MULT16_16(sqrt_xx, sqrt_yy)), 16);
            /* Approximate loudness difference */
            ldiff = CeltConstants.Q15ONE * Inlines.ABS16(qrrt_xx - qrrt_yy) / (CeltConstants.EPSILON + qrrt_xx + qrrt_yy);
            width = Inlines.MULT16_16_Q15(Inlines.celt_sqrt(((int) (0.5 + (1.0f) * (((int) 1) << (30))))/*Inlines.QCONST32(1.0f, 30)*/ - Inlines.MULT16_16(corr, corr)), ldiff);
            /* Smoothing over one second */
            mem.smoothed_width += (width - mem.smoothed_width) / frame_rate;
            /* Peak follower */
            mem.max_follower = Inlines.MAX16(mem.max_follower - ((short) (0.5 + (.02f) * (((int) 1) << (15))))/*Inlines.QCONST16(.02f, 15)*/ / frame_rate, mem.smoothed_width);
        } else {
            width = 0;
            corr = CeltConstants.Q15ONE;
            ldiff = 0;
        }
        /*printf("%f %f %f %f %f ", corr/(float)1.0f, ldiff/(float)1.0f, width/(float)1.0f, mem.smoothed_width/(float)1.0f, mem.max_follower/(float)1.0f);*/
        return Inlines.EXTRACT16(Inlines.MIN32(CeltConstants.Q15ONE, 20 * mem.max_follower));
    }

    static void smooth_fade(short[] in1, int in1_ptr, short[] in2, int in2_ptr,
            short[] output, int output_ptr, int overlap, int channels,
            int[] window, int Fs) {
        int i, c;
        int inc = 48000 / Fs;
        for (c = 0; c < channels; c++) {
            for (i = 0; i < overlap; i++) {
                int w = Inlines.MULT16_16_Q15(window[i * inc], window[i * inc]);
                output[output_ptr + (i * channels) + c] = (short) (Inlines.SHR32(Inlines.MAC16_16(Inlines.MULT16_16(w, in2[in2_ptr + (i * channels) + c]),
                        CeltConstants.Q15ONE - w, in1[in1_ptr + (i * channels) + c]), 15));
            }
        }
    }

    //static void opus_pcm_soft_clip(Pointer<float> _x, int N, int C, Pointer<float> declip_mem)
    //{
    //    int c;
    //    int i;
    //    Pointer<float> x;
    //    if (C < 1 || N < 1 || _x == null || declip_mem == null) return;
    //    /* First thing: saturate everything to +/- 2 which is the highest level our
    //       non-linearity can handle. At the point where the signal reaches +/-2,
    //       the derivative will be zero anyway, so this doesn't introduce any
    //       discontinuity in the derivative. */
    //    for (i = 0; i < N * C; i++)
    //        _x[i] = Inlines.MAX16(-2.0f, Inlines.MIN16(2.0f, _x[i]));
    //    for (c = 0; c < C; c++)
    //    {
    //        float a;
    //        float x0;
    //        int curr;
    //        x = _x.Point(c);
    //        a = declip_mem[c];
    //        /* Continue applying the non-linearity from the previous frame to avoid
    //           any discontinuity. */
    //        for (i = 0; i < N; i++)
    //        {
    //            if (x[i * C] * a >= 0)
    //                break;
    //            x[i * C] = x[i * C] + a * x[i * C] * x[i * C];
    //        }
    //        curr = 0;
    //        x0 = x[0];
    //        while (true)
    //        {
    //            int start, end;
    //            float maxval;
    //            int special = 0;
    //            int peak_pos;
    //            for (i = curr; i < N; i++)
    //            {
    //                if (x[i * C] > 1 || x[i * C] < -1)
    //                    break;
    //            }
    //            if (i == N)
    //            {
    //                a = 0;
    //                break;
    //            }
    //            peak_pos = i;
    //            start = end = i;
    //            maxval = Inlines.ABS16(x[i * C]);
    //            /* Look for first zero crossing before clipping */
    //            while (start > 0 && x[i * C] * x[(start - 1) * C] >= 0)
    //                start--;
    //            /* Look for first zero crossing after clipping */
    //            while (end < N && x[i * C] * x[end * C] >= 0)
    //            {
    //                /* Look for other peaks until the next zero-crossing. */
    //                if (Inlines.ABS16(x[end * C]) > maxval)
    //                {
    //                    maxval = Inlines.ABS16(x[end * C]);
    //                    peak_pos = end;
    //                }
    //                end++;
    //            }
    //            /* Detect the special case where we clip before the first zero crossing */
    //            special = (start == 0 && x[i * C] * x[0] >= 0) ? 1 : 0;
    //            /* Compute a such that maxval + a*maxval^2 = 1 */
    //            a = (maxval - 1) / (maxval * maxval);
    //            if (x[i * C] > 0)
    //                a = -a;
    //            /* Apply soft clipping */
    //            for (i = start; i < end; i++)
    //                x[i * C] = x[i * C] + a * x[i * C] * x[i * C];
    //            if (special != 0 && peak_pos >= 2)
    //            {
    //                /* Add a linear ramp from the first sample to the signal peak.
    //                   This avoids a discontinuity at the beginning of the frame. */
    //                float delta;
    //                float offset = x0 - x[0];
    //                delta = offset / peak_pos;
    //                for (i = curr; i < peak_pos; i++)
    //                {
    //                    offset -= delta;
    //                    x[i * C] += offset;
    //                    x[i * C] = Inlines.MAX16(-1.0f, Inlines.MIN16(1.0f, x[i * C]));
    //                }
    //            }
    //            curr = end;
    //            if (curr == N)
    //            {
    //                break;
    //            }
    //        }
    //        declip_mem[c] = a;
    //    }
    //}
    public static String opus_strerror(int error) {
        String[] error_strings = {
            "success",
            "invalid argument",
            "buffer too small",
            "error",
            "corrupted stream",
            "request not implemented",
            "invalid state",
            "memory allocation failed"
        };
        if (error > 0 || error < -7) {
            return "unknown error";
        } else {
            return error_strings[-error];
        }
    }

    public static String GetVersionString() {
        return "concentus 1.0a-java-fixed";
    }
}
