From 13552cce55f6ccd09ac5e83c8a7dc3f2101dfa73 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Tue, 27 Jan 2009 11:46:54 +0000 Subject: Add support for looping ADPCM sounds. I'm also adding decoding of the "standard" IMA ADPCM code (aka Intel DVI ADPCM). This is from Mohawk. ;) svn-id: r36097 --- sound/adpcm.cpp | 156 ++++++++++++++++++++++++++++++++++++-------------------- sound/adpcm.h | 7 ++- 2 files changed, 106 insertions(+), 57 deletions(-) diff --git a/sound/adpcm.cpp b/sound/adpcm.cpp index 3fb6d4d87e..45480742c5 100644 --- a/sound/adpcm.cpp +++ b/sound/adpcm.cpp @@ -31,13 +31,11 @@ namespace Audio { -// TODO: Switch from a SeekableReadStream to a plain ReadStream. This requires -// some internal refactoring but is definitely possible and will increase the -// flexibility of this code. class ADPCMInputStream : public AudioStream { private: Common::SeekableReadStream *_stream; bool _disposeAfterUse; + int32 _startpos; int32 _endpos; int _channels; typesADPCM _type; @@ -47,6 +45,8 @@ private: uint16 _chunkData; int _blockLen; int _rate; + uint _numLoops; + uint _curLoop; struct ADPCMChannelStatus { byte predictor; @@ -58,9 +58,11 @@ private: }; struct adpcmStatus { - // IMA - int32 last; - int32 stepIndex; + // OKI/IMA + struct { + int32 last; + int32 stepIndex; + } ima_ch[2]; // MS ADPCM ADPCMChannelStatus ch[2]; @@ -71,18 +73,20 @@ private: double d0, d1; } _status; + void reset(); int16 stepAdjust(byte); int16 decodeOKI(byte); - int16 decodeMSIMA(byte); + int16 decodeIMA(byte code, bool right = false); // Default to using the left channel/using one channel int16 decodeMS(ADPCMChannelStatus *c, byte); int16 decodeTinsel(int16, double); public: - ADPCMInputStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels = 2, uint32 blockAlign = 0); + ADPCMInputStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels = 2, uint32 blockAlign = 0, uint numLoops = 1); ~ADPCMInputStream(); int readBuffer(int16 *buffer, const int numSamples); int readBufferOKI(int16 *buffer, const int numSamples); + int readBufferIMA(int16 *buffer, const int numSamples); int readBufferMSIMA1(int16 *buffer, const int numSamples); int readBufferMSIMA2(int16 *buffer, const int numSamples); int readBufferMS(int channels, int16 *buffer, const int numSamples); @@ -92,7 +96,7 @@ public: int readBufferTinsel8(int channels, int16 *buffer, const int numSamples); bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos); } - bool isStereo() const { return false; } + bool isStereo() const { return _channels == 2; } int getRate() const { return _rate; } }; @@ -100,32 +104,29 @@ public: // Dialogic or Oki ADPCM coding format aka VOX. // See also // +// IMA ADPCM support is based on +// +// +// Apple IMA ADPCM support is based on +// +// // In addition, also MS IMA ADPCM is supported. See // . -ADPCMInputStream::ADPCMInputStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) - : _stream(stream), _disposeAfterUse(disposeAfterUse), _channels(channels), _type(type), _blockAlign(blockAlign), _rate(rate) { - - _status.last = 0; - _status.stepIndex = 0; - _status.d0 = _status.d1 = 0; - memset(_status.ch, 0, sizeof(_status.ch)); - _endpos = stream->pos() + size; - _blockLen = 0; - _blockPos = _blockAlign; // To make sure first header is read - _chunkPos = 0; - _chunkData = 0; - +ADPCMInputStream::ADPCMInputStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign, uint numLoops) + : _stream(stream), _disposeAfterUse(disposeAfterUse), _channels(channels), _type(type), _blockAlign(blockAlign), _rate(rate), _numLoops(numLoops) { + if (type == kADPCMMSIma && blockAlign == 0) - error("ADPCMInputStream(): blockAlign isn't specifiled for MS IMA ADPCM"); + error("ADPCMInputStream(): blockAlign isn't specified for MS IMA ADPCM"); if (type == kADPCMMS && blockAlign == 0) - error("ADPCMInputStream(): blockAlign isn't specifiled for MS ADPCM"); + error("ADPCMInputStream(): blockAlign isn't specified for MS ADPCM"); + if (type == kADPCMTinsel4 && blockAlign == 0) - error("ADPCMInputStream(): blockAlign isn't specifiled for Tinsel 4-bit ADPCM"); + error("ADPCMInputStream(): blockAlign isn't specified for Tinsel 4-bit ADPCM"); if (type == kADPCMTinsel6 && blockAlign == 0) - error("ADPCMInputStream(): blockAlign isn't specifiled for Tinsel 6-bit ADPCM"); + error("ADPCMInputStream(): blockAlign isn't specified for Tinsel 6-bit ADPCM"); if (type == kADPCMTinsel8 && blockAlign == 0) - error("ADPCMInputStream(): blockAlign isn't specifiled for Tinsel 8-bit ADPCM"); + error("ADPCMInputStream(): blockAlign isn't specified for Tinsel 8-bit ADPCM"); if (type == kADPCMTinsel4 && channels != 1) error("ADPCMInputStream(): Tinsel 4-bit ADPCM only supports mono"); @@ -133,6 +134,11 @@ ADPCMInputStream::ADPCMInputStream(Common::SeekableReadStream *stream, bool disp error("ADPCMInputStream(): Tinsel 6-bit ADPCM only supports mono"); if (type == kADPCMTinsel8 && channels != 1) error("ADPCMInputStream(): Tinsel 8-bit ADPCM only supports mono"); + + _startpos = stream->pos(); + _endpos = _startpos + size; + _curLoop = 0; + reset(); } ADPCMInputStream::~ADPCMInputStream() { @@ -140,28 +146,57 @@ ADPCMInputStream::~ADPCMInputStream() { delete _stream; } +void ADPCMInputStream::reset() { + memset(&_status, 0, sizeof(_status)); + _blockLen = 0; + _blockPos = _blockAlign; // To make sure first header is read + _chunkPos = 0; + _blockPos = 0; +} + int ADPCMInputStream::readBuffer(int16 *buffer, const int numSamples) { + int samplesDecoded = 0; switch (_type) { case kADPCMOki: - return readBufferOKI(buffer, numSamples); + samplesDecoded = readBufferOKI(buffer, numSamples); + break; case kADPCMMSIma: if (_channels == 1) - return readBufferMSIMA1(buffer, numSamples); + samplesDecoded = readBufferMSIMA1(buffer, numSamples); else - return readBufferMSIMA2(buffer, numSamples); + samplesDecoded = readBufferMSIMA2(buffer, numSamples); + break; case kADPCMMS: - return readBufferMS(_channels, buffer, numSamples); + samplesDecoded = readBufferMS(_channels, buffer, numSamples); + break; case kADPCMTinsel4: - return readBufferTinsel4(_channels, buffer, numSamples); + samplesDecoded = readBufferTinsel4(_channels, buffer, numSamples); + break; case kADPCMTinsel6: - return readBufferTinsel6(_channels, buffer, numSamples); + samplesDecoded = readBufferTinsel6(_channels, buffer, numSamples); + break; case kADPCMTinsel8: - return readBufferTinsel8(_channels, buffer, numSamples); + samplesDecoded = readBufferTinsel8(_channels, buffer, numSamples); + break; + case kADPCMIma: + samplesDecoded = readBufferIMA(buffer, numSamples); + break; default: error("Unsupported ADPCM encoding"); break; } - return 0; + + // Loop if necessary + if (samplesDecoded < numSamples || _stream->pos() == _endpos) { + _curLoop++; + if (_numLoops == 0 || _curLoop < _numLoops) { + reset(); + _stream->seek(_startpos); + return samplesDecoded + readBuffer(buffer + samplesDecoded, numSamples - samplesDecoded); + } + } + + return samplesDecoded; } int ADPCMInputStream::readBufferOKI(int16 *buffer, const int numSamples) { @@ -178,6 +213,19 @@ int ADPCMInputStream::readBufferOKI(int16 *buffer, const int numSamples) { return samples; } +int ADPCMInputStream::readBufferIMA(int16 *buffer, const int numSamples) { + int samples; + byte data; + + assert(numSamples % 2 == 0); + + for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + buffer[samples] = TO_LE_16(decodeIMA((data >> 4) & 0x0f)); + buffer[samples + 1] = TO_LE_16(decodeIMA(data & 0x0f, _channels == 2)); + } + return samples; +} int ADPCMInputStream::readBufferMSIMA1(int16 *buffer, const int numSamples) { int samples = 0; @@ -188,16 +236,16 @@ int ADPCMInputStream::readBufferMSIMA1(int16 *buffer, const int numSamples) { while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { if (_blockPos == _blockAlign) { // read block header - _status.last = _stream->readSint16LE(); - _status.stepIndex = _stream->readSint16LE(); + _status.ima_ch[0].last = _stream->readSint16LE(); + _status.ima_ch[0].stepIndex = _stream->readSint16LE(); _blockPos = 4; } for (; samples < numSamples && _blockPos < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { data = _stream->readByte(); _blockPos++; - buffer[samples] = TO_LE_16(decodeMSIMA(data & 0x0f)); - buffer[samples + 1] = TO_LE_16(decodeMSIMA((data >> 4) & 0x0f)); + buffer[samples] = TO_LE_16(decodeIMA(data & 0x0f)); + buffer[samples + 1] = TO_LE_16(decodeIMA((data >> 4) & 0x0f)); } } return samples; @@ -218,7 +266,7 @@ int ADPCMInputStream::readBufferMSIMA2(int16 *buffer, const int numSamples) { for (nibble = 0; nibble < 8; nibble++) { k = ((data & 0xf0000000) >> 28); - buffer[samples + channel + nibble * 2] = TO_LE_16(decodeMSIMA(k)); + buffer[samples + channel + nibble * 2] = TO_LE_16(decodeIMA(k)); data <<= 4; } } @@ -446,21 +494,20 @@ static const int16 okiStepSize[49] = { int16 ADPCMInputStream::decodeOKI(byte code) { int16 diff, E, samp; - E = (2 * (code & 0x7) + 1) * okiStepSize[_status.stepIndex] / 8; + E = (2 * (code & 0x7) + 1) * okiStepSize[_status.ima_ch[0].stepIndex] / 8; diff = (code & 0x08) ? -E : E; - samp = _status.last + diff; + samp = _status.ima_ch[0].last + diff; // Clip the values to +/- 2^11 (supposed to be 12 bits) samp = CLIP(samp, -2048, 2047); - _status.last = samp; - _status.stepIndex += stepAdjust(code); - _status.stepIndex = CLIP(_status.stepIndex, 0, ARRAYSIZE(okiStepSize) - 1); + _status.ima_ch[0].last = samp; + _status.ima_ch[0].stepIndex += stepAdjust(code); + _status.ima_ch[0].stepIndex = CLIP(_status.ima_ch[0].stepIndex, 0, ARRAYSIZE(okiStepSize) - 1); // * 16 effectively converts 12-bit input to 16-bit output return samp * 16; } - static const uint16 imaStepTable[89] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, @@ -476,14 +523,14 @@ static const uint16 imaStepTable[89] = { 32767 }; -int16 ADPCMInputStream::decodeMSIMA(byte code) { - int32 E = (2 * (code & 0x7) + 1) * imaStepTable[_status.stepIndex] / 8; +int16 ADPCMInputStream::decodeIMA(byte code, bool right) { + int32 E = (2 * (code & 0x7) + 1) * imaStepTable[_status.ima_ch[right].stepIndex] / 8; int32 diff = (code & 0x08) ? -E : E; - int32 samp = CLIP(_status.last + diff, -32768, 32767); + int32 samp = CLIP(_status.ima_ch[right].last + diff, -32768, 32767); - _status.last = samp; - _status.stepIndex += stepAdjust(code); - _status.stepIndex = CLIP(_status.stepIndex, 0, ARRAYSIZE(imaStepTable) - 1); + _status.ima_ch[right].last = samp; + _status.ima_ch[right].stepIndex += stepAdjust(code); + _status.ima_ch[right].stepIndex = CLIP(_status.ima_ch[right].stepIndex, 0, ARRAYSIZE(imaStepTable) - 1); return samp; } @@ -501,9 +548,8 @@ int16 ADPCMInputStream::decodeTinsel(int16 code, double eVal) { return (int16) CLIP(sample, -32768.0, 32767.0); } -// TODO: Add optional support for sound looping -AudioStream *makeADPCMStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) { - return new ADPCMInputStream(stream, disposeAfterUse, size, type, rate, channels, blockAlign); +AudioStream *makeADPCMStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign, uint numLoops) { + return new ADPCMInputStream(stream, disposeAfterUse, size, type, rate, channels, blockAlign, numLoops); } } // End of namespace Audio diff --git a/sound/adpcm.h b/sound/adpcm.h index 72afdc6113..886897014b 100644 --- a/sound/adpcm.h +++ b/sound/adpcm.h @@ -45,7 +45,8 @@ enum typesADPCM { kADPCMMS, // Microsoft ADPCM kADPCMTinsel4, // 4-bit ADPCM used by the Tinsel engine kADPCMTinsel6, // 6-bit ADPCM used by the Tinsel engine - kADPCMTinsel8 // 8-bit ADPCM used by the Tinsel engine + kADPCMTinsel8, // 8-bit ADPCM used by the Tinsel engine + kADPCMIma // Standard IMA ADPCM }; /** @@ -59,6 +60,7 @@ enum typesADPCM { * @param rate the sampling rate (default = 22050) * @param channels the number of channels (default = 2) * @param blockAlign block alignment ??? (default = 0) + * @param numLoop how many types the sounds should loop, 0 for infinite loop (default = 1) * @return a new AudioStream, or NULL, if an error occured */ AudioStream *makeADPCMStream( @@ -68,7 +70,8 @@ AudioStream *makeADPCMStream( typesADPCM type, int rate = 22050, int channels = 2, - uint32 blockAlign = 0); + uint32 blockAlign = 0, + uint numLoops = 1); } // End of namespace Audio -- cgit v1.2.3