aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Sandulenko2006-04-22 03:00:21 +0000
committerEugene Sandulenko2006-04-22 03:00:21 +0000
commitff6028b00bca2a01ed41f8f8ed5ee5c86f8f1410 (patch)
treef407a005d05a4803131c2501b39978f3007f5b91
parent7b1e93b596b09f3e6138ee67866e36766b6dc335 (diff)
downloadscummvm-rg350-ff6028b00bca2a01ed41f8f8ed5ee5c86f8f1410.tar.gz
scummvm-rg350-ff6028b00bca2a01ed41f8f8ed5ee5c86f8f1410.tar.bz2
scummvm-rg350-ff6028b00bca2a01ed41f8f8ed5ee5c86f8f1410.zip
- Implemented MS ADPCM WAV format decoder used in Feeble Files. Still it is
out of sync with video. See TODO there. - Fixed bug with MS IMA ADPCM mono to make it possible to work in real streams. svn-id: r22080
-rw-r--r--engines/scumm/he/sound_he.cpp2
-rw-r--r--sound/adpcm.cpp144
-rw-r--r--sound/adpcm.h5
-rw-r--r--sound/wave.cpp21
4 files changed, 142 insertions, 30 deletions
diff --git a/engines/scumm/he/sound_he.cpp b/engines/scumm/he/sound_he.cpp
index a906a0b390..57a03ed129 100644
--- a/engines/scumm/he/sound_he.cpp
+++ b/engines/scumm/he/sound_he.cpp
@@ -419,7 +419,7 @@ void Sound::playHESound(int soundID, int heOffset, int heChannel, int heFlags) {
}
if (compType == 17) {
- AudioStream *voxStream = makeADPCMStream(&stream, size, kADPCMIma, (flags & Audio::Mixer::FLAG_STEREO) ? 2 : 1, blockAlign);
+ AudioStream *voxStream = makeADPCMStream(&stream, size, kADPCMMSIma, rate, (flags & Audio::Mixer::FLAG_STEREO) ? 2 : 1, blockAlign);
sound = (char *)malloc(size * 4);
size = voxStream->readBuffer((int16*)sound, size * 2);
diff --git a/sound/adpcm.cpp b/sound/adpcm.cpp
index d523bf84bc..d2e1e72aed 100644
--- a/sound/adpcm.cpp
+++ b/sound/adpcm.cpp
@@ -38,46 +38,67 @@ private:
int _channels;
typesADPCM _type;
uint32 _blockAlign;
+ uint32 _blockPos;
+ int _blockLen;
+ int _rate;
+
+ struct ADPCMChannelStatus {
+ byte predictor;
+ int16 delta;
+ int16 coeff1;
+ int16 coeff2;
+ int16 sample1;
+ int16 sample2;
+ };
struct adpcmStatus {
+ // IMA
int32 last;
int32 stepIndex;
+
+ // MS ADPCM
+ ADPCMChannelStatus ch[2];
} _status;
int16 stepAdjust(byte);
int16 decodeOKI(byte);
int16 decodeMSIMA(byte);
+ int16 decodeMS(ADPCMChannelStatus *c, byte);
public:
- ADPCMInputStream(Common::SeekableReadStream *stream, uint32 size, typesADPCM type, int channels = 2, uint32 blockAlign = 0);
+ ADPCMInputStream(Common::SeekableReadStream *stream, uint32 size, typesADPCM type, int rate, int channels = 2, uint32 blockAlign = 0);
~ADPCMInputStream() {};
int readBuffer(int16 *buffer, const int numSamples);
int readBufferOKI(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);
bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos); }
bool isStereo() const { return false; }
- int getRate() const { return 22050; }
+ int getRate() const { return _rate; }
};
// Routines to convert 12 bit linear samples to the
// Dialogic or Oki ADPCM coding format aka VOX.
// See also <http://www.comptek.ru/telephony/tnotes/tt1-13.html>
//
-// In addition, also IMA ADPCM is supported. See
-// <http://www.multimedia.cx/simpleaudio.html>.
+// In addition, also MS IMA ADPCM is supported. See
+// <http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM>.
-ADPCMInputStream::ADPCMInputStream(Common::SeekableReadStream *stream, uint32 size, typesADPCM type, int channels, uint32 blockAlign)
- : _stream(stream), _channels(channels), _type(type), _blockAlign(blockAlign) {
+ADPCMInputStream::ADPCMInputStream(Common::SeekableReadStream *stream, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign)
+ : _stream(stream), _channels(channels), _type(type), _blockAlign(blockAlign), _rate(rate) {
_status.last = 0;
_status.stepIndex = 0;
_endpos = stream->pos() + size;
+ _blockPos = _blockLen = 0;
- if (type == kADPCMIma && blockAlign == 0)
- error("ADPCMInputStream(): blockAlign isn't specifiled for MS ADPCM IMA");
+ if (type == kADPCMMSIma && blockAlign == 0)
+ error("ADPCMInputStream(): blockAlign isn't specifiled for MS IMA ADPCM");
+ if (type == kADPCMMS && blockAlign == 0)
+ error("ADPCMInputStream(): blockAlign isn't specifiled for MS ADPCM");
}
int ADPCMInputStream::readBuffer(int16 *buffer, const int numSamples) {
@@ -85,12 +106,15 @@ int ADPCMInputStream::readBuffer(int16 *buffer, const int numSamples) {
case kADPCMOki:
return readBufferOKI(buffer, numSamples);
break;
- case kADPCMIma:
+ case kADPCMMSIma:
if (_channels == 1)
return readBufferMSIMA1(buffer, numSamples);
else
return readBufferMSIMA2(buffer, numSamples);
break;
+ case kADPCMMS:
+ return readBufferMS(_channels, buffer, numSamples);
+ break;
default:
error("Unsupported ADPCM encoding");
break;
@@ -116,22 +140,22 @@ int ADPCMInputStream::readBufferOKI(int16 *buffer, const int numSamples) {
int ADPCMInputStream::readBufferMSIMA1(int16 *buffer, const int numSamples) {
int samples;
byte data;
- int blockLen;
- int i;
assert(numSamples % 2 == 0);
samples = 0;
while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
- // read block header
- _status.last = _stream->readSint16LE();
- _status.stepIndex = _stream->readSint16LE();
-
- blockLen = MIN(_endpos - _stream->pos(), _blockAlign - 4);
+ if (_blockPos == _blockAlign) {
+ // read block header
+ _status.last = _stream->readSint16LE();
+ _status.stepIndex = _stream->readSint16LE();
+ _blockPos = 4;
+ }
- for (i = 0; i < blockLen && !_stream->eos() && _stream->pos() < _endpos; i++, samples += 2) {
+ 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));
}
@@ -162,6 +186,61 @@ int ADPCMInputStream::readBufferMSIMA2(int16 *buffer, const int numSamples) {
return samples;
}
+static const int MSADPCMAdaptCoeff1[] = {
+ 256, 512, 0, 192, 240, 460, 392
+};
+
+static const int MSADPCMAdaptCoeff2[] = {
+ 0, -256, 0, 64, 0, -208, -232
+};
+
+int ADPCMInputStream::readBufferMS(int channels, int16 *buffer, const int numSamples) {
+ int samples;
+ byte data;
+ int stereo = channels - 1; // We use it in index
+
+ samples = 0;
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos == _blockAlign) {
+ // read block header
+ _status.ch[0].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
+ _status.ch[0].coeff1 = MSADPCMAdaptCoeff1[_status.ch[0].predictor];
+ _status.ch[0].coeff2 = MSADPCMAdaptCoeff2[_status.ch[0].predictor];
+ if (stereo) {
+ _status.ch[1].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
+ _status.ch[1].coeff1 = MSADPCMAdaptCoeff1[_status.ch[1].predictor];
+ _status.ch[1].coeff2 = MSADPCMAdaptCoeff2[_status.ch[1].predictor];
+ }
+
+ _status.ch[0].delta = _stream->readSint16LE();
+ if (stereo)
+ _status.ch[1].delta = _stream->readSint16LE();
+
+ buffer[samples++] = _status.ch[0].sample1 = _stream->readSint16LE();
+ if (stereo)
+ buffer[samples++] = _status.ch[1].sample1 = _stream->readSint16LE();
+
+ buffer[samples++] = _status.ch[0].sample2 = _stream->readSint16LE();
+ if (stereo)
+ buffer[samples++] = _status.ch[1].sample2 = _stream->readSint16LE();
+
+ _blockPos = channels * 7;
+ }
+
+
+ for (; samples < numSamples && _blockPos < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
+ data = _stream->readByte();
+ _blockPos++;
+ buffer[samples] = TO_LE_16(decodeMS(&_status.ch[0], (data >> 4) & 0x0f));
+ buffer[samples + 1] = TO_LE_16(decodeMS(&_status.ch[stereo], data & 0x0f));
+ }
+ }
+
+ return samples;
+}
+
+
// adjust the step for use on the next sample.
int16 ADPCMInputStream::stepAdjust(byte code) {
static const int16 adjusts[] = {-1, -1, -1, -1, 2, 4, 6, 8};
@@ -243,6 +322,33 @@ int16 ADPCMInputStream::decodeMSIMA(byte code) {
return samp;
}
-AudioStream *makeADPCMStream(Common::SeekableReadStream *stream, uint32 size, typesADPCM type, int channels, uint32 blockAlign) {
- return new ADPCMInputStream(stream, size, type, channels, blockAlign);
+static const int MSADPCMAdaptationTable[] = {
+ 230, 230, 230, 230, 307, 409, 512, 614,
+ 768, 614, 512, 409, 307, 230, 230, 230
+};
+
+
+int16 ADPCMInputStream::decodeMS(ADPCMChannelStatus *c, byte code) {
+ int32 predictor;
+
+ predictor = (((c->sample1) * (c->coeff1)) + ((c->sample2) * (c->coeff2))) / 256;
+ predictor += (signed)((code & 0x08) ? (code - 0x10) : (code)) * c->delta;
+
+ if (predictor < -0x8000)
+ predictor = -0x8000;
+ else if (predictor > 0x7fff)
+ predictor = 0x7fff;
+
+ c->sample2 = c->sample1;
+ c->sample1 = predictor;
+ c->delta = (MSADPCMAdaptationTable[(int)code] * c->delta) >> 8;
+
+ if (c->delta < 16)
+ c->delta = 16;
+
+ return (int16)predictor;
+}
+
+AudioStream *makeADPCMStream(Common::SeekableReadStream *stream, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) {
+ return new ADPCMInputStream(stream, size, type, rate, channels, blockAlign);
}
diff --git a/sound/adpcm.h b/sound/adpcm.h
index aea1beaf60..6db3acae00 100644
--- a/sound/adpcm.h
+++ b/sound/adpcm.h
@@ -31,10 +31,11 @@ class AudioStream;
enum typesADPCM {
kADPCMOki,
- kADPCMIma
+ kADPCMMSIma,
+ kADPCMMS
};
-AudioStream *makeADPCMStream(Common::SeekableReadStream *stream, uint32 size, typesADPCM type, int channels = 2, uint32 blockAlign = 0);
+AudioStream *makeADPCMStream(Common::SeekableReadStream *stream, uint32 size, typesADPCM type, int rate = 22050, int channels = 2, uint32 blockAlign = 0);
#endif
diff --git a/sound/wave.cpp b/sound/wave.cpp
index 264e4ae7bb..dbee606a39 100644
--- a/sound/wave.cpp
+++ b/sound/wave.cpp
@@ -95,16 +95,16 @@ bool loadWAVFromStream(Common::SeekableReadStream &stream, int &size, int &rate,
printf(" bitsPerSample: %d\n", bitsPerSample);
#endif
- if (type != 1 && type != 17) {
- warning("getWavInfo: only PCM or IMA ADPCM data is supported (type %d)", type);
+ if (type != 1 && type != 2 && type != 17) {
+ warning("getWavInfo: only PCM, MS ADPCM or IMA ADPCM data is supported (type %d)", type);
return false;
}
- if (blockAlign != numChannels * bitsPerSample / 8) {
+ if (blockAlign != numChannels * bitsPerSample / 8 && type != 2) {
debug(0, "getWavInfo: blockAlign is invalid");
}
- if (avgBytesPerSec != samplesPerSec * blockAlign) {
+ if (avgBytesPerSec != samplesPerSec * blockAlign && type != 2) {
debug(0, "getWavInfo: avgBytesPerSec is invalid");
}
@@ -116,7 +116,9 @@ bool loadWAVFromStream(Common::SeekableReadStream &stream, int &size, int &rate,
flags |= Audio::Mixer::FLAG_UNSIGNED;
else if (bitsPerSample == 16) // 16 bit data is signed little endian
flags |= (Audio::Mixer::FLAG_16BITS | Audio::Mixer::FLAG_LITTLE_ENDIAN);
- else if (bitsPerSample == 4 && type == 17) // IMA ADPCM compressed. We decompress it
+ else if (bitsPerSample == 4 && type == 17) // MS IMA ADPCM compressed. We decompress it
+ flags |= (Audio::Mixer::FLAG_16BITS | Audio::Mixer::FLAG_LITTLE_ENDIAN);
+ else if (bitsPerSample == 4 && type == 2) // MS ADPCM compressed. We decompress it
flags |= (Audio::Mixer::FLAG_16BITS | Audio::Mixer::FLAG_LITTLE_ENDIAN);
else {
warning("getWavInfo: unsupported bitsPerSample %d", bitsPerSample);
@@ -160,12 +162,15 @@ AudioStream *makeWAVStream(Common::SeekableReadStream &stream) {
int size, rate;
byte flags;
uint16 type;
+ int blockAlign;
- if (!loadWAVFromStream(stream, size, rate, flags, &type))
+ if (!loadWAVFromStream(stream, size, rate, flags, &type, &blockAlign))
return 0;
- if (type == 17) // IMA ADPCM
- return makeADPCMStream(&stream, size, kADPCMIma, (flags & Audio::Mixer::FLAG_STEREO) ? 2 : 1);
+ if (type == 17) // MS IMA ADPCM
+ return makeADPCMStream(&stream, size, kADPCMMSIma, rate, (flags & Audio::Mixer::FLAG_STEREO) ? 2 : 1);
+ if (type == 2) // MS ADPCM
+ return makeADPCMStream(&stream, size, kADPCMMS, rate, (flags & Audio::Mixer::FLAG_STEREO) ? 2 : 1, blockAlign);
byte *data = (byte *)malloc(size);
assert(data);