diff options
| author | Max Horn | 2010-01-26 22:48:45 +0000 | 
|---|---|---|
| committer | Max Horn | 2010-01-26 22:48:45 +0000 | 
| commit | 1565f14bc13a63aee6a42cc4fac3fe7fa39eda44 (patch) | |
| tree | 41b4b65eb29718398f148dc6f7a6e131376fcc27 /sound/decoders | |
| parent | e0d05a482ce93e029888ef388dfb1b90b438f2ee (diff) | |
| download | scummvm-rg350-1565f14bc13a63aee6a42cc4fac3fe7fa39eda44.tar.gz scummvm-rg350-1565f14bc13a63aee6a42cc4fac3fe7fa39eda44.tar.bz2 scummvm-rg350-1565f14bc13a63aee6a42cc4fac3fe7fa39eda44.zip  | |
Moved audio stream implementations (for MP3, FLAC, etc.) to new dir sound/decoders/
svn-id: r47579
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  | 
