aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Hoops2009-01-27 11:46:54 +0000
committerMatthew Hoops2009-01-27 11:46:54 +0000
commit13552cce55f6ccd09ac5e83c8a7dc3f2101dfa73 (patch)
treed68fd26d9df8783855779a22dcc4f70ac55c757e
parent6ef26264f46b26a2d479d6de2a91cbb19f415cfe (diff)
downloadscummvm-rg350-13552cce55f6ccd09ac5e83c8a7dc3f2101dfa73.tar.gz
scummvm-rg350-13552cce55f6ccd09ac5e83c8a7dc3f2101dfa73.tar.bz2
scummvm-rg350-13552cce55f6ccd09ac5e83c8a7dc3f2101dfa73.zip
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
-rw-r--r--sound/adpcm.cpp156
-rw-r--r--sound/adpcm.h7
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 <http://www.comptek.ru/telephony/tnotes/tt1-13.html>
//
+// IMA ADPCM support is based on
+// <http://wiki.multimedia.cx/index.php?title=IMA_ADPCM>
+//
+// Apple IMA ADPCM support is based on
+// <http://wiki.multimedia.cx/index.php?title=Apple_QuickTime_IMA_ADPCM>
+//
// In addition, also MS IMA ADPCM is supported. See
// <http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM>.
-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<int16>(samp, -2048, 2047);
- _status.last = samp;
- _status.stepIndex += stepAdjust(code);
- _status.stepIndex = CLIP<int32>(_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<int32>(_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<int32>(_status.last + diff, -32768, 32767);
+ int32 samp = CLIP<int32>(_status.ima_ch[right].last + diff, -32768, 32767);
- _status.last = samp;
- _status.stepIndex += stepAdjust(code);
- _status.stepIndex = CLIP<int32>(_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<int32>(_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<double>(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