diff options
Diffstat (limited to 'engines/saga/shorten.cpp')
-rw-r--r-- | engines/saga/shorten.cpp | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/engines/saga/shorten.cpp b/engines/saga/shorten.cpp new file mode 100644 index 0000000000..17f52fe924 --- /dev/null +++ b/engines/saga/shorten.cpp @@ -0,0 +1,531 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "saga/shorten.h" + +#ifdef SOUND_SHORTEN_H + +// Based on etree's Shorten tool, version 3.6.1 +// http://etree.org/shnutils/shorten/ + +// FIXME: This doesn't work yet correctly + +#include "common/endian.h" +#include "common/util.h" + +#include "sound/mixer.h" +#include "sound/decoders/raw.h" + +namespace Saga { + +#define MASKTABSIZE 33 +#define MAX_SUPPORTED_VERSION 3 +#define DEFAULT_BLOCK_SIZE 256 + +enum kShortenTypes { + kTypeAU1 = 0, // lossless ulaw + kTypeS8 = 1, // signed 8 bit + kTypeU8 = 2, // unsigned 8 bit + kTypeS16HL = 3, // signed 16 bit shorts: high-low + kTypeU16HL = 4, // unsigned 16 bit shorts: high-low + kTypeS16LH = 5, // signed 16 bit shorts: low-high + kTypeU16LH = 6, // unsigned 16 bit shorts: low-high + kTypeULaw = 7, // lossy ulaw + kTypeAU2 = 8, // new ulaw with zero mapping + kTypeAU3 = 9, // lossless alaw + kTypeALaw = 10, // lossy alaw + kTypeWAV = 11, // WAV + kTypeAIFF = 12, // AIFF + kTypeEOF = 13, + kTypeGenericULaw = 128, + kTypeGenericALaw = 129 +}; + +enum kShortenCommands { + kCmdDiff0 = 0, + kCmdDiff1 = 1, + kCmdDiff2 = 2, + kCmdDiff3 = 3, + kCmdQuit = 4, + kCmdBlockSize = 5, + kCmdBitShift = 6, + kCmdQLPC = 7, + kCmdZero = 8, + kCmdVerbatim = 9 +}; + +#ifndef M_LN2 +#define M_LN2 0.69314718055994530942 +#endif + +// --------------------------------------------------------------------------- + +class ShortenGolombReader { +public: + ShortenGolombReader(Common::ReadStream *stream, int version); + ~ShortenGolombReader() {} + uint32 getUint32(uint32 numBits); // UINT_GET + int32 getURice(uint32 numBits); // uvar_get + int32 getSRice(uint32 numBits); // var_get +private: + int _version; + uint32 _nbitget; + uint32 _buf; + uint32 _masktab[MASKTABSIZE]; + Common::ReadStream *_stream; +}; + +ShortenGolombReader::ShortenGolombReader(Common::ReadStream *stream, int version) { + _stream = stream; + _version = version; + uint32 val = 0; + _masktab[0] = 0; + _nbitget = 0; + _buf = 0; + + for (int i = 1; i < MASKTABSIZE; i++) { + val <<= 1; + val |= 1; + _masktab[i] = val; + } +} + +int32 ShortenGolombReader::getURice(uint32 numBits) { + int32 result = 0; + + if (!_nbitget) { + _buf = _stream->readUint32BE(); + _nbitget = 32; + } + + for (result = 0; !(_buf & (1L << --_nbitget)); result++) { + if (!_nbitget) { + _buf = _stream->readUint32BE(); + _nbitget = 32; + } + } + + while (numBits != 0) { + if (_nbitget >= numBits) { + result = (result << numBits) | ((_buf >> (_nbitget - numBits)) & _masktab[numBits]); + _nbitget -= numBits; + numBits = 0; + } else { + result = (result << _nbitget) | (_buf & _masktab[_nbitget]); + _buf = _stream->readUint32BE(); + numBits -= _nbitget; + _nbitget = 32; + } + } + + return result; +} + +int32 ShortenGolombReader::getSRice(uint32 numBits) { + uint32 uvar = (uint32) getURice(numBits + 1); + return (uvar & 1) ? (int32) ~(uvar >> 1) : (int32) (uvar >> 1); +} + +uint32 ShortenGolombReader::getUint32(uint32 numBits) { + return (_version == 0) ? (uint32)getURice(numBits) : (uint32)getURice(getURice(2)); +} + +// --------------------------------------------------------------------------- + +byte *loadShortenFromStream(Common::ReadStream &stream, int &size, int &rate, byte &flags) { + int32 *buffer[2], *offset[2]; // up to 2 channels + byte *unpackedBuffer = 0; + byte *pBuf = unpackedBuffer; + int prevSize = 0; + int32 *lpc = 0; + + ShortenGolombReader *gReader; + uint32 i, j, version, mean, type, channels, blockSize; + uint32 maxLPC = 0, lpcqOffset = 0; + int32 bitShift = 0, wrap = 0; + flags = 0; + size = 0; + + // Read header + byte magic[4]; + stream.read(magic, 4); + if (memcmp(magic, "ajkg", 4) != 0) { + warning("loadShortenFromStream: No 'ajkg' header"); + return NULL; + } + + version = stream.readByte(); + + if (version > MAX_SUPPORTED_VERSION) { + warning("loadShortenFromStream: Can't decode version %d, maximum supported version is %d", version, MAX_SUPPORTED_VERSION); + return NULL; + } + + mean = (version < 2) ? 0 : 4; + + gReader = new ShortenGolombReader(&stream, version); + + // Get file type + type = gReader->getUint32(4); + + switch (type) { + case kTypeS8: + break; + case kTypeU8: + flags |= Audio::FLAG_UNSIGNED; + break; + case kTypeS16LH: + flags |= Audio::FLAG_LITTLE_ENDIAN; + case kTypeS16HL: + flags |= Audio::FLAG_16BITS; + break; + case kTypeU16LH: + flags |= Audio::FLAG_LITTLE_ENDIAN; + case kTypeU16HL: + flags |= Audio::FLAG_16BITS; + flags |= Audio::FLAG_UNSIGNED; + break; + case kTypeWAV: + // TODO: Perhaps implement this if we find WAV Shorten encoded files + warning("loadShortenFromStream: Type WAV is not supported"); + delete gReader; + return NULL; + case kTypeAIFF: + // TODO: Perhaps implement this if we find AIFF Shorten encoded files + warning("loadShortenFromStream: Type AIFF is not supported"); + delete gReader; + return NULL; + case kTypeAU1: + case kTypeAU2: + case kTypeAU3: + case kTypeULaw: + case kTypeALaw: + case kTypeEOF: + case kTypeGenericULaw: + case kTypeGenericALaw: + default: + warning("loadShortenFromStream: Type %d is not supported", type); + delete gReader; + return NULL; + } + + // Get channels + channels = gReader->getUint32(0); + if (channels != 1 && channels != 2) { + warning("loadShortenFromStream: Only 1 or 2 channels are supported, stream contains %d channels", channels); + delete gReader; + return NULL; + } + + // Get block size + if (version > 0) { + blockSize = gReader->getUint32((int) (log((double) DEFAULT_BLOCK_SIZE) / M_LN2)); + maxLPC = gReader->getUint32(2); + mean = gReader->getUint32(0); + uint32 skipBytes = gReader->getUint32(1); + if (skipBytes > 0) { + prevSize = size; + size += skipBytes; + unpackedBuffer = (byte *) realloc(unpackedBuffer, size); + pBuf = unpackedBuffer + prevSize; + for (i = 0; i < skipBytes; i++) { + *pBuf++ = gReader->getUint32(7) & 0xFF; + } + } + } else { + blockSize = DEFAULT_BLOCK_SIZE; + } + + wrap = MAX<uint32>(3, maxLPC); + + // Initialize buffers + for (i = 0; i < channels; i++) { + buffer[i] = (int32 *)malloc((blockSize + wrap) * 4); + offset[i] = (int32 *)malloc((MAX<uint32>(1, mean)) * 4); + memset(buffer[i], 0, (blockSize + wrap) * 4); + memset(offset[i], 0, (MAX<uint32>(1, mean)) * 4); + } + + if (maxLPC > 0) + lpc = (int32 *) malloc(maxLPC * 4); + + if (version > 1) + lpcqOffset = 1 << 5; + + // Init offset + int32 offsetMean = 0; + uint32 blocks = MAX<int>(1, mean); + + if (type == kTypeU8) + offsetMean = 0x80; + else if (type == kTypeU16HL || type == kTypeU16LH) + offsetMean = 0x8000; + + for (uint32 channel = 0; channel < channels; channel++) + for (uint32 block = 0; block < blocks; block++) + offset[channel][block] = offsetMean; + + + uint32 curChannel = 0, cmd = 0; + + // Parse Shorten commands + while (true) { + cmd = gReader->getURice(2); + + if (cmd == kCmdQuit) + break; + + switch (cmd) { + case kCmdZero: + case kCmdDiff0: + case kCmdDiff1: + case kCmdDiff2: + case kCmdDiff3: + case kCmdQLPC: + { + int32 channelOffset = 0, energy = 0; + uint32 lpcNum = 0; + + if (cmd != kCmdZero) { + energy = gReader->getURice(3); + // hack for version 0 + if (version == 0) + energy--; + } + + // Find mean offset (code duplicated below) + if (mean == 0) { + channelOffset = offset[curChannel][0]; + } else { + int32 sum = (version < 2) ? 0 : mean / 2; + for (i = 0; i < mean; i++) + sum += offset[curChannel][i]; + + channelOffset = sum / mean; + + if (version >= 2 && bitShift > 0) + channelOffset = (channelOffset >> (bitShift - 1)) >> 1; + } + + // FIXME: The original code in this bit tries to modify memory outside of the array (negative indices) + // in cases kCmdDiff1, kCmdDiff2 and kCmdDiff3 + // I've removed those invalid writes, since they happen all the time (even when curChannel is 0) + switch (cmd) { + case kCmdZero: + for (i = 0; i < blockSize; i++) + buffer[curChannel][i] = 0; + break; + case kCmdDiff0: + for (i = 0; i < blockSize; i++) + buffer[curChannel][i] = gReader->getSRice(energy) + channelOffset; + break; + case kCmdDiff1: + gReader->getSRice(energy); // i = 0 (to fix invalid table/memory access) + for (i = 1; i < blockSize; i++) + buffer[curChannel][i] = gReader->getSRice(energy) + buffer[curChannel][i - 1]; + break; + case kCmdDiff2: + gReader->getSRice(energy); // i = 0 (to fix invalid table/memory access) + gReader->getSRice(energy); // i = 1 (to fix invalid table/memory access) + for (i = 2; i < blockSize; i++) + buffer[curChannel][i] = gReader->getSRice(energy) + 2 * buffer[curChannel][i - 1] - buffer[curChannel][i - 2]; + break; + case kCmdDiff3: + gReader->getSRice(energy); // i = 0 (to fix invalid table/memory access) + gReader->getSRice(energy); // i = 1 (to fix invalid table/memory access) + gReader->getSRice(energy); // i = 2 (to fix invalid table/memory access) + for (i = 3; i < blockSize; i++) + buffer[curChannel][i] = gReader->getSRice(energy) + 3 * (buffer[curChannel][i - 1] - buffer[curChannel][i - 2]) + buffer[curChannel][i - 3]; + break; + case kCmdQLPC: + lpcNum = gReader->getURice(2); + + // Safeguard: if maxLPC < lpcNum, realloc the lpc buffer + if (maxLPC < lpcNum) { + warning("Safeguard: maxLPC < lpcNum (should never happen)"); + maxLPC = lpcNum; + lpc = (int32 *) realloc(lpc, maxLPC * 4); + } + + for (i = 0; i < lpcNum; i++) + lpc[i] = gReader->getSRice(5); + + for (i = 0; i < lpcNum; i++) + buffer[curChannel][i - lpcNum] -= channelOffset; + + for (i = 0; i < blockSize; i++) { + int32 sum = lpcqOffset; + for (j = 0; j < lpcNum; j++) { + // FIXME: The original code did an invalid memory access here + // (if i and j are 0, the array index requested is -1) + // I've removed those invalid writes, since they happen all the time (even when curChannel is 0) + if (i <= j) // ignore invalid table/memory access + continue; + sum += lpc[j] * buffer[curChannel][i - j - 1]; + } + buffer[curChannel][i] = gReader->getSRice(energy) + (sum >> 5); + } + + if (channelOffset > 0) + for (i = 0; i < blockSize; i++) + buffer[curChannel][i] += channelOffset; + + break; + } + + // Store mean value, if appropriate (duplicated code from above) + if (mean > 0) { + int32 sum = (version < 2) ? 0 : blockSize / 2; + for (i = 0; i < blockSize; i++) + sum += buffer[curChannel][i]; + + for (i = 1; i < mean; i++) + offset[curChannel][i - 1] = offset[curChannel][i]; + + offset[curChannel][mean - 1] = sum / blockSize; + + if (version >= 2 && bitShift > 0) + offset[curChannel][mean - 1] = offset[curChannel][mean - 1] << bitShift; + } + + + // Do the wrap + // FIXME: removed for now, as this corrupts the heap, because it + // accesses negative array indices + //for (int32 k = -wrap; k < 0; k++) + // buffer[curChannel][k] = buffer[curChannel][k + blockSize]; + + // Fix bitshift + if (bitShift > 0) { + for (i = 0; i < blockSize; i++) + buffer[curChannel][i] <<= bitShift; + } + + if (curChannel == channels - 1) { + int dataSize = (flags & Audio::FLAG_16BITS) ? 2 : 1; + int limit = (flags & Audio::FLAG_16BITS) ? 32767 : 127; + limit = (flags & Audio::FLAG_UNSIGNED) ? limit * 2 + 1 : limit; + + prevSize = size; + size += (blockSize * dataSize); + unpackedBuffer = (byte *) realloc(unpackedBuffer, size); + pBuf = unpackedBuffer + prevSize; + + if (flags & Audio::FLAG_16BITS) { + for (i = 0; i < blockSize; i++) { + for (j = 0; j < channels; j++) { + int16 val = (int16)(MIN<int32>(buffer[j][i], limit) & 0xFFFF); + // values are written in LE + *pBuf++ = (byte) (val & 0xFF); + *pBuf++ = (byte) ((val >> 8) & 0xFF); + } + } + } else { + for (i = 0; i < blockSize; i++) + for (j = 0; j < channels; j++) + *pBuf++ = (byte)(MIN<int32>(buffer[j][i], limit) & 0xFF); + } + } + curChannel = (curChannel + 1) % channels; + + } + break; + case kCmdBlockSize: + blockSize = gReader->getUint32((uint32)log((double) blockSize / M_LN2)); + break; + case kCmdBitShift: + bitShift = gReader->getURice(2); + break; + case kCmdVerbatim: + { + + uint32 vLen = (uint32)gReader->getURice(5); + prevSize = size; + size += vLen; + unpackedBuffer = (byte *) realloc(unpackedBuffer, size); + pBuf = unpackedBuffer + prevSize; + + while (vLen--) { + *pBuf++ = (byte)(gReader->getURice(8) & 0xFF); + } + + } + break; + default: + warning("loadShortenFromStream: Unknown command: %d", cmd); + + // Cleanup + for (i = 0; i < channels; i++) { + free(buffer[i]); + free(offset[i]); + } + + if (maxLPC > 0) + free(lpc); + + if (size > 0) + free(unpackedBuffer); + + delete gReader; + return NULL; + break; + } + } + + // Rate is always 44100Hz + rate = 44100; + + // Cleanup + for (i = 0; i < channels; i++) { + free(buffer[i]); + free(offset[i]); + } + + if (maxLPC > 0) + free(lpc); + + if (size > 0) + free(unpackedBuffer); + + delete gReader; + return unpackedBuffer; +} + +Audio::AudioStream *makeShortenStream(Common::SeekableReadStream &stream) { + int size, rate; + byte *data, flags; + data = loadShortenFromStream(stream, size, rate, flags); + + if (!data) + return 0; + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return Audio::makeRawMemoryStream(data, size, rate, flags); +} + +} // End of namespace Audio + +#endif // defined(SOUND_SHORTEN_H) + |