aboutsummaryrefslogtreecommitdiff
path: root/audio/decoders
diff options
context:
space:
mode:
Diffstat (limited to 'audio/decoders')
-rw-r--r--audio/decoders/adpcm.cpp851
-rw-r--r--audio/decoders/adpcm.h90
-rw-r--r--audio/decoders/aiff.cpp186
-rw-r--r--audio/decoders/aiff.h71
-rw-r--r--audio/decoders/flac.cpp745
-rw-r--r--audio/decoders/flac.h75
-rw-r--r--audio/decoders/iff_sound.cpp130
-rw-r--r--audio/decoders/iff_sound.h47
-rw-r--r--audio/decoders/mac_snd.cpp116
-rw-r--r--audio/decoders/mac_snd.h58
-rw-r--r--audio/decoders/mp3.cpp375
-rw-r--r--audio/decoders/mp3.h76
-rw-r--r--audio/decoders/raw.cpp356
-rw-r--r--audio/decoders/raw.h153
-rw-r--r--audio/decoders/vag.cpp150
-rw-r--r--audio/decoders/vag.h60
-rw-r--r--audio/decoders/voc.cpp403
-rw-r--r--audio/decoders/voc.h107
-rw-r--r--audio/decoders/vorbis.cpp262
-rw-r--r--audio/decoders/vorbis.h75
-rw-r--r--audio/decoders/wave.cpp194
-rw-r--r--audio/decoders/wave.h84
22 files changed, 4664 insertions, 0 deletions
diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp
new file mode 100644
index 0000000000..7def89b688
--- /dev/null
+++ b/audio/decoders/adpcm.cpp
@@ -0,0 +1,851 @@
+/* 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 "common/endian.h"
+
+#include "audio/decoders/adpcm.h"
+#include "audio/audiostream.h"
+
+
+namespace Audio {
+
+class ADPCMStream : public RewindableAudioStream {
+protected:
+ Common::SeekableReadStream *_stream;
+ const DisposeAfterUse::Flag _disposeAfterUse;
+ const int32 _startpos;
+ const int32 _endpos;
+ const int _channels;
+ const uint32 _blockAlign;
+ uint32 _blockPos[2];
+ const int _rate;
+
+ struct {
+ // OKI/IMA
+ struct {
+ int32 last;
+ int32 stepIndex;
+ } ima_ch[2];
+ } _status;
+
+ virtual void reset();
+ int16 stepAdjust(byte);
+
+public:
+ ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign);
+ ~ADPCMStream();
+
+ virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos); }
+ virtual bool isStereo() const { return _channels == 2; }
+ virtual int getRate() const { return _rate; }
+
+ virtual bool rewind();
+};
+
+// 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>
+//
+// IMA ADPCM support is based on
+// <http://wiki.multimedia.cx/index.php?title=IMA_ADPCM>
+//
+// In addition, also MS IMA ADPCM is supported. See
+// <http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM>.
+
+ADPCMStream::ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : _stream(stream),
+ _disposeAfterUse(disposeAfterUse),
+ _startpos(stream->pos()),
+ _endpos(_startpos + size),
+ _channels(channels),
+ _blockAlign(blockAlign),
+ _rate(rate) {
+
+ reset();
+}
+
+ADPCMStream::~ADPCMStream() {
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _stream;
+}
+
+void ADPCMStream::reset() {
+ memset(&_status, 0, sizeof(_status));
+ _blockPos[0] = _blockPos[1] = _blockAlign; // To make sure first header is read
+}
+
+bool ADPCMStream::rewind() {
+ // TODO: Error checking.
+ reset();
+ _stream->seek(_startpos);
+ return true;
+}
+
+
+#pragma mark -
+
+
+class Oki_ADPCMStream : public ADPCMStream {
+public:
+ Oki_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {}
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+protected:
+ int16 decodeOKI(byte);
+};
+
+int Oki_ADPCMStream::readBuffer(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] = decodeOKI((data >> 4) & 0x0f);
+ buffer[samples + 1] = decodeOKI(data & 0x0f);
+ }
+ return samples;
+}
+
+static const int16 okiStepSize[49] = {
+ 16, 17, 19, 21, 23, 25, 28, 31,
+ 34, 37, 41, 45, 50, 55, 60, 66,
+ 73, 80, 88, 97, 107, 118, 130, 143,
+ 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658,
+ 724, 796, 876, 963, 1060, 1166, 1282, 1411,
+ 1552
+};
+
+// Decode Linear to ADPCM
+int16 Oki_ADPCMStream::decodeOKI(byte code) {
+ int16 diff, E, samp;
+
+ E = (2 * (code & 0x7) + 1) * okiStepSize[_status.ima_ch[0].stepIndex] / 8;
+ diff = (code & 0x08) ? -E : E;
+ 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.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;
+}
+
+
+#pragma mark -
+
+
+class Ima_ADPCMStream : public ADPCMStream {
+protected:
+ int16 decodeIMA(byte code, int channel = 0); // Default to using the left channel/using one channel
+
+public:
+ Ima_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ memset(&_status, 0, sizeof(_status));
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+};
+
+int Ima_ADPCMStream::readBuffer(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] = decodeIMA((data >> 4) & 0x0f);
+ buffer[samples + 1] = decodeIMA(data & 0x0f, _channels == 2 ? 1 : 0);
+ }
+ return samples;
+}
+
+#pragma mark -
+
+
+class Apple_ADPCMStream : public Ima_ADPCMStream {
+protected:
+ // Apple QuickTime IMA ADPCM
+ int32 _streamPos[2];
+ int16 _buffer[2][2];
+ uint8 _chunkPos[2];
+
+ void reset() {
+ Ima_ADPCMStream::reset();
+ _chunkPos[0] = 0;
+ _chunkPos[1] = 0;
+ _streamPos[0] = 0;
+ _streamPos[1] = _blockAlign;
+ }
+
+public:
+ Apple_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ _chunkPos[0] = 0;
+ _chunkPos[1] = 0;
+ _streamPos[0] = 0;
+ _streamPos[1] = _blockAlign;
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+};
+
+int Apple_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ // Need to write at least one samples per channel
+ assert((numSamples % _channels) == 0);
+
+ // Current sample positions
+ int samples[2] = { 0, 0};
+
+ // Number of samples per channel
+ int chanSamples = numSamples / _channels;
+
+ for (int i = 0; i < _channels; i++) {
+ _stream->seek(_streamPos[i]);
+
+ while ((samples[i] < chanSamples) &&
+ // Last byte read and a new one needed
+ !((_stream->eos() || (_stream->pos() >= _endpos)) && (_chunkPos[i] == 0))) {
+
+ if (_blockPos[i] == _blockAlign) {
+ // 2 byte header per block
+ uint16 temp = _stream->readUint16BE();
+
+ // First 9 bits are the upper bits of the predictor
+ _status.ima_ch[i].last = (int16) (temp & 0xFF80);
+ // Lower 7 bits are the step index
+ _status.ima_ch[i].stepIndex = temp & 0x007F;
+
+ // Clip the step index
+ _status.ima_ch[i].stepIndex = CLIP<int32>(_status.ima_ch[i].stepIndex, 0, 88);
+
+ _blockPos[i] = 2;
+ }
+
+ if (_chunkPos[i] == 0) {
+ // Decode data
+ byte data = _stream->readByte();
+ _buffer[i][0] = decodeIMA(data & 0x0F, i);
+ _buffer[i][1] = decodeIMA(data >> 4, i);
+ }
+
+ // The original is interleaved block-wise, we want it sample-wise
+ buffer[_channels * samples[i] + i] = _buffer[i][_chunkPos[i]];
+
+ if (++_chunkPos[i] > 1) {
+ // We're about to decode the next byte, so advance the block position
+ _chunkPos[i] = 0;
+ _blockPos[i]++;
+ }
+
+ samples[i]++;
+
+ if (_channels == 2)
+ if (_blockPos[i] == _blockAlign)
+ // We're at the end of the block.
+ // Since the channels are interleaved, skip the next block
+ _stream->skip(MIN<uint32>(_blockAlign, _endpos - _stream->pos()));
+
+ _streamPos[i] = _stream->pos();
+ }
+ }
+
+ return samples[0] + samples[1];
+}
+
+#pragma mark -
+
+
+class MSIma_ADPCMStream : public Ima_ADPCMStream {
+public:
+ MSIma_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign, bool invertSamples = false)
+ : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign), _invertSamples(invertSamples) {
+ if (blockAlign == 0)
+ error("ADPCMStream(): blockAlign isn't specified for MS IMA ADPCM");
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples) {
+ if (_channels == 1)
+ return readBufferMSIMA1(buffer, numSamples);
+ else
+ return readBufferMSIMA2(buffer, numSamples);
+ }
+
+ int readBufferMSIMA1(int16 *buffer, const int numSamples);
+ int readBufferMSIMA2(int16 *buffer, const int numSamples);
+
+private:
+ bool _invertSamples; // Some implementations invert the way samples are decoded
+};
+
+int MSIma_ADPCMStream::readBufferMSIMA1(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ byte data;
+
+ assert(numSamples % 2 == 0);
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ // read block header
+ _status.ima_ch[0].last = _stream->readSint16LE();
+ _status.ima_ch[0].stepIndex = _stream->readSint16LE();
+ _blockPos[0] = 4;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
+ data = _stream->readByte();
+ _blockPos[0]++;
+ buffer[samples] = decodeIMA(_invertSamples ? (data >> 4) & 0x0f : data & 0x0f);
+ buffer[samples + 1] = decodeIMA(_invertSamples ? data & 0x0f : (data >> 4) & 0x0f);
+ }
+ }
+ return samples;
+}
+
+
+// Microsoft as usual tries to implement it differently. This method
+// is used for stereo data.
+int MSIma_ADPCMStream::readBufferMSIMA2(int16 *buffer, const int numSamples) {
+ int samples;
+ uint32 data;
+ int nibble;
+ byte k;
+
+ // TODO: Currently this implementation only supports
+ // reading a multiple of 16 samples at once. We might
+ // consider changing that so it could read an arbitrary
+ // sample pair count.
+ assert(numSamples % 16 == 0);
+
+ for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos;) {
+ for (int channel = 0; channel < 2; channel++) {
+ data = _stream->readUint32LE();
+
+ for (nibble = 0; nibble < 8; nibble++) {
+ k = ((data & 0xf0000000) >> 28);
+ buffer[samples + channel + nibble * 2] = decodeIMA(k);
+ data <<= 4;
+ }
+ }
+ samples += 16;
+ }
+ return samples;
+}
+
+
+#pragma mark -
+
+
+static const int MSADPCMAdaptCoeff1[] = {
+ 256, 512, 0, 192, 240, 460, 392
+};
+
+static const int MSADPCMAdaptCoeff2[] = {
+ 0, -256, 0, 64, 0, -208, -232
+};
+
+static const int MSADPCMAdaptationTable[] = {
+ 230, 230, 230, 230, 307, 409, 512, 614,
+ 768, 614, 512, 409, 307, 230, 230, 230
+};
+
+
+class MS_ADPCMStream : public ADPCMStream {
+protected:
+ struct ADPCMChannelStatus {
+ byte predictor;
+ int16 delta;
+ int16 coeff1;
+ int16 coeff2;
+ int16 sample1;
+ int16 sample2;
+ };
+
+ struct {
+ // MS ADPCM
+ ADPCMChannelStatus ch[2];
+ } _status;
+
+ void reset() {
+ ADPCMStream::reset();
+ memset(&_status, 0, sizeof(_status));
+ }
+
+public:
+ MS_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ if (blockAlign == 0)
+ error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM");
+ memset(&_status, 0, sizeof(_status));
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+protected:
+ int16 decodeMS(ADPCMChannelStatus *c, byte);
+};
+
+int16 MS_ADPCMStream::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;
+
+ predictor = CLIP<int32>(predictor, -32768, 32767);
+
+ 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;
+}
+
+int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ byte data;
+ int i = 0;
+
+ samples = 0;
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ // read block header
+ for (i = 0; i < _channels; i++) {
+ _status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6);
+ _status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor];
+ _status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor];
+ }
+
+ for (i = 0; i < _channels; i++)
+ _status.ch[i].delta = _stream->readSint16LE();
+
+ for (i = 0; i < _channels; i++)
+ _status.ch[i].sample1 = _stream->readSint16LE();
+
+ for (i = 0; i < _channels; i++)
+ buffer[samples++] = _status.ch[i].sample2 = _stream->readSint16LE();
+
+ for (i = 0; i < _channels; i++)
+ buffer[samples++] = _status.ch[i].sample1;
+
+ _blockPos[0] = _channels * 7;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) {
+ data = _stream->readByte();
+ _blockPos[0]++;
+ buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f);
+ buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f);
+ }
+ }
+
+ return samples;
+}
+
+
+
+#pragma mark -
+
+
+class Tinsel_ADPCMStream : public ADPCMStream {
+protected:
+ struct {
+ // Tinsel
+ double predictor;
+ double K0, K1;
+ double d0, d1;
+ } _status;
+
+ void reset() {
+ ADPCMStream::reset();
+ memset(&_status, 0, sizeof(_status));
+ }
+
+ int16 decodeTinsel(int16, double);
+ void readBufferTinselHeader();
+
+public:
+ Tinsel_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+
+ if (blockAlign == 0)
+ error("Tinsel_ADPCMStream(): blockAlign isn't specified");
+
+ if (channels != 1)
+ error("Tinsel_ADPCMStream(): Tinsel ADPCM only supports mono");
+
+ memset(&_status, 0, sizeof(_status));
+ }
+
+};
+
+static const double TinselFilterTable[4][2] = {
+ {0, 0 },
+ {0.9375, 0},
+ {1.796875, -0.8125},
+ {1.53125, -0.859375}
+};
+
+void Tinsel_ADPCMStream::readBufferTinselHeader() {
+ uint8 start = _stream->readByte();
+ uint8 filterVal = (start & 0xC0) >> 6;
+
+ if ((start & 0x20) != 0) {
+ //Lower 6 bit are negative
+
+ // Negate
+ start = ~(start | 0xC0) + 1;
+
+ _status.predictor = 1 << start;
+ } else {
+ // Lower 6 bit are positive
+
+ // Truncate
+ start &= 0x1F;
+
+ _status.predictor = ((double) 1.0) / (1 << start);
+ }
+
+ _status.K0 = TinselFilterTable[filterVal][0];
+ _status.K1 = TinselFilterTable[filterVal][1];
+}
+
+int16 Tinsel_ADPCMStream::decodeTinsel(int16 code, double eVal) {
+ double sample;
+
+ sample = (double) code;
+ sample *= eVal * _status.predictor;
+ sample += (_status.d0 * _status.K0) + (_status.d1 * _status.K1);
+
+ _status.d1 = _status.d0;
+ _status.d0 = sample;
+
+ return (int16) CLIP<double>(sample, -32768.0, 32767.0);
+}
+
+class Tinsel4_ADPCMStream : public Tinsel_ADPCMStream {
+public:
+ Tinsel4_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {}
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+};
+
+int Tinsel4_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ uint16 data;
+ const double eVal = 1.142822265;
+
+ samples = 0;
+
+ assert(numSamples % 2 == 0);
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ readBufferTinselHeader();
+ _blockPos[0] = 0;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2, _blockPos[0]++) {
+ // Read 1 byte = 8 bits = two 4 bit blocks
+ data = _stream->readByte();
+ buffer[samples] = decodeTinsel((data << 8) & 0xF000, eVal);
+ buffer[samples+1] = decodeTinsel((data << 12) & 0xF000, eVal);
+ }
+ }
+
+ return samples;
+}
+
+class Tinsel6_ADPCMStream : public Tinsel_ADPCMStream {
+protected:
+ uint8 _chunkPos;
+ uint16 _chunkData;
+
+ void reset() {
+ ADPCMStream::reset();
+ _chunkPos = 0;
+ _chunkData = 0;
+ }
+
+public:
+ Tinsel6_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+ _chunkPos = 0;
+ _chunkData = 0;
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+};
+
+int Tinsel6_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ const double eVal = 1.032226562;
+
+ samples = 0;
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ readBufferTinselHeader();
+ _blockPos[0] = 0;
+ _chunkPos = 0;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _chunkPos = (_chunkPos + 1) % 4) {
+
+ switch (_chunkPos) {
+ case 0:
+ _chunkData = _stream->readByte();
+ buffer[samples] = decodeTinsel((_chunkData << 8) & 0xFC00, eVal);
+ break;
+ case 1:
+ _chunkData = (_chunkData << 8) | (_stream->readByte());
+ buffer[samples] = decodeTinsel((_chunkData << 6) & 0xFC00, eVal);
+ _blockPos[0]++;
+ break;
+ case 2:
+ _chunkData = (_chunkData << 8) | (_stream->readByte());
+ buffer[samples] = decodeTinsel((_chunkData << 4) & 0xFC00, eVal);
+ _blockPos[0]++;
+ break;
+ case 3:
+ _chunkData = (_chunkData << 8);
+ buffer[samples] = decodeTinsel((_chunkData << 2) & 0xFC00, eVal);
+ _blockPos[0]++;
+ break;
+ }
+
+ }
+
+ }
+
+ return samples;
+}
+
+class Tinsel8_ADPCMStream : public Tinsel_ADPCMStream {
+public:
+ Tinsel8_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {}
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+};
+
+int Tinsel8_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples;
+ byte data;
+ const double eVal = 1.007843258;
+
+ samples = 0;
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if (_blockPos[0] == _blockAlign) {
+ readBufferTinselHeader();
+ _blockPos[0] = 0;
+ }
+
+ for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _blockPos[0]++) {
+ // Read 1 byte = 8 bits = one 8 bit block
+ data = _stream->readByte();
+ buffer[samples] = decodeTinsel(data << 8, eVal);
+ }
+ }
+
+ return samples;
+}
+
+
+#pragma mark -
+
+// Duck DK3 IMA ADPCM Decoder
+// Based on FFmpeg's decoder and http://wiki.multimedia.cx/index.php?title=Duck_DK3_IMA_ADPCM
+
+class DK3_ADPCMStream : public Ima_ADPCMStream {
+protected:
+
+ void reset() {
+ Ima_ADPCMStream::reset();
+ _topNibble = false;
+ }
+
+public:
+ DK3_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign)
+ : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {
+
+ // DK3 only works as a stereo stream
+ assert(channels == 2);
+ _topNibble = false;
+ }
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+private:
+ byte _nibble, _lastByte;
+ bool _topNibble;
+};
+
+#define DK3_READ_NIBBLE() \
+do { \
+ if (_topNibble) { \
+ _nibble = _lastByte >> 4; \
+ _topNibble = false; \
+ } else { \
+ if (_stream->pos() >= _endpos) \
+ break; \
+ if ((_stream->pos() % _blockAlign) == 0) \
+ continue; \
+ _lastByte = _stream->readByte(); \
+ _nibble = _lastByte & 0xf; \
+ _topNibble = true; \
+ } \
+} while (0)
+
+
+int DK3_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+
+ assert((numSamples % 4) == 0);
+
+ while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) {
+ if ((_stream->pos() % _blockAlign) == 0) {
+ _stream->readUint16LE(); // Unknown
+ uint16 rate = _stream->readUint16LE(); // Copy of rate
+ _stream->skip(6); // Unknown
+ // Get predictor for both sum/diff channels
+ _status.ima_ch[0].last = _stream->readSint16LE();
+ _status.ima_ch[1].last = _stream->readSint16LE();
+ // Get index for both sum/diff channels
+ _status.ima_ch[0].stepIndex = _stream->readByte();
+ _status.ima_ch[1].stepIndex = _stream->readByte();
+
+ if (_stream->eos())
+ break;
+
+ // Sanity check
+ assert(rate == getRate());
+ }
+
+ DK3_READ_NIBBLE();
+ decodeIMA(_nibble, 0);
+
+ DK3_READ_NIBBLE();
+ decodeIMA(_nibble, 1);
+
+ buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last;
+ buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last;
+
+ DK3_READ_NIBBLE();
+ decodeIMA(_nibble, 0);
+
+ buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last;
+ buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last;
+ }
+
+ return samples;
+}
+
+
+#pragma mark -
+
+
+// adjust the step for use on the next sample.
+int16 ADPCMStream::stepAdjust(byte code) {
+ static const int16 adjusts[] = {-1, -1, -1, -1, 2, 4, 6, 8};
+
+ return adjusts[code & 0x07];
+}
+
+static const uint16 imaStepTable[89] = {
+ 7, 8, 9, 10, 11, 12, 13, 14,
+ 16, 17, 19, 21, 23, 25, 28, 31,
+ 34, 37, 41, 45, 50, 55, 60, 66,
+ 73, 80, 88, 97, 107, 118, 130, 143,
+ 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658,
+ 724, 796, 876, 963, 1060, 1166, 1282, 1411,
+ 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
+ 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
+ 7132, 7845, 8630, 9493,10442,11487,12635,13899,
+ 15289,16818,18500,20350,22385,24623,27086,29794,
+ 32767
+};
+
+int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) {
+ int32 E = (2 * (code & 0x7) + 1) * imaStepTable[_status.ima_ch[channel].stepIndex] / 8;
+ int32 diff = (code & 0x08) ? -E : E;
+ int32 samp = CLIP<int32>(_status.ima_ch[channel].last + diff, -32768, 32767);
+
+ _status.ima_ch[channel].last = samp;
+ _status.ima_ch[channel].stepIndex += stepAdjust(code);
+ _status.ima_ch[channel].stepIndex = CLIP<int32>(_status.ima_ch[channel].stepIndex, 0, ARRAYSIZE(imaStepTable) - 1);
+
+ return samp;
+}
+
+RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) {
+ // If size is 0, report the entire size of the stream
+ if (!size)
+ size = stream->size();
+
+ switch (type) {
+ case kADPCMOki:
+ return new Oki_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMMSIma:
+ return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMMSImaLastExpress:
+ return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign, true);
+ case kADPCMMS:
+ return new MS_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMTinsel4:
+ return new Tinsel4_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMTinsel6:
+ return new Tinsel6_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMTinsel8:
+ return new Tinsel8_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMIma:
+ return new Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMApple:
+ return new Apple_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ case kADPCMDK3:
+ return new DK3_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign);
+ default:
+ error("Unsupported ADPCM encoding");
+ break;
+ }
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/adpcm.h b/audio/decoders/adpcm.h
new file mode 100644
index 0000000000..38ec870a27
--- /dev/null
+++ b/audio/decoders/adpcm.h
@@ -0,0 +1,90 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - lastexpress
+ * - mohawk
+ * - saga
+ * - scumm
+ * - tinsel
+ */
+
+#ifndef SOUND_ADPCM_H
+#define SOUND_ADPCM_H
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+
+
+namespace Audio {
+
+class AudioStream;
+class RewindableAudioStream;
+
+// There are several types of ADPCM encoding, only some are supported here
+// For all the different encodings, refer to:
+// http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs
+// Usually, if the audio stream we're trying to play has the FourCC header
+// string intact, it's easy to discern which encoding is used
+enum typesADPCM {
+ kADPCMOki, // Dialogic/Oki ADPCM (aka VOX)
+ kADPCMMSIma, // Microsoft IMA ADPCM
+ kADPCMMSImaLastExpress, // Microsoft IMA ADPCM (with inverted samples)
+ 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
+ kADPCMIma, // Standard IMA ADPCM
+ kADPCMApple, // Apple QuickTime IMA ADPCM
+ kADPCMDK3 // Duck DK3 IMA ADPCM
+};
+
+/**
+ * Takes an input stream containing ADPCM compressed sound data and creates
+ * an RewindableAudioStream from that.
+ *
+ * @param stream the SeekableReadStream from which to read the ADPCM data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @param size how many bytes to read from the stream (0 = all)
+ * @param type the compression type used
+ * @param rate the sampling rate
+ * @param channels the number of channels
+ * @param blockAlign block alignment ???
+ * @return a new RewindableAudioStream, or NULL, if an error occurred
+ */
+RewindableAudioStream *makeADPCMStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse,
+ uint32 size, typesADPCM type,
+ int rate = 22050,
+ int channels = 2,
+ uint32 blockAlign = 0);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/aiff.cpp b/audio/decoders/aiff.cpp
new file mode 100644
index 0000000000..0f947752f6
--- /dev/null
+++ b/audio/decoders/aiff.cpp
@@ -0,0 +1,186 @@
+/* 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$
+ *
+ */
+
+/*
+ * The code in this file is based on information found at
+ * http://www.borg.com/~jglatt/tech/aiff.htm
+ *
+ * We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX
+ * (http://sox.sourceforge.net) may be a good place to start from.
+ */
+
+#include "common/endian.h"
+#include "common/util.h"
+#include "common/stream.h"
+
+#include "audio/decoders/aiff.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+
+uint32 readExtended(Common::SeekableReadStream &stream) {
+ // The sample rate is stored as an "80 bit IEEE Standard 754 floating
+ // point number (Standard Apple Numeric Environment [SANE] data type
+ // Extended).
+
+ byte buf[10];
+ uint32 mantissa;
+ uint32 last = 0;
+ byte exp;
+
+ stream.read(buf, 10);
+ mantissa = READ_BE_UINT32(buf + 2);
+ exp = 30 - buf[1];
+
+ while (exp--) {
+ last = mantissa;
+ mantissa >>= 1;
+ }
+
+ if (last & 0x00000001)
+ mantissa++;
+
+ return mantissa;
+}
+
+bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) {
+ byte buf[4];
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "FORM", 4) != 0) {
+ warning("loadAIFFFromStream: No 'FORM' header");
+ return false;
+ }
+
+ stream.readUint32BE();
+
+ // This could be AIFC, but we don't handle that case.
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "AIFF", 4) != 0) {
+ warning("loadAIFFFromStream: No 'AIFF' header");
+ return false;
+ }
+
+ // From here on, we only care about the COMM and SSND chunks, which are
+ // the only required chunks.
+
+ bool foundCOMM = false;
+ bool foundSSND = false;
+
+ uint16 numChannels = 0, bitsPerSample = 0;
+ uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0;
+
+ while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) {
+ uint32 length, pos;
+
+ stream.read(buf, 4);
+ length = stream.readUint32BE();
+ pos = stream.pos();
+
+ if (memcmp(buf, "COMM", 4) == 0) {
+ foundCOMM = true;
+ numChannels = stream.readUint16BE();
+ numSampleFrames = stream.readUint32BE();
+ bitsPerSample = stream.readUint16BE();
+ rate = readExtended(stream);
+ size = numSampleFrames * numChannels * (bitsPerSample / 8);
+ } else if (memcmp(buf, "SSND", 4) == 0) {
+ foundSSND = true;
+ offset = stream.readUint32BE();
+ blockSize = stream.readUint32BE();
+ soundOffset = stream.pos();
+ }
+
+ stream.seek(pos + length);
+ }
+
+ if (!foundCOMM) {
+ warning("loadAIFFFromStream: Cound not find 'COMM' chunk");
+ return false;
+ }
+
+ if (!foundSSND) {
+ warning("loadAIFFFromStream: Cound not find 'SSND' chunk");
+ return false;
+ }
+
+ // We only implement a subset of the AIFF standard.
+
+ if (numChannels < 1 || numChannels > 2) {
+ warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels);
+ return false;
+ }
+
+ if (bitsPerSample != 8 && bitsPerSample != 16) {
+ warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample);
+ return false;
+ }
+
+ if (offset != 0 || blockSize != 0) {
+ warning("loadAIFFFromStream: Block-aligned data is not supported");
+ return false;
+ }
+
+ // Samples are always signed, and big endian.
+
+ flags = 0;
+ if (bitsPerSample == 16)
+ flags |= Audio::FLAG_16BITS;
+ if (numChannels == 2)
+ flags |= Audio::FLAG_STEREO;
+
+ stream.seek(soundOffset);
+
+ // Stream now points at the sample data
+
+ return true;
+}
+
+SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ int size, rate;
+ byte *data, flags;
+
+ if (!loadAIFFFromStream(*stream, size, rate, flags)) {
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ data = (byte *)malloc(size);
+ assert(data);
+ stream->read(data, size);
+
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+
+ // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES.
+ return makeRawStream(data, size, rate, flags);
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/aiff.h b/audio/decoders/aiff.h
new file mode 100644
index 0000000000..06c56ecd38
--- /dev/null
+++ b/audio/decoders/aiff.h
@@ -0,0 +1,71 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - saga
+ * - sci
+ * - sword1
+ */
+
+#ifndef SOUND_AIFF_H
+#define SOUND_AIFF_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+namespace Common { class SeekableReadStream; }
+
+namespace Audio {
+
+class SeekableAudioStream;
+
+/**
+ * Try to load an AIFF from the given seekable stream. Returns true if
+ * successful. In that case, the stream's seek position will be set to the
+ * start of the audio data, and size, rate and flags contain information
+ * necessary for playback. Currently this function only supports uncompressed
+ * raw PCM data as well as IMA ADPCM.
+ */
+extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags);
+
+/**
+ * Try to load an AIFF from the given seekable stream and create an AudioStream
+ * from that data.
+ *
+ * This function uses loadAIFFFromStream() internally.
+ *
+ * @param stream the SeekableReadStream from which to read the AIFF data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeAIFFStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/flac.cpp b/audio/decoders/flac.cpp
new file mode 100644
index 0000000000..76b6d35419
--- /dev/null
+++ b/audio/decoders/flac.cpp
@@ -0,0 +1,745 @@
+/* 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$
+ *
+ */
+
+// Disable symbol overrides for FILE as that is used in FLAC headers
+#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
+
+#include "audio/decoders/flac.h"
+
+#ifdef USE_FLAC
+
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+#include "audio/audiostream.h"
+
+#define FLAC__NO_DLL // that MS-magic gave me headaches - just link the library you like
+#include <FLAC/export.h>
+
+
+// check if we have FLAC >= 1.1.3; LEGACY_FLAC code can be removed once FLAC-1.1.3 propagates everywhere
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT < 8
+#define LEGACY_FLAC
+#else
+#undef LEGACY_FLAC
+#endif
+
+
+#ifdef LEGACY_FLAC
+
+// Before FLAC 1.1.3, we needed to use the stream decoder API.
+#include <FLAC/seekable_stream_decoder.h>
+typedef uint FLAC_size_t;
+
+#else
+
+// With FLAC 1.1.3, the stream decoder API was merged into the regular
+// stream API. In order to stay compatible with older FLAC versions, we
+// simply add some typedefs and #ifdefs to map between the old and new API.
+// We use the typedefs (instead of only #defines) in order to somewhat
+// improve the readability of the code.
+
+#include <FLAC/stream_decoder.h>
+typedef size_t FLAC_size_t;
+// Add aliases for the old names
+typedef FLAC__StreamDecoderState FLAC__SeekableStreamDecoderState;
+typedef FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus;
+typedef FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus;
+typedef FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus;
+typedef FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus;
+typedef FLAC__StreamDecoder FLAC__SeekableStreamDecoder;
+
+#endif
+
+
+namespace Audio {
+
+#pragma mark -
+#pragma mark --- FLAC stream ---
+#pragma mark -
+
+static const uint MAX_OUTPUT_CHANNELS = 2;
+
+
+class FLACStream : public SeekableAudioStream {
+protected:
+ Common::SeekableReadStream *_inStream;
+ bool _disposeAfterUse;
+
+ ::FLAC__SeekableStreamDecoder *_decoder;
+
+ /** Header of the stream */
+ FLAC__StreamMetadata_StreamInfo _streaminfo;
+
+ /** index + 1(!) of the last sample to be played */
+ FLAC__uint64 _lastSample;
+
+ /** total play time */
+ Timestamp _length;
+
+ /** true if the last sample was decoded from the FLAC-API - there might still be data in the buffer */
+ bool _lastSampleWritten;
+
+ typedef int16 SampleType;
+ enum { BUFTYPE_BITS = 16 };
+
+ enum {
+ // Maximal buffer size. According to the FLAC format specification, the block size is
+ // a 16 bit value (in fact it seems the maximal block size is 32768, but we play it safe).
+ BUFFER_SIZE = 65536
+ };
+
+ struct {
+ SampleType bufData[BUFFER_SIZE];
+ SampleType *bufReadPos;
+ uint bufFill;
+ } _sampleCache;
+
+ SampleType *_outBuffer;
+ uint _requestedSamples;
+
+ typedef void (*PFCONVERTBUFFERS)(SampleType*, const FLAC__int32*[], uint, const uint, const uint8);
+ PFCONVERTBUFFERS _methodConvertBuffers;
+
+
+public:
+ FLACStream(Common::SeekableReadStream *inStream, bool dispose);
+ virtual ~FLACStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool isStereo() const { return _streaminfo.channels >= 2; }
+ int getRate() const { return _streaminfo.sample_rate; }
+ bool endOfData() const {
+ // End of data is reached if there either is no valid stream data available,
+ // or if we reached the last sample and completely emptied the sample cache.
+ return _streaminfo.channels == 0 || (_lastSampleWritten && _sampleCache.bufFill == 0);
+ }
+
+ bool seek(const Timestamp &where);
+ Timestamp getLength() const { return _length; }
+
+ bool isStreamDecoderReady() const { return getStreamDecoderState() == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC; }
+protected:
+ uint getChannels() const { return MIN<uint>(_streaminfo.channels, MAX_OUTPUT_CHANNELS); }
+
+ bool allocateBuffer(uint minSamples);
+
+ inline FLAC__StreamDecoderState getStreamDecoderState() const;
+
+ inline bool processSingleBlock();
+ inline bool processUntilEndOfMetadata();
+ bool seekAbsolute(FLAC__uint64 sample);
+
+ inline ::FLAC__SeekableStreamDecoderReadStatus callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes);
+ inline ::FLAC__SeekableStreamDecoderSeekStatus callbackSeek(FLAC__uint64 absoluteByteOffset);
+ inline ::FLAC__SeekableStreamDecoderTellStatus callbackTell(FLAC__uint64 *absoluteByteOffset);
+ inline ::FLAC__SeekableStreamDecoderLengthStatus callbackLength(FLAC__uint64 *streamLength);
+ inline bool callbackEOF();
+ inline ::FLAC__StreamDecoderWriteStatus callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]);
+ inline void callbackMetadata(const ::FLAC__StreamMetadata *metadata);
+ inline void callbackError(::FLAC__StreamDecoderErrorStatus status);
+
+private:
+ static ::FLAC__SeekableStreamDecoderReadStatus callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData);
+ static ::FLAC__SeekableStreamDecoderSeekStatus callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData);
+ static ::FLAC__SeekableStreamDecoderTellStatus callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData);
+ static ::FLAC__SeekableStreamDecoderLengthStatus callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData);
+ static FLAC__bool callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData);
+ static ::FLAC__StreamDecoderWriteStatus callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData);
+ static void callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData);
+ static void callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData);
+
+ void setBestConvertBufferMethod();
+ static void convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+ static void convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+ static void convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+ static void convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+ static void convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits);
+};
+
+FLACStream::FLACStream(Common::SeekableReadStream *inStream, bool dispose)
+#ifdef LEGACY_FLAC
+ : _decoder(::FLAC__seekable_stream_decoder_new()),
+#else
+ : _decoder(::FLAC__stream_decoder_new()),
+#endif
+ _inStream(inStream),
+ _disposeAfterUse(dispose),
+ _length(0, 1000), _lastSample(0),
+ _outBuffer(NULL), _requestedSamples(0), _lastSampleWritten(false),
+ _methodConvertBuffers(&FLACStream::convertBuffersGeneric)
+{
+ assert(_inStream);
+ memset(&_streaminfo, 0, sizeof(_streaminfo));
+
+ _sampleCache.bufReadPos = NULL;
+ _sampleCache.bufFill = 0;
+
+ _methodConvertBuffers = &FLACStream::convertBuffersGeneric;
+
+ bool success;
+#ifdef LEGACY_FLAC
+ ::FLAC__seekable_stream_decoder_set_read_callback(_decoder, &FLACStream::callWrapRead);
+ ::FLAC__seekable_stream_decoder_set_seek_callback(_decoder, &FLACStream::callWrapSeek);
+ ::FLAC__seekable_stream_decoder_set_tell_callback(_decoder, &FLACStream::callWrapTell);
+ ::FLAC__seekable_stream_decoder_set_length_callback(_decoder, &FLACStream::callWrapLength);
+ ::FLAC__seekable_stream_decoder_set_eof_callback(_decoder, &FLACStream::callWrapEOF);
+ ::FLAC__seekable_stream_decoder_set_write_callback(_decoder, &FLACStream::callWrapWrite);
+ ::FLAC__seekable_stream_decoder_set_metadata_callback(_decoder, &FLACStream::callWrapMetadata);
+ ::FLAC__seekable_stream_decoder_set_error_callback(_decoder, &FLACStream::callWrapError);
+ ::FLAC__seekable_stream_decoder_set_client_data(_decoder, (void*)this);
+
+ success = (::FLAC__seekable_stream_decoder_init(_decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK);
+#else
+ success = (::FLAC__stream_decoder_init_stream(
+ _decoder,
+ &FLACStream::callWrapRead,
+ &FLACStream::callWrapSeek,
+ &FLACStream::callWrapTell,
+ &FLACStream::callWrapLength,
+ &FLACStream::callWrapEOF,
+ &FLACStream::callWrapWrite,
+ &FLACStream::callWrapMetadata,
+ &FLACStream::callWrapError,
+ (void*)this
+ ) == FLAC__STREAM_DECODER_INIT_STATUS_OK);
+#endif
+ if (success) {
+ if (processUntilEndOfMetadata() && _streaminfo.channels > 0) {
+ _lastSample = _streaminfo.total_samples + 1;
+ _length = Timestamp(0, _lastSample - 1, getRate());
+ return; // no error occurred
+ }
+ }
+
+ warning("FLACStream: could not create audio stream");
+}
+
+FLACStream::~FLACStream() {
+ if (_decoder != NULL) {
+#ifdef LEGACY_FLAC
+ (void) ::FLAC__seekable_stream_decoder_finish(_decoder);
+ ::FLAC__seekable_stream_decoder_delete(_decoder);
+#else
+ (void) ::FLAC__stream_decoder_finish(_decoder);
+ ::FLAC__stream_decoder_delete(_decoder);
+#endif
+ }
+ if (_disposeAfterUse)
+ delete _inStream;
+}
+
+inline FLAC__StreamDecoderState FLACStream::getStreamDecoderState() const {
+ assert(_decoder != NULL);
+#ifdef LEGACY_FLAC
+ return ::FLAC__seekable_stream_decoder_get_stream_decoder_state(_decoder);
+#else
+ return ::FLAC__stream_decoder_get_state(_decoder);
+#endif
+}
+
+inline bool FLACStream::processSingleBlock() {
+ assert(_decoder != NULL);
+#ifdef LEGACY_FLAC
+ return 0 != ::FLAC__seekable_stream_decoder_process_single(_decoder);
+#else
+ return 0 != ::FLAC__stream_decoder_process_single(_decoder);
+#endif
+}
+
+inline bool FLACStream::processUntilEndOfMetadata() {
+ assert(_decoder != NULL);
+#ifdef LEGACY_FLAC
+ return 0 != ::FLAC__seekable_stream_decoder_process_until_end_of_metadata(_decoder);
+#else
+ return 0 != ::FLAC__stream_decoder_process_until_end_of_metadata(_decoder);
+#endif
+}
+
+bool FLACStream::seekAbsolute(FLAC__uint64 sample) {
+ assert(_decoder != NULL);
+#ifdef LEGACY_FLAC
+ const bool result = (0 != ::FLAC__seekable_stream_decoder_seek_absolute(_decoder, sample));
+#else
+ const bool result = (0 != ::FLAC__stream_decoder_seek_absolute(_decoder, sample));
+#endif
+ if (result) {
+ _lastSampleWritten = (_lastSample != 0 && sample >= _lastSample); // only set if we are SURE
+ }
+ return result;
+}
+
+bool FLACStream::seek(const Timestamp &where) {
+ _sampleCache.bufFill = 0;
+ _sampleCache.bufReadPos = NULL;
+ // FLAC uses the sample pair number, thus we always use "false" for the isStereo parameter
+ // of the convertTimeToStreamPos helper.
+ return seekAbsolute((FLAC__uint64)convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames());
+}
+
+int FLACStream::readBuffer(int16 *buffer, const int numSamples) {
+ const uint numChannels = getChannels();
+
+ if (numChannels == 0) {
+ warning("FLACStream: Stream not successfully initialised, cant playback");
+ return -1; // streaminfo wasnt read!
+ }
+
+ assert(numSamples % numChannels == 0); // must be multiple of channels!
+ assert(buffer != NULL);
+ assert(_outBuffer == NULL);
+ assert(_requestedSamples == 0);
+
+ _outBuffer = buffer;
+ _requestedSamples = numSamples;
+
+ // If there is still data in our buffer from the last time around,
+ // copy that first.
+ if (_sampleCache.bufFill > 0) {
+ assert(_sampleCache.bufReadPos >= _sampleCache.bufData);
+ assert(_sampleCache.bufFill % numChannels == 0);
+
+ const uint copySamples = MIN((uint)numSamples, _sampleCache.bufFill);
+ memcpy(buffer, _sampleCache.bufReadPos, copySamples*sizeof(buffer[0]));
+
+ _outBuffer = buffer + copySamples;
+ _requestedSamples = numSamples - copySamples;
+ _sampleCache.bufReadPos += copySamples;
+ _sampleCache.bufFill -= copySamples;
+ }
+
+ bool decoderOk = true;
+
+ FLAC__StreamDecoderState state = getStreamDecoderState();
+
+ // Keep poking FLAC to process more samples until we completely satisfied the request
+ // respectively until we run out of data.
+ while (!_lastSampleWritten && _requestedSamples > 0 && state == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC) {
+ assert(_sampleCache.bufFill == 0);
+ assert(_requestedSamples % numChannels == 0);
+ processSingleBlock();
+ state = getStreamDecoderState();
+
+ if (state == FLAC__STREAM_DECODER_END_OF_STREAM)
+ _lastSampleWritten = true;
+ }
+
+ // Error handling
+ switch (state) {
+ case FLAC__STREAM_DECODER_END_OF_STREAM:
+ _lastSampleWritten = true;
+ break;
+ case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
+ break;
+ default:
+ decoderOk = false;
+ warning("FLACStream: An error occurred while decoding. DecoderState is: %s",
+ FLAC__StreamDecoderStateString[getStreamDecoderState()]);
+ }
+
+ // Compute how many samples we actually produced
+ const int samples = (int)(_outBuffer - buffer);
+ assert(samples % numChannels == 0);
+
+ _outBuffer = NULL; // basically unnecessary, only for the purpose of the asserts
+ _requestedSamples = 0; // basically unnecessary, only for the purpose of the asserts
+
+ return decoderOk ? samples : -1;
+}
+
+inline ::FLAC__SeekableStreamDecoderReadStatus FLACStream::callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes) {
+ if (*bytes == 0) {
+#ifdef LEGACY_FLAC
+ return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; /* abort to avoid a deadlock */
+#else
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT; /* abort to avoid a deadlock */
+#endif
+ }
+
+ const uint32 bytesRead = _inStream->read(buffer, *bytes);
+
+ if (bytesRead == 0) {
+#ifdef LEGACY_FLAC
+ return _inStream->eos() ? FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR;
+#else
+ return _inStream->eos() ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_ABORT;
+#endif
+ }
+
+ *bytes = static_cast<uint>(bytesRead);
+#ifdef LEGACY_FLAC
+ return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK;
+#else
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+#endif
+}
+
+void FLACStream::setBestConvertBufferMethod() {
+ PFCONVERTBUFFERS tempMethod = &FLACStream::convertBuffersGeneric;
+
+ const uint numChannels = getChannels();
+ const uint8 numBits = (uint8)_streaminfo.bits_per_sample;
+
+ assert(numChannels >= 1);
+ assert(numBits >= 4 && numBits <=32);
+
+ if (numChannels == 1) {
+ if (numBits == 8)
+ tempMethod = &FLACStream::convertBuffersMono8Bit;
+ if (numBits == BUFTYPE_BITS)
+ tempMethod = &FLACStream::convertBuffersMonoNS;
+ } else if (numChannels == 2) {
+ if (numBits == 8)
+ tempMethod = &FLACStream::convertBuffersStereo8Bit;
+ if (numBits == BUFTYPE_BITS)
+ tempMethod = &FLACStream::convertBuffersStereoNS;
+ } /* else ... */
+
+ _methodConvertBuffers = tempMethod;
+}
+
+// 1 channel, no scaling
+void FLACStream::convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numChannels == 1);
+ assert(numBits == BUFTYPE_BITS);
+
+ FLAC__int32 const* inChannel1 = inChannels[0];
+
+ while (numSamples >= 4) {
+ bufDestination[0] = static_cast<SampleType>(inChannel1[0]);
+ bufDestination[1] = static_cast<SampleType>(inChannel1[1]);
+ bufDestination[2] = static_cast<SampleType>(inChannel1[2]);
+ bufDestination[3] = static_cast<SampleType>(inChannel1[3]);
+ bufDestination += 4;
+ inChannel1 += 4;
+ numSamples -= 4;
+ }
+
+ for (; numSamples > 0; --numSamples) {
+ *bufDestination++ = static_cast<SampleType>(*inChannel1++);
+ }
+
+ inChannels[0] = inChannel1;
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+// 1 channel, scaling from 8Bit
+void FLACStream::convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numChannels == 1);
+ assert(numBits == 8);
+ assert(8 < BUFTYPE_BITS);
+
+ FLAC__int32 const* inChannel1 = inChannels[0];
+
+ while (numSamples >= 4) {
+ bufDestination[0] = static_cast<SampleType>(inChannel1[0]) << (BUFTYPE_BITS - 8);
+ bufDestination[1] = static_cast<SampleType>(inChannel1[1]) << (BUFTYPE_BITS - 8);
+ bufDestination[2] = static_cast<SampleType>(inChannel1[2]) << (BUFTYPE_BITS - 8);
+ bufDestination[3] = static_cast<SampleType>(inChannel1[3]) << (BUFTYPE_BITS - 8);
+ bufDestination += 4;
+ inChannel1 += 4;
+ numSamples -= 4;
+ }
+
+ for (; numSamples > 0; --numSamples) {
+ *bufDestination++ = static_cast<SampleType>(*inChannel1++) << (BUFTYPE_BITS - 8);
+ }
+
+ inChannels[0] = inChannel1;
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+// 2 channels, no scaling
+void FLACStream::convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numChannels == 2);
+ assert(numBits == BUFTYPE_BITS);
+ assert(numSamples % 2 == 0); // must be integral multiply of channels
+
+
+ FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel
+ FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel
+
+ while (numSamples >= 2*2) {
+ bufDestination[0] = static_cast<SampleType>(inChannel1[0]);
+ bufDestination[1] = static_cast<SampleType>(inChannel2[0]);
+ bufDestination[2] = static_cast<SampleType>(inChannel1[1]);
+ bufDestination[3] = static_cast<SampleType>(inChannel2[1]);
+ bufDestination += 2 * 2;
+ inChannel1 += 2;
+ inChannel2 += 2;
+ numSamples -= 2 * 2;
+ }
+
+ while (numSamples > 0) {
+ bufDestination[0] = static_cast<SampleType>(*inChannel1++);
+ bufDestination[1] = static_cast<SampleType>(*inChannel2++);
+ bufDestination += 2;
+ numSamples -= 2;
+ }
+
+ inChannels[0] = inChannel1;
+ inChannels[1] = inChannel2;
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+// 2 channels, scaling from 8Bit
+void FLACStream::convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numChannels == 2);
+ assert(numBits == 8);
+ assert(numSamples % 2 == 0); // must be integral multiply of channels
+ assert(8 < BUFTYPE_BITS);
+
+ FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel
+ FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel
+
+ while (numSamples >= 2*2) {
+ bufDestination[0] = static_cast<SampleType>(inChannel1[0]) << (BUFTYPE_BITS - 8);
+ bufDestination[1] = static_cast<SampleType>(inChannel2[0]) << (BUFTYPE_BITS - 8);
+ bufDestination[2] = static_cast<SampleType>(inChannel1[1]) << (BUFTYPE_BITS - 8);
+ bufDestination[3] = static_cast<SampleType>(inChannel2[1]) << (BUFTYPE_BITS - 8);
+ bufDestination += 2 * 2;
+ inChannel1 += 2;
+ inChannel2 += 2;
+ numSamples -= 2 * 2;
+ }
+
+ while (numSamples > 0) {
+ bufDestination[0] = static_cast<SampleType>(*inChannel1++) << (BUFTYPE_BITS - 8);
+ bufDestination[1] = static_cast<SampleType>(*inChannel2++) << (BUFTYPE_BITS - 8);
+ bufDestination += 2;
+ numSamples -= 2;
+ }
+
+ inChannels[0] = inChannel1;
+ inChannels[1] = inChannel2;
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+// all Purpose-conversion - slowest of em all
+void FLACStream::convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) {
+ assert(numSamples % numChannels == 0); // must be integral multiply of channels
+
+ if (numBits < BUFTYPE_BITS) {
+ const uint8 kPower = (uint8)(BUFTYPE_BITS - numBits);
+
+ for (; numSamples > 0; numSamples -= numChannels) {
+ for (uint i = 0; i < numChannels; ++i)
+ *bufDestination++ = static_cast<SampleType>(*(inChannels[i]++)) << kPower;
+ }
+ } else if (numBits > BUFTYPE_BITS) {
+ const uint8 kPower = (uint8)(numBits - BUFTYPE_BITS);
+
+ for (; numSamples > 0; numSamples -= numChannels) {
+ for (uint i = 0; i < numChannels; ++i)
+ *bufDestination++ = static_cast<SampleType>(*(inChannels[i]++) >> kPower);
+ }
+ } else {
+ for (; numSamples > 0; numSamples -= numChannels) {
+ for (uint i = 0; i < numChannels; ++i)
+ *bufDestination++ = static_cast<SampleType>(*(inChannels[i]++));
+ }
+ }
+
+ assert(numSamples == 0); // dint copy too many samples
+}
+
+inline ::FLAC__StreamDecoderWriteStatus FLACStream::callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) {
+ assert(frame->header.channels == _streaminfo.channels);
+ assert(frame->header.sample_rate == _streaminfo.sample_rate);
+ assert(frame->header.bits_per_sample == _streaminfo.bits_per_sample);
+ assert(frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER || _streaminfo.min_blocksize == _streaminfo.max_blocksize);
+
+ // We require that either the sample cache is empty, or that no samples were requested
+ assert(_sampleCache.bufFill == 0 || _requestedSamples == 0);
+
+ uint numSamples = frame->header.blocksize;
+ const uint numChannels = getChannels();
+ const uint8 numBits = (uint8)_streaminfo.bits_per_sample;
+
+ assert(_requestedSamples % numChannels == 0); // must be integral multiply of channels
+
+ const FLAC__uint64 firstSampleNumber = (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER) ?
+ frame->header.number.sample_number : (static_cast<FLAC__uint64>(frame->header.number.frame_number)) * _streaminfo.max_blocksize;
+
+ // Check whether we are about to reach beyond the last sample we are supposed to play.
+ if (_lastSample != 0 && firstSampleNumber + numSamples >= _lastSample) {
+ numSamples = (uint)(firstSampleNumber >= _lastSample ? 0 : _lastSample - firstSampleNumber);
+ _lastSampleWritten = true;
+ }
+
+ // The value in _requestedSamples counts raw samples, so if there are more than one
+ // channel, we have to multiply the number of available sample "pairs" by numChannels
+ numSamples *= numChannels;
+
+ const FLAC__int32 *inChannels[MAX_OUTPUT_CHANNELS];
+ for (uint i = 0; i < numChannels; ++i)
+ inChannels[i] = buffer[i];
+
+ // write the incoming samples directly into the buffer provided to us by the mixer
+ if (_requestedSamples > 0) {
+ assert(_requestedSamples % numChannels == 0);
+ assert(_outBuffer != NULL);
+
+ // Copy & convert the available samples (limited both by how many we have available, and
+ // by how many are actually needed).
+ const uint copySamples = MIN(_requestedSamples, numSamples);
+ (*_methodConvertBuffers)(_outBuffer, inChannels, copySamples, numChannels, numBits);
+
+ _requestedSamples -= copySamples;
+ numSamples -= copySamples;
+ _outBuffer += copySamples;
+ }
+
+ // Write all remaining samples (i.e. those which didn't fit into the mixer buffer)
+ // into the sample cache.
+ if (_sampleCache.bufFill == 0)
+ _sampleCache.bufReadPos = _sampleCache.bufData;
+ const uint cacheSpace = (_sampleCache.bufData + BUFFER_SIZE) - (_sampleCache.bufReadPos + _sampleCache.bufFill);
+ assert(numSamples <= cacheSpace);
+ (*_methodConvertBuffers)(_sampleCache.bufReadPos + _sampleCache.bufFill, inChannels, numSamples, numChannels, numBits);
+
+ _sampleCache.bufFill += numSamples;
+
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+inline ::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callbackSeek(FLAC__uint64 absoluteByteOffset) {
+ _inStream->seek(absoluteByteOffset, SEEK_SET);
+ const bool result = (absoluteByteOffset == (FLAC__uint64)_inStream->pos());
+
+#ifdef LEGACY_FLAC
+ return result ? FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR;
+#else
+ return result ? FLAC__STREAM_DECODER_SEEK_STATUS_OK : FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
+#endif
+}
+
+inline ::FLAC__SeekableStreamDecoderTellStatus FLACStream::callbackTell(FLAC__uint64 *absoluteByteOffset) {
+ *absoluteByteOffset = static_cast<FLAC__uint64>(_inStream->pos());
+#ifdef LEGACY_FLAC
+ return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK;
+#else
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+#endif
+}
+
+inline ::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callbackLength(FLAC__uint64 *streamLength) {
+ *streamLength = static_cast<FLAC__uint64>(_inStream->size());
+#ifdef LEGACY_FLAC
+ return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK;
+#else
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
+#endif
+}
+
+inline bool FLACStream::callbackEOF() {
+ return _inStream->eos();
+}
+
+
+inline void FLACStream::callbackMetadata(const ::FLAC__StreamMetadata *metadata) {
+ assert(_decoder != NULL);
+ assert(metadata->type == FLAC__METADATA_TYPE_STREAMINFO); // others arent really interesting
+
+ _streaminfo = metadata->data.stream_info;
+ setBestConvertBufferMethod(); // should be set after getting stream-information. FLAC always parses the info first
+}
+inline void FLACStream::callbackError(::FLAC__StreamDecoderErrorStatus status) {
+ // some of these are non-critical-Errors
+ debug(1, "FLACStream: An error occurred while decoding. DecoderState is: %s",
+ FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+/* Static Callback Wrappers */
+::FLAC__SeekableStreamDecoderReadStatus FLACStream::callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackRead(buffer, bytes);
+}
+
+::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackSeek(absoluteByteOffset);
+}
+
+::FLAC__SeekableStreamDecoderTellStatus FLACStream::callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackTell(absoluteByteOffset);
+}
+
+::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackLength(streamLength);
+}
+
+FLAC__bool FLACStream::callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackEOF();
+}
+
+::FLAC__StreamDecoderWriteStatus FLACStream::callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ return instance->callbackWrite(frame, buffer);
+}
+
+void FLACStream::callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ instance->callbackMetadata(metadata);
+}
+
+void FLACStream::callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData) {
+ FLACStream *instance = (FLACStream *)clientData;
+ assert(0 != instance);
+ instance->callbackError(status);
+}
+
+
+#pragma mark -
+#pragma mark --- FLAC factory functions ---
+#pragma mark -
+
+SeekableAudioStream *makeFLACStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ SeekableAudioStream *s = new FLACStream(stream, disposeAfterUse);
+ if (s && s->endOfData()) {
+ delete s;
+ return 0;
+ } else {
+ return s;
+ }
+}
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_FLAC
diff --git a/audio/decoders/flac.h b/audio/decoders/flac.h
new file mode 100644
index 0000000000..17f95ec1fb
--- /dev/null
+++ b/audio/decoders/flac.h
@@ -0,0 +1,75 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - draci
+ * - kyra
+ * - m4
+ * - queen
+ * - saga
+ * - sci
+ * - scumm
+ * - sword1
+ * - sword2
+ * - touche
+ * - tucker
+ */
+
+#ifndef SOUND_FLAC_H
+#define SOUND_FLAC_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+#ifdef USE_FLAC
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+/**
+ * Create a new SeekableAudioStream from the FLAC data in the given stream.
+ * Allows for seeking (which is why we require a SeekableReadStream).
+ *
+ * @param stream the SeekableReadStream from which to read the FLAC data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeFLACStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_FLAC
+#endif // #ifndef SOUND_FLAC_H
diff --git a/audio/decoders/iff_sound.cpp b/audio/decoders/iff_sound.cpp
new file mode 100644
index 0000000000..2ec189c586
--- /dev/null
+++ b/audio/decoders/iff_sound.cpp
@@ -0,0 +1,130 @@
+/* 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 "audio/decoders/iff_sound.h"
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+#include "common/iff_container.h"
+#include "common/func.h"
+
+namespace Audio {
+
+struct Voice8Header {
+ uint32 oneShotHiSamples;
+ uint32 repeatHiSamples;
+ uint32 samplesPerHiCycle;
+ uint16 samplesPerSec;
+ byte octaves;
+ byte compression;
+ uint32 volume;
+
+ Voice8Header() {
+ memset(this, 0, sizeof(Voice8Header));
+ }
+
+ void load(Common::ReadStream &stream);
+};
+
+void Voice8Header::load(Common::ReadStream &stream) {
+ oneShotHiSamples = stream.readUint32BE();
+ repeatHiSamples = stream.readUint32BE();
+ samplesPerHiCycle = stream.readUint32BE();
+ samplesPerSec = stream.readUint16BE();
+ octaves = stream.readByte();
+ compression = stream.readByte();
+ volume = stream.readUint32BE();
+}
+
+
+
+struct A8SVXLoader {
+ Voice8Header _header;
+ int8 *_data;
+ uint32 _dataSize;
+
+ void load(Common::ReadStream &input) {
+ Common::IFFParser parser(&input);
+ Common::Functor1Mem< Common::IFFChunk&, bool, A8SVXLoader > c(this, &A8SVXLoader::callback);
+ parser.parse(c);
+ }
+
+ bool callback(Common::IFFChunk &chunk) {
+ switch (chunk._type) {
+ case ID_VHDR:
+ _header.load(*chunk._stream);
+ break;
+
+ case ID_BODY:
+ _dataSize = chunk._size;
+ _data = (int8*)malloc(_dataSize);
+ assert(_data);
+ loadData(chunk._stream);
+ return true;
+ }
+
+ return false;
+ }
+
+ void loadData(Common::ReadStream *stream) {
+ switch (_header.compression) {
+ case 0:
+ stream->read(_data, _dataSize);
+ break;
+
+ case 1:
+ // implement other formats here
+ error("compressed IFF audio is not supported");
+ break;
+ }
+
+ }
+};
+
+
+AudioStream *make8SVXStream(Common::ReadStream &input, bool loop) {
+ A8SVXLoader loader;
+ loader.load(input);
+
+ SeekableAudioStream *stream = Audio::makeRawStream((byte *)loader._data, loader._dataSize, loader._header.samplesPerSec, 0);
+
+ uint32 loopStart = 0, loopEnd = 0;
+ if (loop) {
+ // the standard way to loop 8SVX audio implies use of the oneShotHiSamples and
+ // repeatHiSamples fields
+ loopStart = 0;
+ loopEnd = loader._header.oneShotHiSamples + loader._header.repeatHiSamples;
+
+ if (loopStart != loopEnd) {
+ return new SubLoopingAudioStream(stream, 0,
+ Timestamp(0, loopStart, loader._header.samplesPerSec),
+ Timestamp(0, loopEnd, loader._header.samplesPerSec));
+ }
+ }
+
+ return stream;
+}
+
+}
diff --git a/audio/decoders/iff_sound.h b/audio/decoders/iff_sound.h
new file mode 100644
index 0000000000..4e53059380
--- /dev/null
+++ b/audio/decoders/iff_sound.h
@@ -0,0 +1,47 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - parallaction
+ */
+
+#ifndef SOUND_IFF_H
+#define SOUND_IFF_H
+
+namespace Common {
+ class ReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+
+AudioStream *make8SVXStream(Common::ReadStream &stream, bool loop);
+
+}
+
+#endif
diff --git a/audio/decoders/mac_snd.cpp b/audio/decoders/mac_snd.cpp
new file mode 100644
index 0000000000..7c1a2f75f0
--- /dev/null
+++ b/audio/decoders/mac_snd.cpp
@@ -0,0 +1,116 @@
+/* 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$
+ *
+ */
+
+/*
+ * The code in this file is based on information found at
+ * http://developer.apple.com/legacy/mac/library/documentation/mac/Sound/Sound-60.html#HEADING60-15
+ *
+ * We implement both type 1 and type 2 snd resources, but only those that are sampled
+ */
+
+#include "common/util.h"
+#include "common/stream.h"
+
+#include "audio/decoders/mac_snd.h"
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+
+SeekableAudioStream *makeMacSndStream(Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+
+ uint16 sndType = stream->readUint16BE();
+
+ if (sndType == 1) {
+ // "normal" snd resources
+ if (stream->readUint16BE() != 1) {
+ warning("makeMacSndStream(): Unsupported data type count");
+ return 0;
+ }
+
+ if (stream->readUint16BE() != 5) {
+ // 5 == sampled
+ warning("makeMacSndStream(): Unsupported data type");
+ return 0;
+ }
+
+ stream->readUint32BE(); // initialization option
+ } else if (sndType == 2) {
+ // old HyperCard snd resources
+ stream->readUint16BE(); // reference count (unused)
+ } else {
+ warning("makeMacSndStream(): Unknown format type %d", sndType);
+ return 0;
+ }
+
+ // We really should never get this as long as we have sampled data only
+ if (stream->readUint16BE() != 1) {
+ warning("makeMacSndStream(): Unsupported command count");
+ return 0;
+ }
+
+ uint16 command = stream->readUint16BE();
+
+ // 0x8050 - soundCmd (with dataOffsetFlag set): install a sampled sound as a voice
+ // 0x8051 - bufferCmd (with dataOffsetFlag set): play a sample sound
+ if (command != 0x8050 && command != 0x8051) {
+ warning("makeMacSndStream(): Unsupported command %04x", command);
+ return 0;
+ }
+
+ stream->readUint16BE(); // 0
+ uint32 soundHeaderOffset = stream->readUint32BE();
+
+ stream->seek(soundHeaderOffset);
+
+ uint32 soundDataOffset = stream->readUint32BE();
+ uint32 size = stream->readUint32BE();
+ uint16 rate = stream->readUint32BE() >> 16; // Really fixed point, but we only support integer rates
+ stream->readUint32BE(); // loop start
+ stream->readUint32BE(); // loop end
+ byte encoding = stream->readByte();
+ stream->readByte(); // base frequency
+
+ if (encoding != 0) {
+ // 0 == PCM
+ warning("makeMacSndStream(): Unsupported compression %d", encoding);
+ return 0;
+ }
+
+ stream->skip(soundDataOffset);
+
+ byte *data = (byte *)malloc(size);
+ assert(data);
+ stream->read(data, size);
+
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+
+ // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES.
+ return makeRawStream(data, size, rate, Audio::FLAG_UNSIGNED);
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/mac_snd.h b/audio/decoders/mac_snd.h
new file mode 100644
index 0000000000..198a61333e
--- /dev/null
+++ b/audio/decoders/mac_snd.h
@@ -0,0 +1,58 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - sci
+ */
+
+#ifndef SOUND_MAC_SND_H
+#define SOUND_MAC_SND_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+namespace Common { class SeekableReadStream; }
+
+namespace Audio {
+
+class SeekableAudioStream;
+
+/**
+ * Try to load a Mac snd resource from the given seekable stream and create a SeekableAudioStream
+ * from that data.
+ *
+ * @param stream the SeekableReadStream from which to read the snd data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeMacSndStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/mp3.cpp b/audio/decoders/mp3.cpp
new file mode 100644
index 0000000000..53d68fa9db
--- /dev/null
+++ b/audio/decoders/mp3.cpp
@@ -0,0 +1,375 @@
+/* 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 "audio/decoders/mp3.h"
+
+#ifdef USE_MAD
+
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+#include "audio/audiostream.h"
+
+#include <mad.h>
+
+#if defined(__PSP__)
+ #include "backends/platform/psp/mp3.h"
+#endif
+namespace Audio {
+
+
+#pragma mark -
+#pragma mark --- MP3 (MAD) stream ---
+#pragma mark -
+
+
+class MP3Stream : public SeekableAudioStream {
+protected:
+ enum State {
+ MP3_STATE_INIT, // Need to init the decoder
+ MP3_STATE_READY, // ready for processing data
+ MP3_STATE_EOS // end of data reached (may need to loop)
+ };
+
+ Common::SeekableReadStream *_inStream;
+ DisposeAfterUse::Flag _disposeAfterUse;
+
+ uint _posInFrame;
+ State _state;
+
+ Timestamp _length;
+ mad_timer_t _totalTime;
+
+ mad_stream _stream;
+ mad_frame _frame;
+ mad_synth _synth;
+
+ enum {
+ BUFFER_SIZE = 5 * 8192
+ };
+
+ // This buffer contains a slab of input data
+ byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD];
+
+public:
+ MP3Stream(Common::SeekableReadStream *inStream,
+ DisposeAfterUse::Flag dispose);
+ ~MP3Stream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return _state == MP3_STATE_EOS; }
+ bool isStereo() const { return MAD_NCHANNELS(&_frame.header) == 2; }
+ int getRate() const { return _frame.header.samplerate; }
+
+ bool seek(const Timestamp &where);
+ Timestamp getLength() const { return _length; }
+protected:
+ void decodeMP3Data();
+ void readMP3Data();
+
+ void initStream();
+ void readHeader();
+ void deinitStream();
+};
+
+MP3Stream::MP3Stream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
+ _inStream(inStream),
+ _disposeAfterUse(dispose),
+ _posInFrame(0),
+ _state(MP3_STATE_INIT),
+ _length(0, 1000),
+ _totalTime(mad_timer_zero) {
+
+ // The MAD_BUFFER_GUARD must always contain zeros (the reason
+ // for this is that the Layer III Huffman decoder of libMAD
+ // may read a few bytes beyond the end of the input buffer).
+ memset(_buf + BUFFER_SIZE, 0, MAD_BUFFER_GUARD);
+
+ // Calculate the length of the stream
+ initStream();
+
+ while (_state != MP3_STATE_EOS)
+ readHeader();
+
+ // To rule out any invalid sample rate to be encountered here, say in case the
+ // MP3 stream is invalid, we just check the MAD error code here.
+ // We need to assure this, since else we might trigger an assertion in Timestamp
+ // (When getRate() returns 0 or a negative number to be precise).
+ // Note that we allow "MAD_ERROR_BUFLEN" as error code here, since according
+ // to mad.h it is also set on EOF.
+ if ((_stream.error == MAD_ERROR_NONE || _stream.error == MAD_ERROR_BUFLEN) && getRate() > 0)
+ _length = Timestamp(mad_timer_count(_totalTime, MAD_UNITS_MILLISECONDS), getRate());
+
+ deinitStream();
+
+ // Reinit stream
+ _state = MP3_STATE_INIT;
+
+ // Decode the first chunk of data. This is necessary so that _frame
+ // is setup and isStereo() and getRate() return correct results.
+ decodeMP3Data();
+}
+
+MP3Stream::~MP3Stream() {
+ deinitStream();
+
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _inStream;
+}
+
+void MP3Stream::decodeMP3Data() {
+ do {
+ if (_state == MP3_STATE_INIT)
+ initStream();
+
+ if (_state == MP3_STATE_EOS)
+ return;
+
+ // If necessary, load more data into the stream decoder
+ if (_stream.error == MAD_ERROR_BUFLEN)
+ readMP3Data();
+
+ while (_state == MP3_STATE_READY) {
+ _stream.error = MAD_ERROR_NONE;
+
+ // Decode the next frame
+ if (mad_frame_decode(&_frame, &_stream) == -1) {
+ if (_stream.error == MAD_ERROR_BUFLEN) {
+ break; // Read more data
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ // Note: we will occasionally see MAD_ERROR_BADDATAPTR errors here.
+ // These are normal and expected (caused by our frame skipping (i.e. "seeking")
+ // code above).
+ debug(6, "MP3Stream: Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MP3Stream: Unrecoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ // Synthesize PCM data
+ mad_synth_frame(&_synth, &_frame);
+ _posInFrame = 0;
+ break;
+ }
+ } while (_state != MP3_STATE_EOS && _stream.error == MAD_ERROR_BUFLEN);
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _state = MP3_STATE_EOS;
+}
+
+void MP3Stream::readMP3Data() {
+ uint32 remaining = 0;
+
+ // Give up immediately if we already used up all data in the stream
+ if (_inStream->eos()) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ if (_stream.next_frame) {
+ // If there is still data in the MAD stream, we need to preserve it.
+ // Note that we use memmove, as we are reusing the same buffer,
+ // and hence the data regions we copy from and to may overlap.
+ remaining = _stream.bufend - _stream.next_frame;
+ assert(remaining < BUFFER_SIZE); // Paranoia check
+ memmove(_buf, _stream.next_frame, remaining);
+ }
+
+ // Try to read the next block
+ uint32 size = _inStream->read(_buf + remaining, BUFFER_SIZE - remaining);
+ if (size <= 0) {
+ _state = MP3_STATE_EOS;
+ return;
+ }
+
+ // Feed the data we just read into the stream decoder
+ _stream.error = MAD_ERROR_NONE;
+ mad_stream_buffer(&_stream, _buf, size + remaining);
+}
+
+bool MP3Stream::seek(const Timestamp &where) {
+ if (where == _length) {
+ _state = MP3_STATE_EOS;
+ return true;
+ } else if (where > _length) {
+ return false;
+ }
+
+ const uint32 time = where.msecs();
+
+ mad_timer_t destination;
+ mad_timer_set(&destination, time / 1000, time % 1000, 1000);
+
+ if (_state != MP3_STATE_READY || mad_timer_compare(destination, _totalTime) < 0)
+ initStream();
+
+ while (mad_timer_compare(destination, _totalTime) > 0 && _state != MP3_STATE_EOS)
+ readHeader();
+
+ decodeMP3Data();
+
+ return (_state != MP3_STATE_EOS);
+}
+
+void MP3Stream::initStream() {
+ if (_state != MP3_STATE_INIT)
+ deinitStream();
+
+ // Init MAD
+ mad_stream_init(&_stream);
+ mad_frame_init(&_frame);
+ mad_synth_init(&_synth);
+
+ // Reset the stream data
+ _inStream->seek(0, SEEK_SET);
+ _totalTime = mad_timer_zero;
+ _posInFrame = 0;
+
+ // Update state
+ _state = MP3_STATE_READY;
+
+ // Read the first few sample bytes
+ readMP3Data();
+}
+
+void MP3Stream::readHeader() {
+ if (_state != MP3_STATE_READY)
+ return;
+
+ // If necessary, load more data into the stream decoder
+ if (_stream.error == MAD_ERROR_BUFLEN)
+ readMP3Data();
+
+ while (_state != MP3_STATE_EOS) {
+ _stream.error = MAD_ERROR_NONE;
+
+ // Decode the next header. Note: mad_frame_decode would do this for us, too.
+ // However, for seeking we don't want to decode the full frame (else it would
+ // be far too slow). Hence we perform this explicitly in a separate step.
+ if (mad_header_decode(&_frame.header, &_stream) == -1) {
+ if (_stream.error == MAD_ERROR_BUFLEN) {
+ readMP3Data(); // Read more data
+ continue;
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ debug(6, "MP3Stream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MP3Stream: Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ // Sum up the total playback time so far
+ mad_timer_add(&_totalTime, _frame.header.duration);
+ break;
+ }
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _state = MP3_STATE_EOS;
+}
+
+void MP3Stream::deinitStream() {
+ if (_state == MP3_STATE_INIT)
+ return;
+
+ // Deinit MAD
+ mad_synth_finish(&_synth);
+ mad_frame_finish(&_frame);
+ mad_stream_finish(&_stream);
+
+ _state = MP3_STATE_EOS;
+}
+
+static inline int scale_sample(mad_fixed_t sample) {
+ // round
+ sample += (1L << (MAD_F_FRACBITS - 16));
+
+ // clip
+ if (sample > MAD_F_ONE - 1)
+ sample = MAD_F_ONE - 1;
+ else if (sample < -MAD_F_ONE)
+ sample = -MAD_F_ONE;
+
+ // quantize and scale to not saturate when mixing a lot of channels
+ return sample >> (MAD_F_FRACBITS + 1 - 16);
+}
+
+int MP3Stream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ // Keep going as long as we have input available
+ while (samples < numSamples && _state != MP3_STATE_EOS) {
+ const int len = MIN(numSamples, samples + (int)(_synth.pcm.length - _posInFrame) * MAD_NCHANNELS(&_frame.header));
+ while (samples < len) {
+ *buffer++ = (int16)scale_sample(_synth.pcm.samples[0][_posInFrame]);
+ samples++;
+ if (MAD_NCHANNELS(&_frame.header) == 2) {
+ *buffer++ = (int16)scale_sample(_synth.pcm.samples[1][_posInFrame]);
+ samples++;
+ }
+ _posInFrame++;
+ }
+ if (_posInFrame >= _synth.pcm.length) {
+ // We used up all PCM data in the current frame -- read & decode more
+ decodeMP3Data();
+ }
+ }
+ return samples;
+}
+
+
+#pragma mark -
+#pragma mark --- MP3 factory functions ---
+#pragma mark -
+
+SeekableAudioStream *makeMP3Stream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+
+#if defined(__PSP__)
+ SeekableAudioStream *s = 0;
+
+ if (Mp3PspStream::isOkToCreateStream())
+ s = new Mp3PspStream(stream, disposeAfterUse);
+
+ if (!s) // go to regular MAD mp3 stream if ME fails
+ s = new MP3Stream(stream, disposeAfterUse);
+#else
+ SeekableAudioStream *s = new MP3Stream(stream, disposeAfterUse);
+#endif
+ if (s && s->endOfData()) {
+ delete s;
+ return 0;
+ } else {
+ return s;
+ }
+}
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_MAD
diff --git a/audio/decoders/mp3.h b/audio/decoders/mp3.h
new file mode 100644
index 0000000000..72bc6e1b3e
--- /dev/null
+++ b/audio/decoders/mp3.h
@@ -0,0 +1,76 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - draci
+ * - kyra
+ * - m4
+ * - mohawk
+ * - queen
+ * - saga
+ * - sci
+ * - scumm
+ * - sword1
+ * - sword2
+ * - touche
+ * - tucker
+ */
+
+#ifndef SOUND_MP3_H
+#define SOUND_MP3_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+#ifdef USE_MAD
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+/**
+ * Create a new SeekableAudioStream from the MP3 data in the given stream.
+ * Allows for seeking (which is why we require a SeekableReadStream).
+ *
+ * @param stream the SeekableReadStream from which to read the MP3 data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeMP3Stream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_MAD
+#endif // #ifndef SOUND_MP3_H
diff --git a/audio/decoders/raw.cpp b/audio/decoders/raw.cpp
new file mode 100644
index 0000000000..8b833c7838
--- /dev/null
+++ b/audio/decoders/raw.cpp
@@ -0,0 +1,356 @@
+/* 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 "common/endian.h"
+#include "common/memstream.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+
+// This used to be an inline template function, but
+// buggy template function handling in MSVC6 forced
+// us to go with the macro approach. So far this is
+// the only template function that MSVC6 seemed to
+// compile incorrectly. Knock on wood.
+#define READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, ptr, isLE) \
+ ((is16Bit ? (isLE ? READ_LE_UINT16(ptr) : READ_BE_UINT16(ptr)) : (*ptr << 8)) ^ (isUnsigned ? 0x8000 : 0))
+
+
+#pragma mark -
+#pragma mark --- RawStream ---
+#pragma mark -
+
+/**
+ * This is a stream, which allows for playing raw PCM data from a stream.
+ * It also features playback of multiple blocks from a given stream.
+ */
+template<bool is16Bit, bool isUnsigned, bool isLE>
+class RawStream : public SeekableAudioStream {
+public:
+ RawStream(int rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream, const RawStreamBlockList &blocks)
+ : _rate(rate), _isStereo(stereo), _playtime(0, rate), _stream(stream), _disposeAfterUse(disposeStream), _blocks(blocks), _curBlock(_blocks.begin()), _blockLeft(0), _buffer(0) {
+
+ assert(_blocks.size() > 0);
+
+ // Setup our buffer for readBuffer
+ _buffer = new byte[kSampleBufferLength * (is16Bit ? 2 : 1)];
+ assert(_buffer);
+
+ // Set current buffer state, playing first block
+ _stream->seek(_curBlock->pos, SEEK_SET);
+
+ // In case of an error we will stop (or rather
+ // not start) stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ } else {
+ _blockLeft = _curBlock->len;
+ }
+
+ // Add up length of all blocks in order to caluclate total play time
+ int32 len = 0;
+ for (RawStreamBlockList::const_iterator i = _blocks.begin(); i != _blocks.end(); ++i) {
+ assert(i->len % (_isStereo ? 2 : 1) == 0);
+ len += i->len;
+ }
+
+ _playtime = Timestamp(0, len / (_isStereo ? 2 : 1), rate);
+ }
+
+ ~RawStream() {
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _stream;
+
+ delete[] _buffer;
+ }
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool isStereo() const { return _isStereo; }
+ bool endOfData() const { return (_curBlock == _blocks.end()) && (_blockLeft == 0); }
+
+ int getRate() const { return _rate; }
+ Timestamp getLength() const { return _playtime; }
+
+ bool seek(const Timestamp &where);
+private:
+ const int _rate; ///< Sample rate of stream
+ const bool _isStereo; ///< Whether this is an stereo stream
+ Timestamp _playtime; ///< Calculated total play time
+ Common::SeekableReadStream *_stream; ///< Stream to read data from
+ const DisposeAfterUse::Flag _disposeAfterUse; ///< Indicates whether the stream object should be deleted when this RawStream is destructed
+ const RawStreamBlockList _blocks; ///< Audio block list
+
+ RawStreamBlockList::const_iterator _curBlock; ///< Current audio block number
+ int32 _blockLeft; ///< How many bytes are still left in the current block
+
+ /**
+ * Advance one block in the stream in case
+ * the current one is empty.
+ */
+ void updateBlockIfNeeded();
+
+ byte *_buffer; ///< Buffer used in readBuffer
+ enum {
+ /**
+ * How many samples we can buffer at once.
+ *
+ * TODO: Check whether this size suffices
+ * for systems with slow disk I/O.
+ */
+ kSampleBufferLength = 2048
+ };
+
+ /**
+ * Fill the temporary sample buffer used in readBuffer.
+ *
+ * @param maxSamples Maximum samples to read.
+ * @return actual count of samples read.
+ */
+ int fillBuffer(int maxSamples);
+};
+
+template<bool is16Bit, bool isUnsigned, bool isLE>
+int RawStream<is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) {
+ int samplesLeft = numSamples;
+
+ while (samplesLeft > 0) {
+ // Try to read up to "samplesLeft" samples.
+ int len = fillBuffer(samplesLeft);
+
+ // In case we were not able to read any samples
+ // we will stop reading here.
+ if (!len)
+ break;
+
+ // Adjust the samples left to read.
+ samplesLeft -= len;
+
+ // Copy the data to the caller's buffer.
+ const byte *src = _buffer;
+ while (len-- > 0) {
+ *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, src, isLE);
+ src += (is16Bit ? 2 : 1);
+ }
+ }
+
+ return numSamples - samplesLeft;
+}
+
+template<bool is16Bit, bool isUnsigned, bool isLE>
+int RawStream<is16Bit, isUnsigned, isLE>::fillBuffer(int maxSamples) {
+ int bufferedSamples = 0;
+ byte *dst = _buffer;
+
+ // We can only read up to "kSampleBufferLength" samples
+ // so we take this into consideration, when trying to
+ // read up to maxSamples.
+ maxSamples = MIN<int>(kSampleBufferLength, maxSamples);
+
+ // We will only read up to maxSamples
+ while (maxSamples > 0 && !endOfData()) {
+ // Calculate how many samples we can safely read
+ // from the current block.
+ const int len = MIN<int>(maxSamples, _blockLeft);
+
+ // Try to read all the sample data and update the
+ // destination pointer.
+ const int bytesRead = _stream->read(dst, len * (is16Bit ? 2 : 1));
+ dst += bytesRead;
+
+ // Calculate how many samples we actually read.
+ const int samplesRead = bytesRead / (is16Bit ? 2 : 1);
+
+ // Update all status variables
+ bufferedSamples += samplesRead;
+ maxSamples -= samplesRead;
+ _blockLeft -= samplesRead;
+
+ // In case of an error we will stop
+ // stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ }
+
+ // Advance to the next block in case the current
+ // one is already finished.
+ updateBlockIfNeeded();
+ }
+
+ return bufferedSamples;
+}
+
+template<bool is16Bit, bool isUnsigned, bool isLE>
+void RawStream<is16Bit, isUnsigned, isLE>::updateBlockIfNeeded() {
+ // Have we now finished this block? If so, read the next block
+ if (_blockLeft == 0 && _curBlock != _blocks.end()) {
+ // Next block
+ ++_curBlock;
+
+ // Check whether we reached the end of the stream
+ // yet. In case we did not do this, we will just
+ // setup the next block as new block.
+ if (_curBlock != _blocks.end()) {
+ _stream->seek(_curBlock->pos, SEEK_SET);
+
+ // In case of an error we will stop
+ // stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ } else {
+ _blockLeft = _curBlock->len;
+ }
+ }
+ }
+}
+
+template<bool is16Bit, bool isUnsigned, bool isLE>
+bool RawStream<is16Bit, isUnsigned, isLE>::seek(const Timestamp &where) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+
+ if (where > _playtime)
+ return false;
+
+ const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames();
+ uint32 curSample = 0;
+
+ // Search for the disk block in which the specific sample is placed
+ for (_curBlock = _blocks.begin(); _curBlock != _blocks.end(); ++_curBlock) {
+ uint32 nextBlockSample = curSample + _curBlock->len;
+
+ if (nextBlockSample > seekSample)
+ break;
+
+ curSample = nextBlockSample;
+ }
+
+ if (_curBlock == _blocks.end()) {
+ return ((seekSample - curSample) == 0);
+ } else {
+ const uint32 offset = seekSample - curSample;
+
+ _stream->seek(_curBlock->pos + offset * (is16Bit ? 2 : 1), SEEK_SET);
+
+ // In case of an error we will stop
+ // stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ } else {
+ _blockLeft = _curBlock->len - offset;
+ }
+
+ return true;
+ }
+}
+
+#pragma mark -
+#pragma mark --- Raw stream factories ---
+#pragma mark -
+
+/* In the following, we use preprocessor / macro tricks to simplify the code
+ * which instantiates the input streams. We used to use template functions for
+ * this, but MSVC6 / EVC 3-4 (used for WinCE builds) are extremely buggy when it
+ * comes to this feature of C++... so as a compromise we use macros to cut down
+ * on the (source) code duplication a bit.
+ * So while normally macro tricks are said to make maintenance harder, in this
+ * particular case it should actually help it :-)
+ */
+
+#define MAKE_RAW_STREAM(UNSIGNED) \
+ if (is16Bit) { \
+ if (isLE) \
+ return new RawStream<true, UNSIGNED, true>(rate, isStereo, disposeAfterUse, stream, blockList); \
+ else \
+ return new RawStream<true, UNSIGNED, false>(rate, isStereo, disposeAfterUse, stream, blockList); \
+ } else \
+ return new RawStream<false, UNSIGNED, false>(rate, isStereo, disposeAfterUse, stream, blockList)
+
+SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
+ const RawStreamBlockList &blockList,
+ int rate,
+ byte flags,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ const bool isStereo = (flags & Audio::FLAG_STEREO) != 0;
+ const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0;
+ const bool isUnsigned = (flags & Audio::FLAG_UNSIGNED) != 0;
+ const bool isLE = (flags & Audio::FLAG_LITTLE_ENDIAN) != 0;
+
+ if (blockList.empty()) {
+ warning("Empty block list passed to makeRawStream");
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ if (isUnsigned) {
+ MAKE_RAW_STREAM(true);
+ } else {
+ MAKE_RAW_STREAM(false);
+ }
+}
+
+SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ RawStreamBlockList blocks;
+ RawStreamBlock block;
+ block.pos = 0;
+
+ const bool isStereo = (flags & Audio::FLAG_STEREO) != 0;
+ const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0;
+
+ assert(stream->size() % ((is16Bit ? 2 : 1) * (isStereo ? 2 : 1)) == 0);
+
+ block.len = stream->size() / (is16Bit ? 2 : 1);
+ blocks.push_back(block);
+
+ return makeRawStream(stream, blocks, rate, flags, disposeAfterUse);
+}
+
+SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ return makeRawStream(new Common::MemoryReadStream(buffer, size, disposeAfterUse), rate, flags, DisposeAfterUse::YES);
+}
+
+SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream, RawStreamBlock *block, int numBlocks,
+ int rate, byte flags, DisposeAfterUse::Flag disposeStream) {
+ assert(numBlocks > 0);
+ RawStreamBlockList blocks;
+ for (int i = 0; i < numBlocks; ++i)
+ blocks.push_back(block[i]);
+
+ return makeRawStream(stream, blocks, rate, flags, disposeStream);
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/raw.h b/audio/decoders/raw.h
new file mode 100644
index 0000000000..3e9426012c
--- /dev/null
+++ b/audio/decoders/raw.h
@@ -0,0 +1,153 @@
+/* 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$
+ *
+ */
+
+#ifndef SOUND_RAW_H
+#define SOUND_RAW_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+#include "common/list.h"
+
+
+namespace Common { class SeekableReadStream; }
+
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+/**
+ * Various flags which can be bit-ORed and then passed to
+ * makeRawStream and some other AudioStream factories
+ * to control their behavior.
+ *
+ * Engine authors are advised not to rely on a certain value or
+ * order of these flags (in particular, do not store them verbatim
+ * in savestates).
+ */
+enum RawFlags {
+ /** unsigned samples (default: signed) */
+ FLAG_UNSIGNED = 1 << 0,
+
+ /** sound is 16 bits wide (default: 8bit) */
+ FLAG_16BITS = 1 << 1,
+
+ /** samples are little endian (default: big endian) */
+ FLAG_LITTLE_ENDIAN = 1 << 2,
+
+ /** sound is in stereo (default: mono) */
+ FLAG_STEREO = 1 << 3
+};
+
+
+/**
+ * Struct used to define the audio data to be played by a RawStream.
+ */
+struct RawStreamBlock {
+ int32 pos; ///< Position in stream of the block (in bytes of course!)
+ int32 len; ///< Length of the block (in raw samples, not sample pairs!)
+};
+
+/**
+ * List containing all blocks of a raw stream.
+ * @see RawStreamBlock
+ */
+typedef Common::List<RawStreamBlock> RawStreamBlockList;
+
+/**
+ * Creates an audio stream, which plays from the given buffer.
+ *
+ * @param buffer Buffer to play from.
+ * @param size Size of the buffer in bytes.
+ * @param rate Rate of the sound data.
+ * @param flags Audio flags combination.
+ * @see RawFlags
+ * @param disposeAfterUse Whether to free the buffer after use (with free!).
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+/**
+ * Creates an audio stream, which plays from the given stream.
+ *
+ * @param stream Stream object to play from.
+ * @param rate Rate of the sound data.
+ * @param flags Audio flags combination.
+ * @see RawFlags
+ * @param disposeAfterUse Whether to delete the stream after use.
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+/**
+ * Creates an audio stream, which plays from the given stream.
+ *
+ * @param stream Stream object to play from.
+ * @param blockList List of blocks to play.
+ * @see RawDiskStreamAudioBlock
+ * @see RawStreamBlockList
+ * @param rate Rate of the sound data.
+ * @param flags Audio flags combination.
+ * @see RawFlags
+ * @param disposeAfterUse Whether to delete the stream after use.
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream,
+ const RawStreamBlockList &blockList,
+ int rate,
+ byte flags,
+ DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
+
+/**
+ * NOTE:
+ * This API is considered deprecated.
+ *
+ * Creates a audio stream, which plays from given stream.
+ *
+ * @param stream Stream to play from
+ * @param block Pointer to an RawStreamBlock array
+ * @see RawStreamBlock
+ * @param numBlocks Number of blocks.
+ * @param rate The rate
+ * @param flags Flags combination.
+ * @see RawFlags
+ * @param disposeStream Whether the "stream" object should be destroyed after playback.
+ * @return The new SeekableAudioStream (or 0 on failure).
+ */
+SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream,
+ RawStreamBlock *block, int numBlocks,
+ int rate, byte flags,
+ DisposeAfterUse::Flag disposeStream);
+
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/vag.cpp b/audio/decoders/vag.cpp
new file mode 100644
index 0000000000..2c3a36202a
--- /dev/null
+++ b/audio/decoders/vag.cpp
@@ -0,0 +1,150 @@
+/* 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 "audio/decoders/vag.h"
+#include "audio/audiostream.h"
+#include "common/stream.h"
+
+namespace Audio {
+
+class VagStream : public Audio::RewindableAudioStream {
+public:
+ VagStream(Common::SeekableReadStream *stream, int rate);
+ ~VagStream();
+
+ bool isStereo() const { return false; }
+ bool endOfData() const { return _stream->pos() == _stream->size(); }
+ int getRate() const { return _rate; }
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool rewind();
+private:
+ Common::SeekableReadStream *_stream;
+
+ byte _predictor;
+ double _samples[28];
+ byte _samplesRemaining;
+ int _rate;
+ double _s1, _s2;
+};
+
+VagStream::VagStream(Common::SeekableReadStream *stream, int rate) : _stream(stream) {
+ _samplesRemaining = 0;
+ _predictor = 0;
+ _s1 = _s2 = 0.0;
+ _rate = rate;
+}
+
+
+VagStream::~VagStream() {
+ delete _stream;
+}
+
+static const double s_vagDataTable[5][2] =
+ {
+ { 0.0, 0.0 },
+ { 60.0 / 64.0, 0.0 },
+ { 115.0 / 64.0, -52.0 / 64.0 },
+ { 98.0 / 64.0, -55.0 / 64.0 },
+ { 122.0 / 64.0, -60.0 / 64.0 }
+ };
+
+int VagStream::readBuffer(int16 *buffer, const int numSamples) {
+ int32 samplesDecoded = 0;
+
+ if (_samplesRemaining) {
+ byte i = 0;
+
+ for (i = 28 - _samplesRemaining; i < 28 && samplesDecoded < numSamples; i++) {
+ _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1];
+ _s2 = _s1;
+ _s1 = _samples[i];
+ int16 d = (int) (_samples[i] + 0.5);
+ buffer[samplesDecoded] = d;
+ samplesDecoded++;
+ }
+
+#if 0
+ assert(i == 28); // We're screwed if this fails :P
+#endif
+ // This might mean the file is corrupted, or that the stream has
+ // been closed.
+ if (i != 28) return 0;
+
+ _samplesRemaining = 0;
+ }
+
+ while (samplesDecoded < numSamples) {
+ byte i = 0;
+
+ _predictor = _stream->readByte();
+ byte shift = _predictor & 0xf;
+ _predictor >>= 4;
+
+ if (_stream->readByte() == 7)
+ return samplesDecoded;
+
+ for (i = 0; i < 28; i += 2) {
+ byte d = _stream->readByte();
+ int16 s = (d & 0xf) << 12;
+ if (s & 0x8000)
+ s |= 0xffff0000;
+ _samples[i] = (double)(s >> shift);
+ s = (d & 0xf0) << 8;
+ if (s & 0x8000)
+ s |= 0xffff0000;
+ _samples[i + 1] = (double)(s >> shift);
+ }
+
+ for (i = 0; i < 28 && samplesDecoded < numSamples; i++) {
+ _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1];
+ _s2 = _s1;
+ _s1 = _samples[i];
+ int16 d = (int) (_samples[i] + 0.5);
+ buffer[samplesDecoded] = d;
+ samplesDecoded++;
+ }
+
+ if (i != 27)
+ _samplesRemaining = 28 - i;
+ }
+
+ return samplesDecoded;
+}
+
+bool VagStream::rewind() {
+ _stream->seek(0);
+ _samplesRemaining = 0;
+ _predictor = 0;
+ _s1 = _s2 = 0.0;
+
+ return true;
+}
+
+RewindableAudioStream *makeVagStream(Common::SeekableReadStream *stream, int rate) {
+ return new VagStream(stream, rate);
+}
+
+}
diff --git a/audio/decoders/vag.h b/audio/decoders/vag.h
new file mode 100644
index 0000000000..cdf91a8ea1
--- /dev/null
+++ b/audio/decoders/vag.h
@@ -0,0 +1,60 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - sword1 (PSX port of the game)
+ * - sword2 (PSX port of the game)
+ * - tinsel (PSX port of the game)
+ */
+
+#ifndef SOUND_VAG_H
+#define SOUND_VAG_H
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+class RewindableAudioStream;
+
+/**
+ * Takes an input stream containing Vag sound data and creates
+ * an RewindableAudioStream from that.
+ *
+ * @param stream the SeekableReadStream from which to read the ADPCM data
+ * @param rate the sampling rate
+ * @return a new RewindableAudioStream, or NULL, if an error occurred
+ */
+RewindableAudioStream *makeVagStream(
+ Common::SeekableReadStream *stream,
+ int rate = 11025);
+
+} // End of namespace Sword1
+
+#endif
diff --git a/audio/decoders/voc.cpp b/audio/decoders/voc.cpp
new file mode 100644
index 0000000000..b811a640ec
--- /dev/null
+++ b/audio/decoders/voc.cpp
@@ -0,0 +1,403 @@
+/* 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 "common/debug.h"
+#include "common/endian.h"
+#include "common/util.h"
+#include "common/stream.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/raw.h"
+#include "audio/decoders/voc.h"
+
+
+namespace Audio {
+
+int getSampleRateFromVOCRate(int vocSR) {
+ if (vocSR == 0xa5 || vocSR == 0xa6) {
+ return 11025;
+ } else if (vocSR == 0xd2 || vocSR == 0xd3) {
+ return 22050;
+ } else {
+ int sr = 1000000L / (256L - vocSR);
+ // inexact sampling rates occur e.g. in the kitchen in Monkey Island,
+ // very easy to reach right from the start of the game.
+ //warning("inexact sample rate used: %i (0x%x)", sr, vocSR);
+ return sr;
+ }
+}
+
+static byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate, int &loops, int &begin_loop, int &end_loop) {
+ VocFileHeader fileHeader;
+
+ debug(2, "loadVOCFromStream");
+
+ if (stream.read(&fileHeader, 8) != 8)
+ goto invalid;
+
+ if (!memcmp(&fileHeader, "VTLK", 4)) {
+ if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader))
+ goto invalid;
+ } else if (!memcmp(&fileHeader, "Creative", 8)) {
+ if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8)
+ goto invalid;
+ } else {
+ invalid:;
+ warning("loadVOCFromStream: Invalid header");
+ return NULL;
+ }
+
+ if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0)
+ error("loadVOCFromStream: Invalid header");
+ if (fileHeader.desc[19] != 0x1A)
+ debug(3, "loadVOCFromStream: Partially invalid header");
+
+ int32 offset = FROM_LE_16(fileHeader.datablock_offset);
+ int16 version = FROM_LE_16(fileHeader.version);
+ int16 code = FROM_LE_16(fileHeader.id);
+ assert(offset == sizeof(VocFileHeader));
+ // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and
+ // French version of Simon the Sorcerer 2 (CD)
+ assert(version == 0x010A || version == 0x0114 || version == 0x0100);
+ assert(code == ~version + 0x1234);
+
+ int len;
+ byte *ret_sound = 0;
+ size = 0;
+ begin_loop = 0;
+ end_loop = 0;
+
+ while ((code = stream.readByte())) {
+ len = stream.readByte();
+ len |= stream.readByte() << 8;
+ len |= stream.readByte() << 16;
+
+ debug(2, "Block code %d, len %d", code, len);
+
+ switch (code) {
+ case 1:
+ case 9: {
+ int packing;
+ if (code == 1) {
+ int time_constant = stream.readByte();
+ packing = stream.readByte();
+ len -= 2;
+ rate = getSampleRateFromVOCRate(time_constant);
+ } else {
+ rate = stream.readUint32LE();
+ int bits = stream.readByte();
+ int channels = stream.readByte();
+ if (bits != 8 || channels != 1) {
+ warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels);
+ break;
+ }
+ packing = stream.readUint16LE();
+ stream.readUint32LE();
+ len -= 12;
+ }
+ debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len);
+ if (packing == 0) {
+ if (size) {
+ ret_sound = (byte *)realloc(ret_sound, size + len);
+ } else {
+ ret_sound = (byte *)malloc(len);
+ }
+ stream.read(ret_sound + size, len);
+ size += len;
+ begin_loop = size;
+ end_loop = size;
+ } else {
+ warning("VOC file packing %d unsupported", packing);
+ }
+ } break;
+ case 3: // silence
+ // occur with a few Igor sounds, voc file starts with a silence block with a
+ // frequency different from the data block. Just ignore fow now (implementing
+ // it wouldn't make a big difference anyway...)
+ assert(len == 3);
+ stream.readUint16LE();
+ stream.readByte();
+ break;
+ case 6: // begin of loop
+ assert(len == 2);
+ loops = stream.readUint16LE();
+ break;
+ case 7: // end of loop
+ assert(len == 0);
+ break;
+ case 8: { // "Extended"
+ // This occures in the LoL Intro demo.
+ // This block overwrites the next parameters of a block 1 "Sound data".
+ // To assure we never get any bad data here, we will assert in case
+ // this tries to define a stereo sound block or tries to use something
+ // different than 8bit unsigned sound data.
+ // TODO: Actually we would need to check the frequency divisor (the
+ // first word) here too. It is used in the following equation:
+ // sampleRate = 256000000/(channels * (65536 - frequencyDivisor))
+ assert(len == 4);
+ stream.readUint16LE();
+ uint8 codec = stream.readByte();
+ uint8 channels = stream.readByte() + 1;
+ assert(codec == 0 && channels == 1);
+ } break;
+ default:
+ warning("Unhandled code %d in VOC file (len %d)", code, len);
+ return ret_sound;
+ }
+ }
+ debug(4, "VOC Data Size : %d", size);
+ return ret_sound;
+}
+
+byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate) {
+ int loops, begin_loop, end_loop;
+ return loadVOCFromStream(stream, size, rate, loops, begin_loop, end_loop);
+}
+
+
+#ifdef STREAM_AUDIO_FROM_DISK
+
+int parseVOCFormat(Common::SeekableReadStream& stream, RawStreamBlock* block, int &rate, int &loops, int &begin_loop, int &end_loop) {
+ VocFileHeader fileHeader;
+ int currentBlock = 0;
+ int size = 0;
+
+ debug(2, "parseVOCFormat");
+
+ if (stream.read(&fileHeader, 8) != 8)
+ goto invalid;
+
+ if (!memcmp(&fileHeader, "VTLK", 4)) {
+ if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader))
+ goto invalid;
+ } else if (!memcmp(&fileHeader, "Creative", 8)) {
+ if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8)
+ goto invalid;
+ } else {
+ invalid:;
+ warning("loadVOCFromStream: Invalid header");
+ return 0;
+ }
+
+ if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0)
+ error("loadVOCFromStream: Invalid header");
+ if (fileHeader.desc[19] != 0x1A)
+ debug(3, "loadVOCFromStream: Partially invalid header");
+
+ int32 offset = FROM_LE_16(fileHeader.datablock_offset);
+ int16 version = FROM_LE_16(fileHeader.version);
+ int16 code = FROM_LE_16(fileHeader.id);
+ assert(offset == sizeof(VocFileHeader));
+ // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and
+ // French version of Simon the Sorcerer 2 (CD)
+ assert(version == 0x010A || version == 0x0114 || version == 0x0100);
+ assert(code == ~version + 0x1234);
+
+ int len;
+ size = 0;
+ begin_loop = 0;
+ end_loop = 0;
+
+ while ((code = stream.readByte())) {
+ len = stream.readByte();
+ len |= stream.readByte() << 8;
+ len |= stream.readByte() << 16;
+
+ debug(2, "Block code %d, len %d", code, len);
+
+ switch (code) {
+ case 1:
+ case 9: {
+ int packing;
+ if (code == 1) {
+ int time_constant = stream.readByte();
+ packing = stream.readByte();
+ len -= 2;
+ rate = getSampleRateFromVOCRate(time_constant);
+ } else {
+ rate = stream.readUint32LE();
+ int bits = stream.readByte();
+ int channels = stream.readByte();
+ if (bits != 8 || channels != 1) {
+ warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels);
+ break;
+ }
+ packing = stream.readUint16LE();
+ stream.readUint32LE();
+ len -= 12;
+ }
+ debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len);
+ if (packing == 0) {
+
+ // Found a data block - so add it to the block list
+ block[currentBlock].pos = stream.pos();
+ block[currentBlock].len = len;
+ currentBlock++;
+
+ stream.seek(len, SEEK_CUR);
+
+ size += len;
+ begin_loop = size;
+ end_loop = size;
+ } else {
+ warning("VOC file packing %d unsupported", packing);
+ }
+ } break;
+ case 3: // silence
+ // occur with a few Igor sounds, voc file starts with a silence block with a
+ // frequency different from the data block. Just ignore fow now (implementing
+ // it wouldn't make a big difference anyway...)
+ assert(len == 3);
+ stream.readUint16LE();
+ stream.readByte();
+ break;
+ case 6: // begin of loop
+ assert(len == 2);
+ loops = stream.readUint16LE();
+ break;
+ case 7: // end of loop
+ assert(len == 0);
+ break;
+ case 8: // "Extended"
+ // This occures in the LoL Intro demo. This block can usually be used to create stereo
+ // sound, but the LoL intro has only an empty block, thus this dummy implementation will
+ // work.
+ assert(len == 4);
+ stream.readUint16LE();
+ stream.readByte();
+ stream.readByte();
+ break;
+ default:
+ warning("Unhandled code %d in VOC file (len %d)", code, len);
+ return 0;
+ }
+ }
+ debug(4, "VOC Data Size : %d", size);
+ return currentBlock;
+}
+
+AudioStream *makeVOCDiskStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) {
+ const int MAX_AUDIO_BLOCKS = 256;
+
+ RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS];
+ int rate, loops, begin_loop, end_loop;
+
+ int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop);
+
+ AudioStream *audioStream = 0;
+
+ // Create an audiostream from the data. Note the numBlocks may be 0,
+ // e.g. when invalid data is encountered. See bug #2890038.
+ if (numBlocks)
+ audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse/*, begin_loop, end_loop*/);
+
+ delete[] block;
+
+ return audioStream;
+}
+
+SeekableAudioStream *makeVOCDiskStreamNoLoop(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) {
+ const int MAX_AUDIO_BLOCKS = 256;
+
+ RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS];
+ int rate, loops, begin_loop, end_loop;
+
+ int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop);
+
+ SeekableAudioStream *audioStream = 0;
+
+ // Create an audiostream from the data. Note the numBlocks may be 0,
+ // e.g. when invalid data is encountered. See bug #2890038.
+ if (numBlocks)
+ audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse);
+
+ delete[] block;
+
+ return audioStream;
+}
+
+#endif
+
+
+AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, uint loopStart, uint loopEnd, DisposeAfterUse::Flag disposeAfterUse) {
+#ifdef STREAM_AUDIO_FROM_DISK
+ return makeVOCDiskStream(stream, flags, disposeAfterUse);
+#else
+ int size, rate;
+
+ byte *data = loadVOCFromStream(*stream, size, rate);
+
+ if (!data) {
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ SeekableAudioStream *s = Audio::makeRawStream(data, size, rate, flags);
+
+ if (loopStart != loopEnd) {
+ const bool isStereo = (flags & Audio::FLAG_STEREO) != 0;
+ const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0;
+
+ if (loopEnd == 0)
+ loopEnd = size;
+ assert(loopStart <= loopEnd);
+ assert(loopEnd <= (uint)size);
+
+ // Verify the buffer sizes are sane
+ if (is16Bit && isStereo)
+ assert((loopStart & 3) == 0 && (loopEnd & 3) == 0);
+ else if (is16Bit || isStereo)
+ assert((loopStart & 1) == 0 && (loopEnd & 1) == 0);
+
+ const uint32 extRate = s->getRate() * (is16Bit ? 2 : 1) * (isStereo ? 2 : 1);
+
+ return new SubLoopingAudioStream(s, 0, Timestamp(0, loopStart, extRate), Timestamp(0, loopEnd, extRate));
+ } else {
+ return s;
+ }
+#endif
+}
+
+SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) {
+#ifdef STREAM_AUDIO_FROM_DISK
+ return makeVOCDiskStreamNoLoop(stream, flags, disposeAfterUse);
+#else
+ int size, rate;
+
+ byte *data = loadVOCFromStream(*stream, size, rate);
+
+ if (!data) {
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ return makeRawStream(data, size, rate, flags);
+#endif
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/voc.h b/audio/decoders/voc.h
new file mode 100644
index 0000000000..82cc261f2c
--- /dev/null
+++ b/audio/decoders/voc.h
@@ -0,0 +1,107 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - drascula
+ * - kyra
+ * - made
+ * - saga
+ * - scumm
+ * - touche
+ */
+
+#ifndef SOUND_VOC_H
+#define SOUND_VOC_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+namespace Common { class ReadStream; }
+namespace Common { class SeekableReadStream; }
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+struct VocFileHeader {
+ uint8 desc[20];
+ uint16 datablock_offset;
+ uint16 version;
+ uint16 id;
+} PACKED_STRUCT;
+
+struct VocBlockHeader {
+ uint8 blocktype;
+ uint8 size[3];
+ uint8 sr;
+ uint8 pack;
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+/**
+ * Take a sample rate parameter as it occurs in a VOC sound header, and
+ * return the corresponding sample frequency.
+ *
+ * This method has special cases for the standard rates of 11025 and 22050 kHz,
+ * which due to limitations of the format, cannot be encoded exactly in a VOC
+ * file. As a consequence, many game files have sound data sampled with those
+ * rates, but the VOC marks them incorrectly as 11111 or 22222 kHz. This code
+ * works around that and "unrounds" the sampling rates.
+ */
+extern int getSampleRateFromVOCRate(int vocSR);
+
+/**
+ * Try to load a VOC from the given stream. Returns a pointer to memory
+ * containing the PCM sample data (allocated with malloc). It is the callers
+ * responsibility to dellocate that data again later on! Currently this
+ * function only supports uncompressed raw PCM data.
+ */
+extern byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate);
+
+/**
+ * Try to load a VOC from the given seekable stream and create an AudioStream
+ * from that data. Currently this function only supports uncompressed raw PCM
+ * data. Optionally supports (infinite) looping of a portion of the data.
+ *
+ * This function uses loadVOCFromStream() internally.
+ */
+AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags = 0, uint loopStart = 0, uint loopEnd = 0, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::NO);
+
+/**
+ * This does not use any of the looping features of VOC files!
+ */
+SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif
diff --git a/audio/decoders/vorbis.cpp b/audio/decoders/vorbis.cpp
new file mode 100644
index 0000000000..dc37e852d3
--- /dev/null
+++ b/audio/decoders/vorbis.cpp
@@ -0,0 +1,262 @@
+/* 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$
+ *
+ */
+
+// Disable symbol overrides for FILE and fseek as those are used in the
+// Vorbis headers.
+#define FORBIDDEN_SYMBOL_EXCEPTION_FILE
+#define FORBIDDEN_SYMBOL_EXCEPTION_fseek
+
+#include "audio/decoders/vorbis.h"
+
+#ifdef USE_VORBIS
+
+#include "common/debug.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+#include "audio/audiostream.h"
+
+#ifdef USE_TREMOR
+#if defined(__GP32__) // custom libtremor locations
+#include <ivorbisfile.h>
+#else
+#include <tremor/ivorbisfile.h>
+#endif
+#else
+#include <vorbis/vorbisfile.h>
+#endif
+
+
+namespace Audio {
+
+// These are wrapper functions to allow using a SeekableReadStream object to
+// provide data to the OggVorbis_File object.
+
+static size_t read_stream_wrap(void *ptr, size_t size, size_t nmemb, void *datasource) {
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
+
+ uint32 result = stream->read(ptr, size * nmemb);
+
+ return result / size;
+}
+
+static int seek_stream_wrap(void *datasource, ogg_int64_t offset, int whence) {
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
+ stream->seek((int32)offset, whence);
+ return stream->pos();
+}
+
+static int close_stream_wrap(void *datasource) {
+ // Do nothing -- we leave it up to the VorbisStream to free memory as appropriate.
+ return 0;
+}
+
+static long tell_stream_wrap(void *datasource) {
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource;
+ return stream->pos();
+}
+
+static ov_callbacks g_stream_wrap = {
+ read_stream_wrap, seek_stream_wrap, close_stream_wrap, tell_stream_wrap
+};
+
+
+
+#pragma mark -
+#pragma mark --- Ogg Vorbis stream ---
+#pragma mark -
+
+
+class VorbisStream : public SeekableAudioStream {
+protected:
+ Common::SeekableReadStream *_inStream;
+ DisposeAfterUse::Flag _disposeAfterUse;
+
+ bool _isStereo;
+ int _rate;
+
+ Timestamp _length;
+
+ OggVorbis_File _ovFile;
+
+ int16 _buffer[4096];
+ const int16 *_bufferEnd;
+ const int16 *_pos;
+
+public:
+ // startTime / duration are in milliseconds
+ VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose);
+ ~VorbisStream();
+
+ int readBuffer(int16 *buffer, const int numSamples);
+
+ bool endOfData() const { return _pos >= _bufferEnd; }
+ bool isStereo() const { return _isStereo; }
+ int getRate() const { return _rate; }
+
+ bool seek(const Timestamp &where);
+ Timestamp getLength() const { return _length; }
+protected:
+ bool refill();
+};
+
+VorbisStream::VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
+ _inStream(inStream),
+ _disposeAfterUse(dispose),
+ _length(0, 1000),
+ _bufferEnd(_buffer + ARRAYSIZE(_buffer)) {
+
+ int res = ov_open_callbacks(inStream, &_ovFile, NULL, 0, g_stream_wrap);
+ if (res < 0) {
+ warning("Could not create Vorbis stream (%d)", res);
+ _pos = _bufferEnd;
+ return;
+ }
+
+ // Read in initial data
+ if (!refill())
+ return;
+
+ // Setup some header information
+ _isStereo = ov_info(&_ovFile, -1)->channels >= 2;
+ _rate = ov_info(&_ovFile, -1)->rate;
+
+#ifdef USE_TREMOR
+ _length = Timestamp(ov_time_total(&_ovFile, -1), getRate());
+#else
+ _length = Timestamp(uint32(ov_time_total(&_ovFile, -1) * 1000.0), getRate());
+#endif
+}
+
+VorbisStream::~VorbisStream() {
+ ov_clear(&_ovFile);
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _inStream;
+}
+
+int VorbisStream::readBuffer(int16 *buffer, const int numSamples) {
+ int samples = 0;
+ while (samples < numSamples && _pos < _bufferEnd) {
+ const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos));
+ memcpy(buffer, _pos, len * 2);
+ buffer += len;
+ _pos += len;
+ samples += len;
+ if (_pos >= _bufferEnd) {
+ if (!refill())
+ break;
+ }
+ }
+ return samples;
+}
+
+bool VorbisStream::seek(const Timestamp &where) {
+ // Vorbisfile uses the sample pair number, thus we always use "false" for the isStereo parameter
+ // of the convertTimeToStreamPos helper.
+ int res = ov_pcm_seek(&_ovFile, convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames());
+ if (res) {
+ warning("Error seeking in Vorbis stream (%d)", res);
+ _pos = _bufferEnd;
+ return false;
+ }
+
+ return refill();
+}
+
+bool VorbisStream::refill() {
+ // Read the samples
+ uint len_left = sizeof(_buffer);
+ char *read_pos = (char *)_buffer;
+
+ while (len_left > 0) {
+ long result;
+
+#ifdef USE_TREMOR
+ // Tremor ov_read() always returns data as signed 16 bit interleaved PCM
+ // in host byte order. As such, it does not take arguments to request
+ // specific signedness, byte order or bit depth as in Vorbisfile.
+ result = ov_read(&_ovFile, read_pos, len_left,
+ NULL);
+#else
+#ifdef SCUMM_BIG_ENDIAN
+ result = ov_read(&_ovFile, read_pos, len_left,
+ 1,
+ 2, // 16 bit
+ 1, // signed
+ NULL);
+#else
+ result = ov_read(&_ovFile, read_pos, len_left,
+ 0,
+ 2, // 16 bit
+ 1, // signed
+ NULL);
+#endif
+#endif
+ if (result == OV_HOLE) {
+ // Possibly recoverable, just warn about it
+ warning("Corrupted data in Vorbis file");
+ } else if (result == 0) {
+ //warning("End of file while reading from Vorbis file");
+ //_pos = _bufferEnd;
+ //return false;
+ break;
+ } else if (result < 0) {
+ warning("Error reading from Vorbis stream (%d)", int(result));
+ _pos = _bufferEnd;
+ // Don't delete it yet, that causes problems in
+ // the CD player emulation code.
+ return false;
+ } else {
+ len_left -= result;
+ read_pos += result;
+ }
+ }
+
+ _pos = _buffer;
+ _bufferEnd = (int16 *)read_pos;
+
+ return true;
+}
+
+
+#pragma mark -
+#pragma mark --- Ogg Vorbis factory functions ---
+#pragma mark -
+
+SeekableAudioStream *makeVorbisStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse) {
+ SeekableAudioStream *s = new VorbisStream(stream, disposeAfterUse);
+ if (s && s->endOfData()) {
+ delete s;
+ return 0;
+ } else {
+ return s;
+ }
+}
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_VORBIS
diff --git a/audio/decoders/vorbis.h b/audio/decoders/vorbis.h
new file mode 100644
index 0000000000..7cc395cccb
--- /dev/null
+++ b/audio/decoders/vorbis.h
@@ -0,0 +1,75 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - draci
+ * - kyra
+ * - m4
+ * - queen
+ * - saga
+ * - sci
+ * - scumm
+ * - sword1
+ * - sword2
+ * - touche
+ * - tucker
+ */
+
+#ifndef SOUND_VORBIS_H
+#define SOUND_VORBIS_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+#ifdef USE_VORBIS
+
+namespace Common {
+ class SeekableReadStream;
+}
+
+namespace Audio {
+
+class AudioStream;
+class SeekableAudioStream;
+
+/**
+ * Create a new SeekableAudioStream from the Ogg Vorbis data in the given stream.
+ * Allows for seeking (which is why we require a SeekableReadStream).
+ *
+ * @param stream the SeekableReadStream from which to read the Ogg Vorbis data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new SeekableAudioStream, or NULL, if an error occurred
+ */
+SeekableAudioStream *makeVorbisStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif // #ifdef USE_VORBIS
+#endif // #ifndef SOUND_VORBIS_H
diff --git a/audio/decoders/wave.cpp b/audio/decoders/wave.cpp
new file mode 100644
index 0000000000..1f0ddd8ceb
--- /dev/null
+++ b/audio/decoders/wave.cpp
@@ -0,0 +1,194 @@
+/* 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 "common/debug.h"
+#include "common/util.h"
+#include "common/stream.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+#include "audio/decoders/wave.h"
+#include "audio/decoders/adpcm.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+
+bool loadWAVFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags, uint16 *wavType, int *blockAlign_) {
+ const int32 initialPos = stream.pos();
+ byte buf[4+1];
+
+ buf[4] = 0;
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "RIFF", 4) != 0) {
+ warning("getWavInfo: No 'RIFF' header");
+ return false;
+ }
+
+ int32 wavLength = stream.readUint32LE();
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "WAVE", 4) != 0) {
+ warning("getWavInfo: No 'WAVE' header");
+ return false;
+ }
+
+ stream.read(buf, 4);
+ if (memcmp(buf, "fmt ", 4) != 0) {
+ warning("getWavInfo: No 'fmt' header");
+ return false;
+ }
+
+ uint32 fmtLength = stream.readUint32LE();
+ if (fmtLength < 16) {
+ // A valid fmt chunk always contains at least 16 bytes
+ warning("getWavInfo: 'fmt' header is too short");
+ return false;
+ }
+
+ // Next comes the "type" field of the fmt header. Some typical
+ // values for it:
+ // 1 -> uncompressed PCM
+ // 17 -> IMA ADPCM compressed WAVE
+ // See <http://www.saettler.com/RIFFNEW/RIFFNEW.htm> for a more complete
+ // list of common WAVE compression formats...
+ uint16 type = stream.readUint16LE(); // == 1 for PCM data
+ uint16 numChannels = stream.readUint16LE(); // 1 for mono, 2 for stereo
+ uint32 samplesPerSec = stream.readUint32LE(); // in Hz
+ uint32 avgBytesPerSec = stream.readUint32LE(); // == SampleRate * NumChannels * BitsPerSample/8
+
+ uint16 blockAlign = stream.readUint16LE(); // == NumChannels * BitsPerSample/8
+ uint16 bitsPerSample = stream.readUint16LE(); // 8, 16 ...
+ // 8 bit data is unsigned, 16 bit data signed
+
+
+ if (wavType != 0)
+ *wavType = type;
+
+ if (blockAlign_ != 0)
+ *blockAlign_ = blockAlign;
+#if 0
+ debug("WAVE information:");
+ debug(" total size: %d", wavLength);
+ debug(" fmt size: %d", fmtLength);
+ debug(" type: %d", type);
+ debug(" numChannels: %d", numChannels);
+ debug(" samplesPerSec: %d", samplesPerSec);
+ debug(" avgBytesPerSec: %d", avgBytesPerSec);
+ debug(" blockAlign: %d", blockAlign);
+ debug(" bitsPerSample: %d", bitsPerSample);
+#endif
+
+ 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 && type != 2) {
+ debug(0, "getWavInfo: blockAlign is invalid");
+ }
+
+ if (avgBytesPerSec != samplesPerSec * blockAlign && type != 2) {
+ debug(0, "getWavInfo: avgBytesPerSec is invalid");
+ }
+
+ // Prepare the return values.
+ rate = samplesPerSec;
+
+ flags = 0;
+ if (bitsPerSample == 8) // 8 bit data is unsigned
+ flags |= Audio::FLAG_UNSIGNED;
+ else if (bitsPerSample == 16) // 16 bit data is signed little endian
+ flags |= (Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
+ else if (bitsPerSample == 4 && (type == 2 || type == 17))
+ flags |= Audio::FLAG_16BITS;
+ else {
+ warning("getWavInfo: unsupported bitsPerSample %d", bitsPerSample);
+ return false;
+ }
+
+ if (numChannels == 2)
+ flags |= Audio::FLAG_STEREO;
+ else if (numChannels != 1) {
+ warning("getWavInfo: unsupported number of channels %d", numChannels);
+ return false;
+ }
+
+ // It's almost certainly a WAV file, but we still need to find its
+ // 'data' chunk.
+
+ // Skip over the rest of the fmt chunk.
+ int offset = fmtLength - 16;
+
+ do {
+ stream.seek(offset, SEEK_CUR);
+ if (stream.pos() >= initialPos + wavLength + 8) {
+ warning("getWavInfo: Can't find 'data' chunk");
+ return false;
+ }
+ stream.read(buf, 4);
+ offset = stream.readUint32LE();
+
+#if 0
+ debug(" found a '%s' tag of size %d", buf, offset);
+#endif
+ } while (memcmp(buf, "data", 4) != 0);
+
+ // Stream now points at 'offset' bytes of sample data...
+ size = offset;
+
+ return true;
+}
+
+RewindableAudioStream *makeWAVStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
+ int size, rate;
+ byte flags;
+ uint16 type;
+ int blockAlign;
+
+ if (!loadWAVFromStream(*stream, size, rate, flags, &type, &blockAlign)) {
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+ return 0;
+ }
+
+ if (type == 17) // MS IMA ADPCM
+ return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMSIma, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign);
+ else if (type == 2) // MS ADPCM
+ return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMS, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign);
+
+ // Raw PCM. Just read everything at once.
+ // TODO: More elegant would be to wrap the stream.
+ byte *data = (byte *)malloc(size);
+ assert(data);
+ stream->read(data, size);
+
+ if (disposeAfterUse == DisposeAfterUse::YES)
+ delete stream;
+
+ return makeRawStream(data, size, rate, flags);
+}
+
+} // End of namespace Audio
diff --git a/audio/decoders/wave.h b/audio/decoders/wave.h
new file mode 100644
index 0000000000..2bdbe8f0b6
--- /dev/null
+++ b/audio/decoders/wave.h
@@ -0,0 +1,84 @@
+/* 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$
+ *
+ */
+
+/**
+ * @file
+ * Sound decoder used in engines:
+ * - agos
+ * - gob
+ * - mohawk
+ * - saga
+ * - sci
+ * - scumm
+ * - sword1
+ * - sword2
+ * - tucker
+ */
+
+#ifndef SOUND_WAVE_H
+#define SOUND_WAVE_H
+
+#include "common/scummsys.h"
+#include "common/types.h"
+
+namespace Common { class SeekableReadStream; }
+
+namespace Audio {
+
+class RewindableAudioStream;
+
+/**
+ * Try to load a WAVE from the given seekable stream. Returns true if
+ * successful. In that case, the stream's seek position will be set to the
+ * start of the audio data, and size, rate and flags contain information
+ * necessary for playback. Currently this function supports uncompressed
+ * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally).
+ */
+extern bool loadWAVFromStream(
+ Common::SeekableReadStream &stream,
+ int &size,
+ int &rate,
+ byte &flags,
+ uint16 *wavType = 0,
+ int *blockAlign = 0);
+
+/**
+ * Try to load a WAVE from the given seekable stream and create an AudioStream
+ * from that data. Currently this function supports uncompressed
+ * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally).
+ *
+ * This function uses loadWAVFromStream() internally.
+ *
+ * @param stream the SeekableReadStream from which to read the WAVE data
+ * @param disposeAfterUse whether to delete the stream after use
+ * @return a new RewindableAudioStream, or NULL, if an error occurred
+ */
+RewindableAudioStream *makeWAVStream(
+ Common::SeekableReadStream *stream,
+ DisposeAfterUse::Flag disposeAfterUse);
+
+} // End of namespace Audio
+
+#endif