// Convert a block from PCM to BRR
        // Returns the squared error between original data and encoded data
        // If "is_end_point" is true, the predictions p1/p2 at loop are also used in caluclating the error (depending on filter at loop)
        private static double ADPCMMash(int shiftamount, byte filter, Int16[] PCMData, bool write, bool isEndPoint, BrrEncoderOptions options, BrrEncoderStatistics statistics, ref Int16 p1, ref Int16 p2, ref byte[] brr)
        {
            double d2   = 0.0;
            Int16  l1   = p1;
            Int16  l2   = p2;
            int    step = 1 << shiftamount;

            int vlin, d, da, dp, c;

            if (PCMData.Length < 16)
            {
                return(0);
            }

            for (int i = 0; i < 16; ++i)
            {
                //Make linear prediction for next sample
                //vlin = (v0 * iCoef[0] + v1 * iCoef[1]) >> 8;
                vlin = getBrrPrediction(filter, l1, l2) >> 1;
                d    = (PCMData[i] >> 1) - vlin; //difference between linear prediction and current sample
                da   = Math.Abs(d);

                // Take advantage of wrapping
                if (options.wrapEn && da > 16384 && da < 32768)
                {
                    d = d - 32768 * (d >> 24);
                    //if(write) printf("Caution : Wrapping was used.\n");
                }

                dp = d + (step << 2) + (step >> 2);
                c  = 0;
                if (dp > 0)
                {
                    if (step > 1)
                    {
                        c = dp / (step / 2);
                    }
                    else
                    {
                        c = dp * 2;
                    }
                    if (c > 15)
                    {
                        c = 15;
                    }
                }
                c -= 8;
                dp = (c << shiftamount) >> 1; // quantized estimate of samp - vlin
                                              // edge case, if caller even wants to use it
                if (shiftamount > 12)
                {
                    dp = (dp >> 14) & ~0x7FF;
                }
                c &= 0x0f; // mask to 4 bits

                l2 = l1;   //shift history */
                l1 = (Int16)(sclamp16(vlin + dp) * 2);

                d   = PCMData[i] - l1;
                d2 += (double)d * d; // update square-error

                if (write)           // If we want output, put it in proper place
                {
                    brr[1 + (i >> 1)] |= ((i & 1) != 0) ? (byte)c : (byte)(c << 4);
                }
            }

            // Also account for history points when looping is enabled & filters used
            if (!isEndPoint)
            {
                d2 /= 16.0;
            }
            else
            {
                switch (statistics.filterAtLoop)
                {
                case 0:
                    d2 /= 16.0;
                    break;

                case 1:     // Filter 1
                    d   = l1 - statistics.p1AtLoop;
                    d2 += (double)d * d;
                    d2 /= 17.0;
                    break;

                default:     // Filters 2 & 3
                    d   = l1 - statistics.p1AtLoop;
                    d2 += (double)d * d;
                    d   = l2 - statistics.p2AtLoop;
                    d2 += (double)d * d;
                    d2 /= 18.0;
                    break;
                }
            }

            // when generating real output, we want to return these
            if (write)
            {
                p1 = l1;
                p2 = l2;

                //Set the end bit if we're on the last block
                brr[0] = Convert.ToByte((shiftamount << 4) | (filter << 2));
                if (isEndPoint)
                {
                    brr[0] |= 1;
                }
            }
            return(d2);
        }
        // Encode a ADPCM block using brute force over filters and shift amounts
        private static void ADPCMBlockMash(Int16[] PCMData, bool isLoopPoint, bool isEndPoint, BrrEncoderOptions options, BrrEncoderStatistics statistics, ref Int16 p1, ref Int16 p2, ref byte[] brr)
        {
            byte   smin = byte.MaxValue;
            byte   kmin = byte.MaxValue;
            double dmin = double.MaxValue;

            for (byte s = 0; s < 13; ++s)
            {
                for (byte k = 0; k < 4; ++k)
                {
                    if (!options.FIRen[k])
                    {
                        continue;
                    }
                    double d = ADPCMMash(s, k, PCMData, false, isEndPoint, options, statistics, ref p1, ref p2, ref brr);
                    if (d >= dmin)
                    {
                        continue;
                    }

                    kmin = k;           //Memorize the filter, shift values with smaller error
                    dmin = d;
                    smin = s;
                }
            }

            if (isLoopPoint)
            {
                statistics.filterAtLoop = kmin;
                statistics.p1AtLoop     = p1;
                statistics.p2AtLoop     = p2;
            }

            ADPCMMash(smin, kmin, PCMData, true, isEndPoint, options, statistics, ref p1, ref p2, ref brr);
            statistics.FIRstats[kmin]++;
        }
        public static byte[] encodeBrr(WavFileHeader hdr, Int16[] samples, BrrEncoderOptions options)
        {
            List <byte> output = new List <byte>();

            byte   loopFlag    = 0x00;
            UInt16 loopStart   = 0;
            uint   newLoopsize = 0;

            if (options.loopAddress >= 0)
            {
                loopFlag  = 0x02; // 0x02 if loop flag is active.
                loopStart = Convert.ToUInt16(options.loopAddress);
            }

            // Optional truncation of input sample
            UInt32 samplesLength = hdr.dataSize / hdr.blockAlign; // FIXME comprobar qué es esto ¿no se puede utilizar samples.Length?

            // Adjust amplitude in function of amount of channels
            samples = adjustAmplitudeOfWavSamples(samples, samplesLength, hdr.chans, hdr.bitsPerSample, options);

            // Apply resampling if needed
            applyResampling(ref samples, ref samplesLength, ref newLoopsize, hdr.sampleRate, loopStart, options);

            // Apply trebble boost filter (gussian lowpass compensation) if requested by user
            if (options.trebleBoost)
            {
                samples = trebleBoostFilter(samples, samplesLength);
            }

            //Add zeroes at the beginning if the Amount of PCM samples isn't a multiple of 16
            int zeroesToAdd = samples.Length % 16;

            if (zeroesToAdd != 0)
            {
                samples = Enumerable.Repeat((Int16)0, zeroesToAdd).Concat(samples).ToArray();
            }

            //Begin the actual BRR encoding

            //Initialization needed if any of the first 16 samples isn't zero
            bool needsInitialBrrBlock = false;

            if (samples.Length >= 16)
            {
                for (int i = 0; i < 16 && !needsInitialBrrBlock; ++i)
                {
                    needsInitialBrrBlock |= samples[i] != 0;
                }
            }
            else
            {
                needsInitialBrrBlock = true;
            }

            if (needsInitialBrrBlock)
            {
                output = new List <byte>()
                {
                    loopFlag, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
                }
            }
            ;

            //Encode BRR

            //Set the truncate size
            if (options.truncateLen != 0 && output.Count > 0)
            {
                options.truncateLen -= 16;
            }

            if (options.truncateLen != 0 && (options.truncateLen != samplesLength))
            {
                samplesLength = options.truncateLen;
            }

            Int16 p1 = 0;
            Int16 p2 = 0;
            BrrEncoderStatistics statistics = new BrrEncoderStatistics();

            for (int n = 0; n < samplesLength; n += 16)
            {
                byte[] brrChunk = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

                //Encode BRR block, tell the encoder if we're at loop point (if loop is enabled), and if we're at end point
                bool isLoopPoint = options.fixLoopEn && (n == (samplesLength - newLoopsize));
                bool isEndPoint  = n == samplesLength - 16;

                Int16[] samplesToCheck = samples.Skip(n).ToArray();
                if (samplesToCheck.Length < 16)
                {
                    samplesToCheck = samplesToCheck.Concat(new Int16[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }).ToArray();
                }

                ADPCMBlockMash(samples.Skip(n).ToArray(), isLoopPoint, isEndPoint, options, statistics, ref p1, ref p2, ref brrChunk);

                //Set the loop flag if needed
                brrChunk[0] |= loopFlag;
                output.AddRange(brrChunk);
            }

            //HACKME recover this
            //    if(fix_loop_en)
            //    {
            //        unsigned int k = samples_length - (initial_block ? new_loopsize - 16 : new_loopsize);
            //        printf("Position of the loop within the BRR sample : %u samples = %u BRR blocks.\n", k, k/16);
            //    }

            //    for(int i=0; i<4; i++)
            //        if (FIRstats[i]>0) printf("Filter %u used on %u blocks.\n", i, FIRstats[i]);

            return(output.ToArray());
        }