NScumm.Scumm.Audio.IMuse.IMuseDigital.BundleCodecs.DecompressADPCM C# (CSharp) Method

DecompressADPCM() static private method

static private DecompressADPCM ( byte compInput, byte compOutput, int channels ) : int
compInput byte
compOutput byte
channels int
return int
        static int DecompressADPCM(byte[] compInput, byte[] compOutput, int channels)
        {
            // Decoder for the the IMA ADPCM variants used in COMI.
            // Contrary to regular IMA ADPCM, this codec uses a variable
            // bitsize for the encoded data.

            const int MAX_CHANNELS = 2;
            int outputSamplesLeft;
            int destPos;
            short firstWord;
            var initialTablePos = new byte[MAX_CHANNELS];
            //int32 initialimcTableEntry[MAX_CHANNELS] = {7, 7};
            var initialOutputWord = new int[MAX_CHANNELS];
            int totalBitOffset, curTablePos, outputWord;

            // We only support mono and stereo
            Debug.Assert(channels == 1 || channels == 2);

            var src = compInput;
            var dst = compOutput;
            int srcPos = 0;
            int dstPos = 0;
            outputSamplesLeft = 0x1000;

            // Every data packet contains 0x2000 bytes of audio data
            // when extracted. In order to encode bigger data sets,
            // one has to split the data into multiple blocks.
            //
            // Every block starts with a 2 byte word. If that word is
            // non-zero, it indicates the size of a block of raw audio
            // data (not encoded) following it. That data we simply copy
            // to the output buffer and then proceed by decoding the
            // remaining data.
            //
            // If on the other hand the word is zero, then what follows
            // are 7*channels bytes containing seed data for the decoder.
            firstWord = src.ToInt16BigEndian();
            srcPos += 2;
            if (firstWord != 0)
            {
                // Copy raw data
                Array.Copy(src, srcPos, dst, dstPos, firstWord);
                dstPos += firstWord;
                srcPos += firstWord;
                Debug.Assert((firstWord & 1) == 0);
                outputSamplesLeft -= firstWord / 2;
            }
            else
            {
                // Read the seed values for the decoder.
                for (var i = 0; i < channels; i++)
                {
                    initialTablePos[i] = src[srcPos];
                    srcPos += 1;
                    //initialimcTableEntry[i] = READ_BE_UINT32(src);
                    srcPos += 4;
                    initialOutputWord[i] = src.ToInt32BigEndian(srcPos);
                    srcPos += 4;
                }
            }

            totalBitOffset = 0;
            // The channels are encoded separately.
            for (int chan = 0; chan < channels; chan++)
            {
                // Read initial state (this makes it possible for the data stream
                // to be split & spread across multiple data chunks.
                curTablePos = initialTablePos[chan];
                //imcTableEntry = initialimcTableEntry[chan];
                outputWord = initialOutputWord[chan];

                // We need to interleave the channels in the output; we achieve
                // that by using a variables dest offset:
                destPos = chan * 2;

                var bound = (channels == 1)
                    ? outputSamplesLeft
                    : ((chan == 0)
                        ? (outputSamplesLeft + 1) / 2
                        : outputSamplesLeft / 2);
                for (var i = 0; i < bound; ++i)
                {
                    // Determine the size (in bits) of the next data packet
                    var curTableEntryBitCount = _destImcTable[curTablePos];
                    Debug.Assert(2 <= curTableEntryBitCount && curTableEntryBitCount <= 7);

                    // Read the next data packet
                    int readPos = srcPos + (totalBitOffset >> 3);
                    var readWord = (ushort)(src.ToInt16BigEndian(readPos) << (totalBitOffset & 7));
                    var packet = (byte)(readWord >> (16 - curTableEntryBitCount));

                    // Advance read position to the next data packet
                    totalBitOffset += curTableEntryBitCount;

                    // Decode the data packet into a delta value for the output signal.
                    byte signBitMask = (byte)(1 << (curTableEntryBitCount - 1));
                    byte dataBitMask = (byte)(signBitMask - 1);
                    byte data = (byte)(packet & dataBitMask);

                    var tmpA = (data << (7 - curTableEntryBitCount));
                    int imcTableEntry = Ima_ADPCMStream._imaTable[curTablePos] >> (curTableEntryBitCount - 1);
                    int delta = (int)(imcTableEntry + _destImcTable2[tmpA + (curTablePos * 64)]);

                    // The topmost bit in the data packet tells is a sign bit
                    if ((packet & signBitMask) != 0)
                    {
                        delta = -delta;
                    }

                    // Accumulate the delta onto the output data
                    outputWord += delta;

                    // Clip outputWord to 16 bit signed, and write it into the destination stream
                    outputWord = ScummHelper.Clip(outputWord, -0x8000, 0x7fff);
                    ScummHelper.WriteUInt16BigEndian(dst, dstPos + destPos, (ushort)outputWord);
                    destPos += channels << 1;

                    // Adjust the curTablePos
                    curTablePos += (sbyte)imxOtherTable[curTableEntryBitCount - 2][data];
                    curTablePos = ScummHelper.Clip(curTablePos, 0, Ima_ADPCMStream._imaTable.Length - 1);
                }
            }

            return 0x2000;
        }