diff options
Diffstat (limited to 'sound/decoders')
-rw-r--r-- | sound/decoders/adpcm.cpp | 627 | ||||
-rw-r--r-- | sound/decoders/adpcm.h | 86 | ||||
-rw-r--r-- | sound/decoders/aiff.cpp | 179 | ||||
-rw-r--r-- | sound/decoders/aiff.h | 63 | ||||
-rw-r--r-- | sound/decoders/flac.cpp | 735 | ||||
-rw-r--r-- | sound/decoders/flac.h | 73 | ||||
-rw-r--r-- | sound/decoders/iff_sound.cpp | 128 | ||||
-rw-r--r-- | sound/decoders/iff_sound.h | 47 | ||||
-rw-r--r-- | sound/decoders/mp3.cpp | 349 | ||||
-rw-r--r-- | sound/decoders/mp3.h | 73 | ||||
-rw-r--r-- | sound/decoders/raw.cpp | 427 | ||||
-rw-r--r-- | sound/decoders/raw.h | 134 | ||||
-rw-r--r-- | sound/decoders/shorten.cpp | 533 | ||||
-rw-r--r-- | sound/decoders/shorten.h | 68 | ||||
-rw-r--r-- | sound/decoders/vag.cpp | 123 | ||||
-rw-r--r-- | sound/decoders/vag.h | 65 | ||||
-rw-r--r-- | sound/decoders/voc.cpp | 375 | ||||
-rw-r--r-- | sound/decoders/voc.h | 107 | ||||
-rw-r--r-- | sound/decoders/vorbis.cpp | 250 | ||||
-rw-r--r-- | sound/decoders/vorbis.h | 73 | ||||
-rw-r--r-- | sound/decoders/wave.cpp | 195 | ||||
-rw-r--r-- | sound/decoders/wave.h | 82 |
22 files changed, 4792 insertions, 0 deletions
diff --git a/sound/decoders/adpcm.cpp b/sound/decoders/adpcm.cpp new file mode 100644 index 0000000000..898780350b --- /dev/null +++ b/sound/decoders/adpcm.cpp @@ -0,0 +1,627 @@ +/* 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 "sound/decoders/adpcm.h" +#include "sound/audiostream.h" + + +namespace Audio { + +class ADPCMInputStream : public RewindableAudioStream { +private: + Common::SeekableReadStream *_stream; + bool _disposeAfterUse; + int32 _startpos; + int32 _endpos; + int _channels; + typesADPCM _type; + uint32 _blockAlign; + uint32 _blockPos[2]; + uint8 _chunkPos; + uint16 _chunkData; + int _blockLen; + int _rate; + + struct ADPCMChannelStatus { + byte predictor; + int16 delta; + int16 coeff1; + int16 coeff2; + int16 sample1; + int16 sample2; + }; + + struct adpcmStatus { + // OKI/IMA + struct { + int32 last; + int32 stepIndex; + } ima_ch[2]; + + // Apple QuickTime IMA ADPCM + int32 streamPos[2]; + + // MS ADPCM + ADPCMChannelStatus ch[2]; + + // Tinsel + double predictor; + double K0, K1; + double d0, d1; + } _status; + + void reset(); + int16 stepAdjust(byte); + int16 decodeOKI(byte); + int16 decodeIMA(byte code, int channel = 0); // Default to using the left channel/using one channel + int16 decodeMS(ADPCMChannelStatus *c, byte); + int16 decodeTinsel(int16, double); + +public: + ADPCMInputStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign); + ~ADPCMInputStream(); + + int readBuffer(int16 *buffer, const int numSamples); + int readBufferOKI(int16 *buffer, const int numSamples); + int readBufferIMA(int16 *buffer, const int numSamples); + int readBufferMSIMA1(int16 *buffer, const int numSamples); + int readBufferMSIMA2(int16 *buffer, const int numSamples); + int readBufferMS(int channels, int16 *buffer, const int numSamples); + void readBufferTinselHeader(); + int readBufferTinsel4(int channels, int16 *buffer, const int numSamples); + int readBufferTinsel6(int channels, int16 *buffer, const int numSamples); + int readBufferTinsel8(int channels, int16 *buffer, const int numSamples); + int readBufferApple(int16 *buffer, const int numSamples); + + bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos); } + bool isStereo() const { return _channels == 2; } + int getRate() const { return _rate; } + + 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>. + +ADPCMInputStream::ADPCMInputStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) + : _stream(stream), _disposeAfterUse(disposeAfterUse), _channels(channels), _type(type), _blockAlign(blockAlign), _rate(rate) { + + if (type == kADPCMMSIma && blockAlign == 0) + error("ADPCMInputStream(): blockAlign isn't specified for MS IMA ADPCM"); + if (type == kADPCMMS && blockAlign == 0) + error("ADPCMInputStream(): blockAlign isn't specified for MS ADPCM"); + + if (type == kADPCMTinsel4 && blockAlign == 0) + error("ADPCMInputStream(): blockAlign isn't specified for Tinsel 4-bit ADPCM"); + if (type == kADPCMTinsel6 && blockAlign == 0) + error("ADPCMInputStream(): blockAlign isn't specified for Tinsel 6-bit ADPCM"); + if (type == kADPCMTinsel8 && blockAlign == 0) + error("ADPCMInputStream(): blockAlign isn't specified for Tinsel 8-bit ADPCM"); + + if (type == kADPCMTinsel4 && channels != 1) + error("ADPCMInputStream(): Tinsel 4-bit ADPCM only supports mono"); + if (type == kADPCMTinsel6 && channels != 1) + error("ADPCMInputStream(): Tinsel 6-bit ADPCM only supports mono"); + if (type == kADPCMTinsel8 && channels != 1) + error("ADPCMInputStream(): Tinsel 8-bit ADPCM only supports mono"); + + _startpos = stream->pos(); + _endpos = _startpos + size; + reset(); +} + +ADPCMInputStream::~ADPCMInputStream() { + if (_disposeAfterUse) + delete _stream; +} + +void ADPCMInputStream::reset() { + memset(&_status, 0, sizeof(_status)); + _blockLen = 0; + _blockPos[0] = _blockPos[1] = _blockAlign; // To make sure first header is read + _status.streamPos[0] = 0; + _status.streamPos[1] = _blockAlign; + _chunkPos = 0; +} + +bool ADPCMInputStream::rewind() { + // TODO: Error checking. + reset(); + _stream->seek(_startpos); + return true; +} + +int ADPCMInputStream::readBuffer(int16 *buffer, const int numSamples) { + int samplesDecoded = 0; + switch (_type) { + case kADPCMOki: + samplesDecoded = readBufferOKI(buffer, numSamples); + break; + case kADPCMMSIma: + if (_channels == 1) + samplesDecoded = readBufferMSIMA1(buffer, numSamples); + else + samplesDecoded = readBufferMSIMA2(buffer, numSamples); + break; + case kADPCMMS: + samplesDecoded = readBufferMS(_channels, buffer, numSamples); + break; + case kADPCMTinsel4: + samplesDecoded = readBufferTinsel4(_channels, buffer, numSamples); + break; + case kADPCMTinsel6: + samplesDecoded = readBufferTinsel6(_channels, buffer, numSamples); + break; + case kADPCMTinsel8: + samplesDecoded = readBufferTinsel8(_channels, buffer, numSamples); + break; + case kADPCMIma: + samplesDecoded = readBufferIMA(buffer, numSamples); + break; + case kADPCMApple: + samplesDecoded = readBufferApple(buffer, numSamples); + break; + default: + error("Unsupported ADPCM encoding"); + break; + } + + return samplesDecoded; +} + +int ADPCMInputStream::readBufferOKI(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; +} + +int ADPCMInputStream::readBufferIMA(int16 *buffer, const int numSamples) { + int samples; + byte data; + + assert(numSamples % 2 == 0); + + for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + buffer[samples] = decodeIMA((data >> 4) & 0x0f); + buffer[samples + 1] = decodeIMA(data & 0x0f, _channels == 2 ? 1 : 0); + } + return samples; +} + +int ADPCMInputStream::readBufferApple(int16 *buffer, const int numSamples) { + // Need to write 2 samples per channel + assert(numSamples % (2 * _channels) == 0); + + // Current sample positions + int samples[2] = { 0, 0}; + // Current data bytes + byte data[2] = { 0, 0}; + // Current nibble selectors + bool lowNibble[2] = {true, true}; + + // Number of samples per channel + int chanSamples = numSamples / _channels; + + for (int i = 0; i < _channels; i++) { + _stream->seek(_status.streamPos[i]); + + while ((samples[i] < chanSamples) && + // Last byte read and a new one needed + !((_stream->eos() || (_stream->pos() >= _endpos)) && lowNibble[i])) { + + 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; + } + + // First decode the lower nibble, then the upper + if (lowNibble[i]) + data[i] = _stream->readByte(); + + int16 sample; + if (lowNibble[i]) + sample = decodeIMA(data[i] & 0x0F, i); + else + sample = decodeIMA(data[i] >> 4, i); + + // The original is interleaved block-wise, we want it sample-wise + buffer[_channels * samples[i] + i] = sample; + + samples[i]++; + + // Different nibble + lowNibble[i] = !lowNibble[i]; + + // We're about to decode a new lower nibble again, so advance the block position + if (lowNibble[i]) + _blockPos[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())); + + _status.streamPos[i] = _stream->pos(); + } + } + + return samples[0] + samples[1]; +} + +int ADPCMInputStream::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(data & 0x0f); + buffer[samples + 1] = decodeIMA((data >> 4) & 0x0f); + } + } + return samples; +} + + +// Microsoft as usual tries to implement it differently. This method +// is used for stereo data. +int ADPCMInputStream::readBufferMSIMA2(int16 *buffer, const int numSamples) { + int samples; + uint32 data; + int nibble; + byte k; + + 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; +} + +static const int MSADPCMAdaptCoeff1[] = { + 256, 512, 0, 192, 240, 460, 392 +}; + +static const int MSADPCMAdaptCoeff2[] = { + 0, -256, 0, 64, 0, -208, -232 +}; + +int ADPCMInputStream::readBufferMS(int channels, int16 *buffer, const int numSamples) { + int samples; + byte data; + int 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; +} + +static const double TinselFilterTable[4][2] = { + {0, 0 }, + {0.9375, 0}, + {1.796875, -0.8125}, + {1.53125, -0.859375} +}; + +void ADPCMInputStream::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]; +} + +int ADPCMInputStream::readBufferTinsel4(int channels, 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; +} + +int ADPCMInputStream::readBufferTinsel6(int channels, 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; +} + +int ADPCMInputStream::readBufferTinsel8(int channels, 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; +} + +static const int MSADPCMAdaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 +}; + + +int16 ADPCMInputStream::decodeMS(ADPCMChannelStatus *c, byte code) { + int32 predictor; + + predictor = (((c->sample1) * (c->coeff1)) + ((c->sample2) * (c->coeff2))) / 256; + predictor += (signed)((code & 0x08) ? (code - 0x10) : (code)) * c->delta; + + 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; +} + +// adjust the step for use on the next sample. +int16 ADPCMInputStream::stepAdjust(byte code) { + static const int16 adjusts[] = {-1, -1, -1, -1, 2, 4, 6, 8}; + + return adjusts[code & 0x07]; +} + +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 ADPCMInputStream::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; +} + +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 ADPCMInputStream::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; +} + +int16 ADPCMInputStream::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); +} + +RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) { + return new ADPCMInputStream(stream, disposeAfterUse, size, type, rate, channels, blockAlign); +} + +} // End of namespace Audio diff --git a/sound/decoders/adpcm.h b/sound/decoders/adpcm.h new file mode 100644 index 0000000000..46ccb582c3 --- /dev/null +++ b/sound/decoders/adpcm.h @@ -0,0 +1,86 @@ +/* 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 + * - 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 + 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 +}; + +/** + * 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 occured + */ +RewindableAudioStream *makeADPCMStream( + Common::SeekableReadStream *stream, + bool disposeAfterUse, + uint32 size, typesADPCM type, + int rate = 22050, + int channels = 2, + uint32 blockAlign = 0); + +} // End of namespace Audio + +#endif diff --git a/sound/decoders/aiff.cpp b/sound/decoders/aiff.cpp new file mode 100644 index 0000000000..e474e999d0 --- /dev/null +++ b/sound/decoders/aiff.cpp @@ -0,0 +1,179 @@ +/* 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 "sound/decoders/aiff.h" +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "sound/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) { + int size, rate; + byte *data, flags; + + if (!loadAIFFFromStream(stream, size, rate, flags)) + return 0; + + data = (byte *)malloc(size); + assert(data); + stream.read(data, size); + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return makeRawMemoryStream(data, size, rate, flags); +} + +} // End of namespace Audio diff --git a/sound/decoders/aiff.h b/sound/decoders/aiff.h new file mode 100644 index 0000000000..e8a3b6f0b0 --- /dev/null +++ b/sound/decoders/aiff.h @@ -0,0 +1,63 @@ +/* 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 + * - sword1 + */ + +#ifndef SOUND_AIFF_H +#define SOUND_AIFF_H + +#include "common/scummsys.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. + */ +SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream &stream); + +} // End of namespace Audio + +#endif diff --git a/sound/decoders/flac.cpp b/sound/decoders/flac.cpp new file mode 100644 index 0000000000..29302827da --- /dev/null +++ b/sound/decoders/flac.cpp @@ -0,0 +1,735 @@ +/* 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 "sound/decoders/flac.h" + +#ifdef USE_FLAC + +#include "common/debug.h" +#include "common/stream.h" +#include "common/util.h" + +#include "sound/audiostream.h" +#include "sound/audiocd.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 FlacInputStream : 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: + FlacInputStream(Common::SeekableReadStream *inStream, bool dispose); + virtual ~FlacInputStream(); + + 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); +}; + +FlacInputStream::FlacInputStream(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(&FlacInputStream::convertBuffersGeneric) +{ + assert(_inStream); + memset(&_streaminfo, 0, sizeof(_streaminfo)); + + _sampleCache.bufReadPos = NULL; + _sampleCache.bufFill = 0; + + _methodConvertBuffers = &FlacInputStream::convertBuffersGeneric; + + bool success; +#ifdef LEGACY_FLAC + ::FLAC__seekable_stream_decoder_set_read_callback(_decoder, &FlacInputStream::callWrapRead); + ::FLAC__seekable_stream_decoder_set_seek_callback(_decoder, &FlacInputStream::callWrapSeek); + ::FLAC__seekable_stream_decoder_set_tell_callback(_decoder, &FlacInputStream::callWrapTell); + ::FLAC__seekable_stream_decoder_set_length_callback(_decoder, &FlacInputStream::callWrapLength); + ::FLAC__seekable_stream_decoder_set_eof_callback(_decoder, &FlacInputStream::callWrapEOF); + ::FLAC__seekable_stream_decoder_set_write_callback(_decoder, &FlacInputStream::callWrapWrite); + ::FLAC__seekable_stream_decoder_set_metadata_callback(_decoder, &FlacInputStream::callWrapMetadata); + ::FLAC__seekable_stream_decoder_set_error_callback(_decoder, &FlacInputStream::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, + &FlacInputStream::callWrapRead, + &FlacInputStream::callWrapSeek, + &FlacInputStream::callWrapTell, + &FlacInputStream::callWrapLength, + &FlacInputStream::callWrapEOF, + &FlacInputStream::callWrapWrite, + &FlacInputStream::callWrapMetadata, + &FlacInputStream::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 occured + } + } + + warning("FlacInputStream: could not create audio stream"); +} + +FlacInputStream::~FlacInputStream() { + 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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::seek(const Timestamp &where) { + _sampleCache.bufFill = 0; + _sampleCache.bufReadPos = NULL; + return seekAbsolute((FLAC__uint64)calculateSampleOffset(where, _streaminfo.sample_rate)); +} + +int FlacInputStream::readBuffer(int16 *buffer, const int numSamples) { + const uint numChannels = getChannels(); + + if (numChannels == 0) { + warning("FlacInputStream: Stream not sucessfully 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("FlacInputStream: An error occured 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 FlacInputStream::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 FlacInputStream::setBestConvertBufferMethod() { + PFCONVERTBUFFERS tempMethod = &FlacInputStream::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 = &FlacInputStream::convertBuffersMono8Bit; + if (numBits == BUFTYPE_BITS) + tempMethod = &FlacInputStream::convertBuffersMonoNS; + } else if (numChannels == 2) { + if (numBits == 8) + tempMethod = &FlacInputStream::convertBuffersStereo8Bit; + if (numBits == BUFTYPE_BITS) + tempMethod = &FlacInputStream::convertBuffersStereoNS; + } /* else ... */ + + _methodConvertBuffers = tempMethod; +} + +// 1 channel, no scaling +void FlacInputStream::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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::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 FlacInputStream::callbackEOF() { + return _inStream->eos(); +} + + +inline void FlacInputStream::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 FlacInputStream::callbackError(::FLAC__StreamDecoderErrorStatus status) { + // some of these are non-critical-Errors + debug(1, "FlacInputStream: An error occured while decoding. DecoderState is: %s", + FLAC__StreamDecoderErrorStatusString[status]); +} + +/* Static Callback Wrappers */ +::FLAC__SeekableStreamDecoderReadStatus FlacInputStream::callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData) { + FlacInputStream *instance = (FlacInputStream *)clientData; + assert(0 != instance); + return instance->callbackRead(buffer, bytes); +} + +::FLAC__SeekableStreamDecoderSeekStatus FlacInputStream::callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData) { + FlacInputStream *instance = (FlacInputStream *)clientData; + assert(0 != instance); + return instance->callbackSeek(absoluteByteOffset); +} + +::FLAC__SeekableStreamDecoderTellStatus FlacInputStream::callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData) { + FlacInputStream *instance = (FlacInputStream *)clientData; + assert(0 != instance); + return instance->callbackTell(absoluteByteOffset); +} + +::FLAC__SeekableStreamDecoderLengthStatus FlacInputStream::callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData) { + FlacInputStream *instance = (FlacInputStream *)clientData; + assert(0 != instance); + return instance->callbackLength(streamLength); +} + +FLAC__bool FlacInputStream::callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData) { + FlacInputStream *instance = (FlacInputStream *)clientData; + assert(0 != instance); + return instance->callbackEOF(); +} + +::FLAC__StreamDecoderWriteStatus FlacInputStream::callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData) { + FlacInputStream *instance = (FlacInputStream *)clientData; + assert(0 != instance); + return instance->callbackWrite(frame, buffer); +} + +void FlacInputStream::callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData) { + FlacInputStream *instance = (FlacInputStream *)clientData; + assert(0 != instance); + instance->callbackMetadata(metadata); +} + +void FlacInputStream::callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData) { + FlacInputStream *instance = (FlacInputStream *)clientData; + assert(0 != instance); + instance->callbackError(status); +} + + +#pragma mark - +#pragma mark --- Flac factory functions --- +#pragma mark - + +SeekableAudioStream *makeFlacStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + return new FlacInputStream(stream, disposeAfterUse); +} + +} // End of namespace Audio + +#endif // #ifdef USE_FLAC diff --git a/sound/decoders/flac.h b/sound/decoders/flac.h new file mode 100644 index 0000000000..e35b608253 --- /dev/null +++ b/sound/decoders/flac.h @@ -0,0 +1,73 @@ +/* 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 + * - kyra + * - m4 + * - queen + * - saga + * - scumm + * - sword1 + * - sword2 + * - touche + * - tucker + */ + +#ifndef SOUND_FLAC_H +#define SOUND_FLAC_H + +#include "common/types.h" +#include "common/scummsys.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 occured + */ +SeekableAudioStream *makeFlacStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif // #ifdef USE_FLAC +#endif // #ifndef SOUND_FLAC_H diff --git a/sound/decoders/iff_sound.cpp b/sound/decoders/iff_sound.cpp new file mode 100644 index 0000000000..f394b55ef0 --- /dev/null +++ b/sound/decoders/iff_sound.cpp @@ -0,0 +1,128 @@ +/* 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 "sound/decoders/iff_sound.h" +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "sound/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::makeRawMemoryStream((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 = loader._header.oneShotHiSamples; + loopEnd = loader._header.oneShotHiSamples + loader._header.repeatHiSamples; + + return new SubLoopingAudioStream(stream, 0, + Timestamp(0, loopStart, loader._header.samplesPerSec), + Timestamp(0, loopEnd, loader._header.samplesPerSec)); + } + + return stream; +} + +} diff --git a/sound/decoders/iff_sound.h b/sound/decoders/iff_sound.h new file mode 100644 index 0000000000..4e53059380 --- /dev/null +++ b/sound/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/sound/decoders/mp3.cpp b/sound/decoders/mp3.cpp new file mode 100644 index 0000000000..378f77dac0 --- /dev/null +++ b/sound/decoders/mp3.cpp @@ -0,0 +1,349 @@ +/* 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 "sound/decoders/mp3.h" + +#ifdef USE_MAD + +#include "common/debug.h" +#include "common/stream.h" +#include "common/util.h" + +#include "sound/audiocd.h" +#include "sound/audiostream.h" + +#include <mad.h> + + +namespace Audio { + + +#pragma mark - +#pragma mark --- MP3 (MAD) stream --- +#pragma mark - + + +class MP3InputStream : 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: + MP3InputStream(Common::SeekableReadStream *inStream, + DisposeAfterUse::Flag dispose); + ~MP3InputStream(); + + 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(); +}; + +MP3InputStream::MP3InputStream(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(); + + _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(); +} + +MP3InputStream::~MP3InputStream() { + deinitStream(); + + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _inStream; +} + +void MP3InputStream::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) { + // TODO: Do we need to use readHeader, when we do not do any seeking here? + readHeader(); + + // 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, "MP3InputStream: Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream)); + continue; + } else { + warning("MP3InputStream: 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 MP3InputStream::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 MP3InputStream::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(); + + return (_state != MP3_STATE_EOS); +} + +void MP3InputStream::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 MP3InputStream::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, "MP3InputStream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream)); + continue; + } else { + warning("MP3InputStream: 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 MP3InputStream::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 MP3InputStream::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) { + return new MP3InputStream(stream, disposeAfterUse); +} + +} // End of namespace Audio + +#endif // #ifdef USE_MAD diff --git a/sound/decoders/mp3.h b/sound/decoders/mp3.h new file mode 100644 index 0000000000..3175df5e92 --- /dev/null +++ b/sound/decoders/mp3.h @@ -0,0 +1,73 @@ +/* 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 + * - kyra + * - m4 + * - queen + * - saga + * - scumm + * - sword1 + * - sword2 + * - touche + * - tucker + */ + +#ifndef SOUND_MP3_H +#define SOUND_MP3_H + +#include "common/types.h" +#include "common/scummsys.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 occured + */ +SeekableAudioStream *makeMP3Stream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif // #ifdef USE_MAD +#endif // #ifndef SOUND_MP3_H diff --git a/sound/decoders/raw.cpp b/sound/decoders/raw.cpp new file mode 100644 index 0000000000..6747111a1a --- /dev/null +++ b/sound/decoders/raw.cpp @@ -0,0 +1,427 @@ +/* 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/stream.h" + +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "sound/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)) + + +// TODO: Get rid of this +uint32 calculateSampleOffset(const Timestamp &where, int rate) { + return where.convertToFramerate(rate).totalNumberOfFrames(); +} + + + +#pragma mark - +#pragma mark --- RawMemoryStream --- +#pragma mark - + +/** + * A simple raw audio stream, purely memory based. It operates on a single + * block of data, which is passed to it upon creation. + * Optionally supports looping the sound. + * + * Design note: This code tries to be as efficient as possible (without + * resorting to assembly, that is). To this end, it is written as a template + * class. This way the compiler can create optimized code for each special + * case. This results in a total of 12 versions of the code being generated. + */ +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +class RawMemoryStream : public SeekableAudioStream { +protected: + const byte *_ptr; + const byte *_end; + const int _rate; + const byte *_origPtr; + const DisposeAfterUse::Flag _disposeAfterUse; + const Timestamp _playtime; + +public: + RawMemoryStream(int rate, const byte *ptr, uint len, DisposeAfterUse::Flag autoFreeMemory) + : _ptr(ptr), _end(ptr+len), _rate(rate), _origPtr(ptr), + _disposeAfterUse(autoFreeMemory), + _playtime(0, len / (is16Bit ? 2 : 1) / (stereo ? 2 : 1), rate) { + } + + virtual ~RawMemoryStream() { + if (_disposeAfterUse == DisposeAfterUse::YES) + free(const_cast<byte *>(_origPtr)); + } + + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return stereo; } + bool endOfData() const { return _ptr >= _end; } + + int getRate() const { return _rate; } + bool seek(const Timestamp &where); + Timestamp getLength() const { return _playtime; } +}; + +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +int RawMemoryStream<stereo, is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) { + int samples = numSamples; + while (samples > 0 && _ptr < _end) { + int len = MIN(samples, (int)(_end - _ptr) / (is16Bit ? 2 : 1)); + samples -= len; + do { + *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, _ptr, isLE); + _ptr += (is16Bit ? 2 : 1); + } while (--len); + } + return numSamples-samples; +} + +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +bool RawMemoryStream<stereo, is16Bit, isUnsigned, isLE>::seek(const Timestamp &where) { + const uint8 *ptr = _origPtr + calculateSampleOffset(where, getRate()) * (is16Bit ? 2 : 1) * (stereo ? 2 : 1); + if (ptr > _end) { + _ptr = _end; + return false; + } else if (ptr == _end) { + _ptr = _end; + return true; + } else { + _ptr = ptr; + return true; + } +} + +#pragma mark - +#pragma mark --- RawDiskStream --- +#pragma mark - + + + +/** + * RawDiskStream. This can stream raw PCM audio data from disk. The + * function takes an pointer to an array of RawDiskStreamAudioBlock which defines the + * start position and length of each block of uncompressed audio in the stream. + */ +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +class RawDiskStream : public SeekableAudioStream { + +// Allow backends to override buffer size +#ifdef CUSTOM_AUDIO_BUFFER_SIZE + static const int32 BUFFER_SIZE = CUSTOM_AUDIO_BUFFER_SIZE; +#else + static const int32 BUFFER_SIZE = 16384; +#endif + +protected: + byte* _buffer; ///< Streaming buffer + const byte *_ptr; ///< Pointer to current position in stream buffer + const int _rate; ///< Sample rate of stream + + Timestamp _playtime; ///< Calculated total play time + Common::SeekableReadStream *_stream; ///< Stream to read data from + int32 _filePos; ///< Current position in stream + int32 _diskLeft; ///< Samples left in stream in current block not yet read to buffer + int32 _bufferLeft; ///< Samples left in buffer in current block + const DisposeAfterUse::Flag _disposeAfterUse; ///< Indicates whether the stream object should be deleted when this RawDiskStream is destructed + + RawDiskStreamAudioBlock *_audioBlock; ///< Audio block list + const int _audioBlockCount; ///< Number of blocks in _audioBlock + int _currentBlock; ///< Current audio block number +public: + RawDiskStream(int rate, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream, RawDiskStreamAudioBlock *block, uint numBlocks) + : _rate(rate), _playtime(0, rate), _stream(stream), _disposeAfterUse(disposeStream), + _audioBlockCount(numBlocks) { + + assert(numBlocks > 0); + + // Allocate streaming buffer + if (is16Bit) { + _buffer = (byte *)malloc(BUFFER_SIZE * sizeof(int16)); + } else { + _buffer = (byte *)malloc(BUFFER_SIZE * sizeof(byte)); + } + + _ptr = _buffer; + _bufferLeft = 0; + + // Copy audio block data to our buffer + // TODO: Replace this with a Common::Array or Common::List to + // make it a little friendlier. + _audioBlock = new RawDiskStreamAudioBlock[numBlocks]; + memcpy(_audioBlock, block, numBlocks * sizeof(RawDiskStreamAudioBlock)); + + // Set current buffer state, playing first block + _currentBlock = 0; + _filePos = _audioBlock[_currentBlock].pos; + _diskLeft = _audioBlock[_currentBlock].len; + + // Add up length of all blocks in order to caluclate total play time + int len = 0; + for (int r = 0; r < _audioBlockCount; r++) { + len += _audioBlock[r].len; + } + _playtime = Timestamp(0, len / (is16Bit ? 2 : 1) / (stereo ? 2 : 1), rate); + } + + + virtual ~RawDiskStream() { + if (_disposeAfterUse == DisposeAfterUse::YES) { + delete _stream; + } + + delete[] _audioBlock; + free(_buffer); + } + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return stereo; } + bool endOfData() const { return (_currentBlock == _audioBlockCount - 1) && (_diskLeft == 0) && (_bufferLeft == 0); } + + int getRate() const { return _rate; } + Timestamp getLength() const { return _playtime; } + + bool seek(const Timestamp &where); +}; + +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +int RawDiskStream<stereo, is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) { + int oldPos = _stream->pos(); + bool restoreFilePosition = false; + + int samples = numSamples; + + while (samples > 0 && ((_diskLeft > 0 || _bufferLeft > 0) || (_currentBlock != _audioBlockCount - 1)) ) { + // Output samples in the buffer to the output + int len = MIN<int>(samples, _bufferLeft); + samples -= len; + _bufferLeft -= len; + + while (len > 0) { + *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, _ptr, isLE); + _ptr += (is16Bit ? 2 : 1); + len--; + } + + // Have we now finished this block? If so, read the next block + if ((_bufferLeft == 0) && (_diskLeft == 0) && (_currentBlock != _audioBlockCount - 1)) { + // Next block + _currentBlock++; + + _filePos = _audioBlock[_currentBlock].pos; + _diskLeft = _audioBlock[_currentBlock].len; + } + + // Now read more data from disk if there is more to be read + if ((_bufferLeft == 0) && (_diskLeft > 0)) { + int32 readAmount = MIN(_diskLeft, BUFFER_SIZE); + + _stream->seek(_filePos, SEEK_SET); + _stream->read(_buffer, readAmount * (is16Bit? 2: 1)); + + // Amount of data in buffer is now the amount read in, and + // the amount left to read on disk is decreased by the same amount + _bufferLeft = readAmount; + _diskLeft -= readAmount; + _ptr = (byte *)_buffer; + _filePos += readAmount * (is16Bit ? 2 : 1); + + // Set this flag now we've used the file, it restores it's + // original position. + restoreFilePosition = true; + } + } + + // In case calling code relies on the position of this stream staying + // constant, I restore the location if I've changed it. This is probably + // not necessary. + if (restoreFilePosition) { + _stream->seek(oldPos, SEEK_SET); + } + + return numSamples - samples; +} + +template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE> +bool RawDiskStream<stereo, is16Bit, isUnsigned, isLE>::seek(const Timestamp &where) { + const uint32 seekSample = calculateSampleOffset(where, getRate()) * (stereo ? 2 : 1); + uint32 curSample = 0; + + // Search for the disk block in which the specific sample is placed + _currentBlock = 0; + while (_currentBlock < _audioBlockCount) { + uint32 nextBlockSample = curSample + _audioBlock[_currentBlock].len; + + if (nextBlockSample > seekSample) + break; + + curSample = nextBlockSample; + ++_currentBlock; + } + + _filePos = 0; + _diskLeft = 0; + _bufferLeft = 0; + + if (_currentBlock == _audioBlockCount) { + return ((seekSample - curSample) == (uint32)_audioBlock[_currentBlock - 1].len); + } else { + const uint32 offset = seekSample - curSample; + + _filePos = _audioBlock[_currentBlock].pos + offset * (is16Bit ? 2 : 1); + _diskLeft = _audioBlock[_currentBlock].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_LINEAR(STEREO, UNSIGNED) \ + if (is16Bit) { \ + if (isLE) \ + return new RawMemoryStream<STEREO, true, UNSIGNED, true>(rate, ptr, len, autoFree); \ + else \ + return new RawMemoryStream<STEREO, true, UNSIGNED, false>(rate, ptr, len, autoFree); \ + } else \ + return new RawMemoryStream<STEREO, false, UNSIGNED, false>(rate, ptr, len, autoFree) + +SeekableAudioStream *makeRawMemoryStream(const byte *ptr, uint32 len, + int rate, byte flags, + DisposeAfterUse::Flag autoFree + ) { + 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; + + // Verify the buffer sizes are sane + if (is16Bit && isStereo) { + assert((len & 3) == 0); + } else if (is16Bit || isStereo) { + assert((len & 1) == 0); + } + + if (isStereo) { + if (isUnsigned) { + MAKE_LINEAR(true, true); + } else { + MAKE_LINEAR(true, false); + } + } else { + if (isUnsigned) { + MAKE_LINEAR(false, true); + } else { + MAKE_LINEAR(false, false); + } + } +} + + +AudioStream *makeRawMemoryStream_OLD(const byte *ptr, uint32 len, + int rate, byte flags, + uint loopStart, uint loopEnd, + DisposeAfterUse::Flag autoFree + ) { + SeekableAudioStream *s = makeRawMemoryStream(ptr, len, rate, flags, autoFree); + + if (loopStart != loopEnd) { + const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; + const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; + + if (loopEnd == 0) + loopEnd = len; + assert(loopStart <= loopEnd); + assert(loopEnd <= len); + + // 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; + } +} + + + +#define MAKE_LINEAR_DISK(STEREO, UNSIGNED) \ + if (is16Bit) { \ + if (isLE) \ + return new RawDiskStream<STEREO, true, UNSIGNED, true>(rate, disposeStream, stream, block, numBlocks); \ + else \ + return new RawDiskStream<STEREO, true, UNSIGNED, false>(rate, disposeStream, stream, block, numBlocks); \ + } else \ + return new RawDiskStream<STEREO, false, UNSIGNED, false>(rate, disposeStream, stream, block, numBlocks) + + +SeekableAudioStream *makeRawDiskStream(Common::SeekableReadStream *stream, RawDiskStreamAudioBlock *block, int numBlocks, + int rate, byte flags, DisposeAfterUse::Flag disposeStream) { + 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 (isStereo) { + if (isUnsigned) { + MAKE_LINEAR_DISK(true, true); + } else { + MAKE_LINEAR_DISK(true, false); + } + } else { + if (isUnsigned) { + MAKE_LINEAR_DISK(false, true); + } else { + MAKE_LINEAR_DISK(false, false); + } + } +} + +} // End of namespace Audio diff --git a/sound/decoders/raw.h b/sound/decoders/raw.h new file mode 100644 index 0000000000..16ab4ec350 --- /dev/null +++ b/sound/decoders/raw.h @@ -0,0 +1,134 @@ +/* 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" + + +namespace Common { class SeekableReadStream; } + + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + +/** + * Various flags which can be bit-ORed and then passed to + * makeRawMemoryStream 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 +}; + + +/** + * Creates a audio stream, which plays the given raw data. + * + * The data pointer is assumed to have been allocated with malloc(). + * In particular, if autofreeBuffer is set to DisposeAfterUse::YES, + * then this buffer will be deallocated using free(). So do not + * use a buffer allocated with new[]! + * + * @param ptr pointer to a buffer containing audio data + * @param len length of the buffer in bytes + * @param rate sample rate of the data + * @param flags audio format flags combination + * @see Mixer::RawFlags + * @param autofreeBuffer whether the data buffer should be destroyed after playback + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawMemoryStream(const byte *ptr, uint32 len, + int rate, byte flags, + DisposeAfterUse::Flag autofreeBuffer = DisposeAfterUse::YES + ); + +/** + * NOTE: + * This API is considered deprecated. + * + * Factory function for a raw PCM AudioStream, which will simply treat all + * data in the buffer described by ptr and len as raw sample data in the + * specified format. It will then simply pass this data directly to the mixer, + * after converting it to the sample format used by the mixer (i.e. 16 bit + * signed native endian). Optionally supports (infinite) looping of a portion + * of the data. + */ +AudioStream *makeRawMemoryStream_OLD(const byte *ptr, uint32 len, + int rate, byte flags, + uint loopStart, uint loopEnd, + DisposeAfterUse::Flag autofreeBuffer = DisposeAfterUse::YES + ); + + +/** + * Struct used to define the audio data to be played by a RawDiskStream. + */ +struct RawDiskStreamAudioBlock { + int32 pos; ///< Position in stream of the block + int32 len; ///< Length of the block (in samples) +}; + +/** + * Creates a audio stream, which plays from given stream. + * + * @param stream Stream to play from + * @param block Pointer to an RawDiskStreamAudioBlock array + * @see RawDiskStreamAudioBlock + * @param numBlocks Number of blocks. + * @param rate The rate + * @param len Length of the data (in bytes!) + * @param flags Flags combination. + * @see Mixer::RawFlags + * @param disposeStream Whether the "stream" object should be destroyed after playback. + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawDiskStream(Common::SeekableReadStream *stream, + RawDiskStreamAudioBlock *block, int numBlocks, + int rate, byte flags, + DisposeAfterUse::Flag disposeStream); + + +} // End of namespace Audio + +#endif diff --git a/sound/decoders/shorten.cpp b/sound/decoders/shorten.cpp new file mode 100644 index 0000000000..fc97f71b56 --- /dev/null +++ b/sound/decoders/shorten.cpp @@ -0,0 +1,533 @@ +/* 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 "sound/decoders/shorten.h" + +#ifdef SOUND_SHORTEN_H + +// Based on etree's Shorten tool, version 3.6.1 +// http://etree.org/shnutils/shorten/ + +// FIXME: This doesn't work yet correctly + +#include "common/endian.h" +#include "common/util.h" +#include "common/stream.h" + +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "sound/decoders/raw.h" + +namespace Audio { + +#define MASKTABSIZE 33 +#define MAX_SUPPORTED_VERSION 3 +#define DEFAULT_BLOCK_SIZE 256 + +enum kShortenTypes { + kTypeAU1 = 0, // lossless ulaw + kTypeS8 = 1, // signed 8 bit + kTypeU8 = 2, // unsigned 8 bit + kTypeS16HL = 3, // signed 16 bit shorts: high-low + kTypeU16HL = 4, // unsigned 16 bit shorts: high-low + kTypeS16LH = 5, // signed 16 bit shorts: low-high + kTypeU16LH = 6, // unsigned 16 bit shorts: low-high + kTypeULaw = 7, // lossy ulaw + kTypeAU2 = 8, // new ulaw with zero mapping + kTypeAU3 = 9, // lossless alaw + kTypeALaw = 10, // lossy alaw + kTypeWAV = 11, // WAV + kTypeAIFF = 12, // AIFF + kTypeEOF = 13, + kTypeGenericULaw = 128, + kTypeGenericALaw = 129 +}; + +enum kShortenCommands { + kCmdDiff0 = 0, + kCmdDiff1 = 1, + kCmdDiff2 = 2, + kCmdDiff3 = 3, + kCmdQuit = 4, + kCmdBlockSize = 5, + kCmdBitShift = 6, + kCmdQLPC = 7, + kCmdZero = 8, + kCmdVerbatim = 9 +}; + +#ifndef M_LN2 +#define M_LN2 0.69314718055994530942 +#endif + +// --------------------------------------------------------------------------- + +class ShortenGolombReader { +public: + ShortenGolombReader(Common::ReadStream *stream, int version); + ~ShortenGolombReader() {} + uint32 getUint32(uint32 numBits); // UINT_GET + int32 getURice(uint32 numBits); // uvar_get + int32 getSRice(uint32 numBits); // var_get +private: + int _version; + uint32 _nbitget; + uint32 _buf; + uint32 _masktab[MASKTABSIZE]; + Common::ReadStream *_stream; +}; + +ShortenGolombReader::ShortenGolombReader(Common::ReadStream *stream, int version) { + _stream = stream; + _version = version; + uint32 val = 0; + _masktab[0] = 0; + _nbitget = 0; + _buf = 0; + + for (int i = 1; i < MASKTABSIZE; i++) { + val <<= 1; + val |= 1; + _masktab[i] = val; + } +} + +int32 ShortenGolombReader::getURice(uint32 numBits) { + int32 result = 0; + + if (!_nbitget) { + _buf = _stream->readUint32BE(); + _nbitget = 32; + } + + for (result = 0; !(_buf & (1L << --_nbitget)); result++) { + if (!_nbitget) { + _buf = _stream->readUint32BE(); + _nbitget = 32; + } + } + + while (numBits != 0) { + if (_nbitget >= numBits) { + result = (result << numBits) | ((_buf >> (_nbitget - numBits)) & _masktab[numBits]); + _nbitget -= numBits; + numBits = 0; + } else { + result = (result << _nbitget) | (_buf & _masktab[_nbitget]); + _buf = _stream->readUint32BE(); + numBits -= _nbitget; + _nbitget = 32; + } + } + + return result; +} + +int32 ShortenGolombReader::getSRice(uint32 numBits) { + uint32 uvar = (uint32) getURice(numBits + 1); + return (uvar & 1) ? (int32) ~(uvar >> 1) : (int32) (uvar >> 1); +} + +uint32 ShortenGolombReader::getUint32(uint32 numBits) { + return (_version == 0) ? (uint32)getURice(numBits) : (uint32)getURice(getURice(2)); +} + +// --------------------------------------------------------------------------- + +byte *loadShortenFromStream(Common::ReadStream &stream, int &size, int &rate, byte &flags) { + int32 *buffer[2], *offset[2]; // up to 2 channels + byte *unpackedBuffer = 0; + byte *pBuf = unpackedBuffer; + int prevSize = 0; + int32 *lpc = 0; + + ShortenGolombReader *gReader; + uint32 i, j, version, mean, type, channels, blockSize; + uint32 maxLPC = 0, lpcqOffset = 0; + int32 bitShift = 0, wrap = 0; + flags = 0; + size = 0; + + // Read header + byte magic[4]; + stream.read(magic, 4); + if (memcmp(magic, "ajkg", 4) != 0) { + warning("loadShortenFromStream: No 'ajkg' header"); + return NULL; + } + + version = stream.readByte(); + + if (version > MAX_SUPPORTED_VERSION) { + warning("loadShortenFromStream: Can't decode version %d, maximum supported version is %d", version, MAX_SUPPORTED_VERSION); + return NULL; + } + + mean = (version < 2) ? 0 : 4; + + gReader = new ShortenGolombReader(&stream, version); + + // Get file type + type = gReader->getUint32(4); + + switch (type) { + case kTypeS8: + break; + case kTypeU8: + flags |= Audio::FLAG_UNSIGNED; + break; + case kTypeS16LH: + flags |= Audio::FLAG_LITTLE_ENDIAN; + case kTypeS16HL: + flags |= Audio::FLAG_16BITS; + break; + case kTypeU16LH: + flags |= Audio::FLAG_LITTLE_ENDIAN; + case kTypeU16HL: + flags |= Audio::FLAG_16BITS; + flags |= Audio::FLAG_UNSIGNED; + break; + case kTypeWAV: + // TODO: Perhaps implement this if we find WAV Shorten encoded files + warning("loadShortenFromStream: Type WAV is not supported"); + delete gReader; + return NULL; + case kTypeAIFF: + // TODO: Perhaps implement this if we find AIFF Shorten encoded files + warning("loadShortenFromStream: Type AIFF is not supported"); + delete gReader; + return NULL; + case kTypeAU1: + case kTypeAU2: + case kTypeAU3: + case kTypeULaw: + case kTypeALaw: + case kTypeEOF: + case kTypeGenericULaw: + case kTypeGenericALaw: + default: + warning("loadShortenFromStream: Type %d is not supported", type); + delete gReader; + return NULL; + } + + // Get channels + channels = gReader->getUint32(0); + if (channels != 1 && channels != 2) { + warning("loadShortenFromStream: Only 1 or 2 channels are supported, stream contains %d channels", channels); + delete gReader; + return NULL; + } + + // Get block size + if (version > 0) { + blockSize = gReader->getUint32((int) (log((double) DEFAULT_BLOCK_SIZE) / M_LN2)); + maxLPC = gReader->getUint32(2); + mean = gReader->getUint32(0); + uint32 skipBytes = gReader->getUint32(1); + if (skipBytes > 0) { + prevSize = size; + size += skipBytes; + unpackedBuffer = (byte *) realloc(unpackedBuffer, size); + pBuf = unpackedBuffer + prevSize; + for (i = 0; i < skipBytes; i++) { + *pBuf++ = gReader->getUint32(7) & 0xFF; + } + } + } else { + blockSize = DEFAULT_BLOCK_SIZE; + } + + wrap = MAX<uint32>(3, maxLPC); + + // Initialize buffers + for (i = 0; i < channels; i++) { + buffer[i] = (int32 *)malloc((blockSize + wrap) * 4); + offset[i] = (int32 *)malloc((MAX<uint32>(1, mean)) * 4); + memset(buffer[i], 0, (blockSize + wrap) * 4); + memset(offset[i], 0, (MAX<uint32>(1, mean)) * 4); + } + + if (maxLPC > 0) + lpc = (int32 *) malloc(maxLPC * 4); + + if (version > 1) + lpcqOffset = 1 << 5; + + // Init offset + int32 offsetMean = 0; + uint32 blocks = MAX<int>(1, mean); + + if (type == kTypeU8) + offsetMean = 0x80; + else if (type == kTypeU16HL || type == kTypeU16LH) + offsetMean = 0x8000; + + for (uint32 channel = 0; channel < channels; channel++) + for (uint32 block = 0; block < blocks; block++) + offset[channel][block] = offsetMean; + + + uint32 curChannel = 0, cmd = 0; + + // Parse Shorten commands + while (true) { + cmd = gReader->getURice(2); + + if (cmd == kCmdQuit) + break; + + switch (cmd) { + case kCmdZero: + case kCmdDiff0: + case kCmdDiff1: + case kCmdDiff2: + case kCmdDiff3: + case kCmdQLPC: + { + int32 channelOffset = 0, energy = 0; + uint32 lpcNum = 0; + + if (cmd != kCmdZero) { + energy = gReader->getURice(3); + // hack for version 0 + if (version == 0) + energy--; + } + + // Find mean offset (code duplicated below) + if (mean == 0) { + channelOffset = offset[curChannel][0]; + } else { + int32 sum = (version < 2) ? 0 : mean / 2; + for (i = 0; i < mean; i++) + sum += offset[curChannel][i]; + + channelOffset = sum / mean; + + if (version >= 2 && bitShift > 0) + channelOffset = (channelOffset >> (bitShift - 1)) >> 1; + } + + // FIXME: The original code in this bit tries to modify memory outside of the array (negative indices) + // in cases kCmdDiff1, kCmdDiff2 and kCmdDiff3 + // I've removed those invalid writes, since they happen all the time (even when curChannel is 0) + switch (cmd) { + case kCmdZero: + for (i = 0; i < blockSize; i++) + buffer[curChannel][i] = 0; + break; + case kCmdDiff0: + for (i = 0; i < blockSize; i++) + buffer[curChannel][i] = gReader->getSRice(energy) + channelOffset; + break; + case kCmdDiff1: + gReader->getSRice(energy); // i = 0 (to fix invalid table/memory access) + for (i = 1; i < blockSize; i++) + buffer[curChannel][i] = gReader->getSRice(energy) + buffer[curChannel][i - 1]; + break; + case kCmdDiff2: + gReader->getSRice(energy); // i = 0 (to fix invalid table/memory access) + gReader->getSRice(energy); // i = 1 (to fix invalid table/memory access) + for (i = 2; i < blockSize; i++) + buffer[curChannel][i] = gReader->getSRice(energy) + 2 * buffer[curChannel][i - 1] - buffer[curChannel][i - 2]; + break; + case kCmdDiff3: + gReader->getSRice(energy); // i = 0 (to fix invalid table/memory access) + gReader->getSRice(energy); // i = 1 (to fix invalid table/memory access) + gReader->getSRice(energy); // i = 2 (to fix invalid table/memory access) + for (i = 3; i < blockSize; i++) + buffer[curChannel][i] = gReader->getSRice(energy) + 3 * (buffer[curChannel][i - 1] - buffer[curChannel][i - 2]) + buffer[curChannel][i - 3]; + break; + case kCmdQLPC: + lpcNum = gReader->getURice(2); + + // Safeguard: if maxLPC < lpcNum, realloc the lpc buffer + if (maxLPC < lpcNum) { + warning("Safeguard: maxLPC < lpcNum (should never happen)"); + maxLPC = lpcNum; + lpc = (int32 *) realloc(lpc, maxLPC * 4); + } + + for (i = 0; i < lpcNum; i++) + lpc[i] = gReader->getSRice(5); + + for (i = 0; i < lpcNum; i++) + buffer[curChannel][i - lpcNum] -= channelOffset; + + for (i = 0; i < blockSize; i++) { + int32 sum = lpcqOffset; + for (j = 0; j < lpcNum; j++) { + // FIXME: The original code did an invalid memory access here + // (if i and j are 0, the array index requested is -1) + // I've removed those invalid writes, since they happen all the time (even when curChannel is 0) + if (i <= j) // ignore invalid table/memory access + continue; + sum += lpc[j] * buffer[curChannel][i - j - 1]; + } + buffer[curChannel][i] = gReader->getSRice(energy) + (sum >> 5); + } + + if (channelOffset > 0) + for (i = 0; i < blockSize; i++) + buffer[curChannel][i] += channelOffset; + + break; + } + + // Store mean value, if appropriate (duplicated code from above) + if (mean > 0) { + int32 sum = (version < 2) ? 0 : blockSize / 2; + for (i = 0; i < blockSize; i++) + sum += buffer[curChannel][i]; + + for (i = 1; i < mean; i++) + offset[curChannel][i - 1] = offset[curChannel][i]; + + offset[curChannel][mean - 1] = sum / blockSize; + + if (version >= 2 && bitShift > 0) + offset[curChannel][mean - 1] = offset[curChannel][mean - 1] << bitShift; + } + + + // Do the wrap + // FIXME: removed for now, as this corrupts the heap, because it + // accesses negative array indices + //for (int32 k = -wrap; k < 0; k++) + // buffer[curChannel][k] = buffer[curChannel][k + blockSize]; + + // Fix bitshift + if (bitShift > 0) { + for (i = 0; i < blockSize; i++) + buffer[curChannel][i] <<= bitShift; + } + + if (curChannel == channels - 1) { + int dataSize = (flags & Audio::FLAG_16BITS) ? 2 : 1; + int limit = (flags & Audio::FLAG_16BITS) ? 32767 : 127; + limit = (flags & Audio::FLAG_UNSIGNED) ? limit * 2 + 1 : limit; + + prevSize = size; + size += (blockSize * dataSize); + unpackedBuffer = (byte *) realloc(unpackedBuffer, size); + pBuf = unpackedBuffer + prevSize; + + if (flags & Audio::FLAG_16BITS) { + for (i = 0; i < blockSize; i++) { + for (j = 0; j < channels; j++) { + int16 val = (int16)(MIN<int32>(buffer[j][i], limit) & 0xFFFF); + // values are written in LE + *pBuf++ = (byte) (val & 0xFF); + *pBuf++ = (byte) ((val >> 8) & 0xFF); + } + } + } else { + for (i = 0; i < blockSize; i++) + for (j = 0; j < channels; j++) + *pBuf++ = (byte)(MIN<int32>(buffer[j][i], limit) & 0xFF); + } + } + curChannel = (curChannel + 1) % channels; + + } + break; + case kCmdBlockSize: + blockSize = gReader->getUint32((uint32)log((double) blockSize / M_LN2)); + break; + case kCmdBitShift: + bitShift = gReader->getURice(2); + break; + case kCmdVerbatim: + { + + uint32 vLen = (uint32)gReader->getURice(5); + prevSize = size; + size += vLen; + unpackedBuffer = (byte *) realloc(unpackedBuffer, size); + pBuf = unpackedBuffer + prevSize; + + while (vLen--) { + *pBuf++ = (byte)(gReader->getURice(8) & 0xFF); + } + + } + break; + default: + warning("loadShortenFromStream: Unknown command: %d", cmd); + + // Cleanup + for (i = 0; i < channels; i++) { + free(buffer[i]); + free(offset[i]); + } + + if (maxLPC > 0) + free(lpc); + + if (size > 0) + free(unpackedBuffer); + + delete gReader; + return NULL; + break; + } + } + + // Rate is always 44100Hz + rate = 44100; + + // Cleanup + for (i = 0; i < channels; i++) { + free(buffer[i]); + free(offset[i]); + } + + if (maxLPC > 0) + free(lpc); + + if (size > 0) + free(unpackedBuffer); + + delete gReader; + return unpackedBuffer; +} + +AudioStream *makeShortenStream(Common::SeekableReadStream &stream) { + int size, rate; + byte *data, flags; + data = loadShortenFromStream(stream, size, rate, flags); + + if (!data) + return 0; + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return makeRawMemoryStream(data, size, rate, flags); +} + +} // End of namespace Audio + +#endif // defined(SOUND_SHORTEN_H) + diff --git a/sound/decoders/shorten.h b/sound/decoders/shorten.h new file mode 100644 index 0000000000..bc9f229687 --- /dev/null +++ b/sound/decoders/shorten.h @@ -0,0 +1,68 @@ +/* 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 currently only used in SAGA2. +// So when it is disabled, we will skip compiling it. +// We also enable this code for ScummVM builds including support +// for dynamic engine plugins. +// If you plan to use this code in another engine, you will have +// to add the proper define check here. +#if !(defined(ENABLE_SAGA2) || defined(DYNAMIC_MODULES)) + +#else + +#ifndef SOUND_SHORTEN_H +#define SOUND_SHORTEN_H + +#include "common/scummsys.h" + +namespace Common { class ReadStream; } + +namespace Audio { + +class AudioStream; + +/** + * Try to load a Shorten file from the given 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. + */ +byte *loadShortenFromStream(Common::ReadStream &stream, int &size, int &rate, byte &flags); + +/** + * Try to load a Shorten file from the given stream and create an AudioStream + * from that data. + * + * This function uses loadShortenFromStream() internally. + */ +AudioStream *makeShortenStream(Common::ReadStream &stream); + +} // End of namespace Audio + +#endif + +#endif // engine and dynamic plugins guard + diff --git a/sound/decoders/vag.cpp b/sound/decoders/vag.cpp new file mode 100644 index 0000000000..f2c9281d80 --- /dev/null +++ b/sound/decoders/vag.cpp @@ -0,0 +1,123 @@ +/* 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 "sound/decoders/vag.h" + +namespace Audio { + +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; +} + +} diff --git a/sound/decoders/vag.h b/sound/decoders/vag.h new file mode 100644 index 0000000000..d659a7db4e --- /dev/null +++ b/sound/decoders/vag.h @@ -0,0 +1,65 @@ +/* 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 + +#include "sound/audiostream.h" +#include "common/stream.h" + +namespace Audio { + +class VagStream : public Audio::RewindableAudioStream { +public: + VagStream(Common::SeekableReadStream *stream, int rate = 11025); + ~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; +}; + +} // End of namespace Sword1 + +#endif diff --git a/sound/decoders/voc.cpp b/sound/decoders/voc.cpp new file mode 100644 index 0000000000..65dd1a3998 --- /dev/null +++ b/sound/decoders/voc.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 "common/debug.h" +#include "common/endian.h" +#include "common/util.h" +#include "common/stream.h" + +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "sound/decoders/raw.h" +#include "sound/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, RawDiskStreamAudioBlock* 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 takeOwnership) { + const int MAX_AUDIO_BLOCKS = 256; + + RawDiskStreamAudioBlock *block = new RawDiskStreamAudioBlock[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(&stream, block, numBlocks, rate, flags, takeOwnership, begin_loop, end_loop); + + delete[] block; + + return audioStream; +} + +SeekableAudioStream *makeVOCDiskStreamNoLoop(Common::SeekableReadStream &stream, byte flags, DisposeAfterUse::Flag takeOwnership) { + const int MAX_AUDIO_BLOCKS = 256; + + RawDiskStreamAudioBlock *block = new RawDiskStreamAudioBlock[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(&stream, block, numBlocks, rate, flags, takeOwnership); + + delete[] block; + + return audioStream; +} + +#endif + + +AudioStream *makeVOCStream(Common::SeekableReadStream &stream, byte flags, uint loopStart, uint loopEnd, DisposeAfterUse::Flag takeOwnershipOfStream) { +#ifdef STREAM_AUDIO_FROM_DISK + return makeVOCDiskStream(stream, flags, takeOwnershipOfStream); +#else + int size, rate; + + byte *data = loadVOCFromStream(stream, size, rate); + + if (!data) + return 0; + + return makeRawMemoryStream_OLD(data, size, rate, flags, loopStart, loopEnd); +#endif +} + +SeekableAudioStream *makeVOCStream(Common::SeekableReadStream &stream, byte flags, DisposeAfterUse::Flag takeOwnershipOfStream) { +#ifdef STREAM_AUDIO_FROM_DISK + return makeVOCDiskStreamNoLoop(stream, flags, takeOwnershipOfStream); +#else + int size, rate; + + byte *data = loadVOCFromStream(stream, size, rate); + + if (!data) + return 0; + + return makeRawMemoryStream(data, size, rate, flags); +#endif +} + +} // End of namespace Audio diff --git a/sound/decoders/voc.h b/sound/decoders/voc.h new file mode 100644 index 0000000000..191f42db02 --- /dev/null +++ b/sound/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/types.h" +#include "common/scummsys.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/sound/decoders/vorbis.cpp b/sound/decoders/vorbis.cpp new file mode 100644 index 0000000000..46a94a6214 --- /dev/null +++ b/sound/decoders/vorbis.cpp @@ -0,0 +1,250 @@ +/* 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 "sound/decoders/vorbis.h" + +#ifdef USE_VORBIS + +#include "common/debug.h" +#include "common/stream.h" +#include "common/util.h" + +#include "sound/audiostream.h" +#include "sound/audiocd.h" + +#ifdef USE_TREMOR +#ifdef __GP32__ // GP32 uses custom libtremor +#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 VorbisInputStream 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 VorbisInputStream : 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 + VorbisInputStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose); + ~VorbisInputStream(); + + 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(); +}; + +VorbisInputStream::VorbisInputStream(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 +} + +VorbisInputStream::~VorbisInputStream() { + ov_clear(&_ovFile); + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _inStream; +} + +int VorbisInputStream::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 VorbisInputStream::seek(const Timestamp &where) { + int res = ov_pcm_seek(&_ovFile, calculateSampleOffset(where, getRate())); + if (res) { + warning("Error seeking in Vorbis stream (%d)", res); + _pos = _bufferEnd; + return false; + } + + return refill(); +} + +bool VorbisInputStream::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) { + return new VorbisInputStream(stream, disposeAfterUse); +} + +} // End of namespace Audio + +#endif // #ifdef USE_VORBIS diff --git a/sound/decoders/vorbis.h b/sound/decoders/vorbis.h new file mode 100644 index 0000000000..dd0a89bfdd --- /dev/null +++ b/sound/decoders/vorbis.h @@ -0,0 +1,73 @@ +/* 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 + * - kyra + * - m4 + * - queen + * - saga + * - scumm + * - sword1 + * - sword2 + * - touche + * - tucker + */ + +#ifndef SOUND_VORBIS_H +#define SOUND_VORBIS_H + +#include "common/types.h" +#include "common/scummsys.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 occured + */ +SeekableAudioStream *makeVorbisStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif // #ifdef USE_VORBIS +#endif // #ifndef SOUND_VORBIS_H diff --git a/sound/decoders/wave.cpp b/sound/decoders/wave.cpp new file mode 100644 index 0000000000..c0865ad30f --- /dev/null +++ b/sound/decoders/wave.cpp @@ -0,0 +1,195 @@ +/* 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 "sound/audiostream.h" +#include "sound/mixer.h" +#include "sound/decoders/wave.h" +#include "sound/decoders/adpcm.h" +#include "sound/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 + printf("WAVE information:\n"); + printf(" total size: %d\n", wavLength); + printf(" fmt size: %d\n", fmtLength); + printf(" type: %d\n", type); + printf(" numChannels: %d\n", numChannels); + printf(" samplesPerSec: %d\n", samplesPerSec); + printf(" avgBytesPerSec: %d\n", avgBytesPerSec); + printf(" blockAlign: %d\n", blockAlign); + printf(" bitsPerSample: %d\n", 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 + printf(" found a '%s' tag of size %d\n", 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; + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return makeRawMemoryStream(data, size, rate, flags); +} + +} // End of namespace Audio diff --git a/sound/decoders/wave.h b/sound/decoders/wave.h new file mode 100644 index 0000000000..f0e3bb249b --- /dev/null +++ b/sound/decoders/wave.h @@ -0,0 +1,82 @@ +/* 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 + * - saga + * - scumm + * - sword1 + * - sword2 + * - tucker + */ + +#ifndef SOUND_WAVE_H +#define SOUND_WAVE_H + +#include "common/types.h" +#include "common/scummsys.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 occured + */ +RewindableAudioStream *makeWAVStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif |