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;
}