2007-05-30 21:56:52 +00:00
|
|
|
/* 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.
|
2003-09-10 12:19:57 +00:00
|
|
|
*
|
|
|
|
* 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
|
2005-10-18 01:30:26 +00:00
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
2003-09-10 12:19:57 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2009-01-30 05:25:17 +00:00
|
|
|
#include "common/debug.h"
|
2006-03-29 15:59:37 +00:00
|
|
|
#include "common/endian.h"
|
2003-09-10 12:19:57 +00:00
|
|
|
#include "common/util.h"
|
2004-07-31 11:46:58 +00:00
|
|
|
#include "common/stream.h"
|
2011-04-24 08:34:27 +00:00
|
|
|
#include "common/textconsole.h"
|
2011-10-12 19:42:24 +00:00
|
|
|
#include "common/list.h"
|
2004-01-03 00:33:14 +00:00
|
|
|
|
2011-02-09 01:09:01 +00:00
|
|
|
#include "audio/audiostream.h"
|
|
|
|
#include "audio/decoders/raw.h"
|
|
|
|
#include "audio/decoders/voc.h"
|
2003-09-10 12:19:57 +00:00
|
|
|
|
2006-04-29 22:33:31 +00:00
|
|
|
namespace Audio {
|
|
|
|
|
2011-10-12 19:42:24 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
bool checkVOCHeader(Common::ReadStream &stream) {
|
|
|
|
VocFileHeader fileHeader;
|
|
|
|
|
|
|
|
if (stream.read(&fileHeader, 8) != 8)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!memcmp(&fileHeader, "VTLK", 4)) {
|
|
|
|
if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader))
|
|
|
|
return false;
|
|
|
|
} else if (!memcmp(&fileHeader, "Creative", 8)) {
|
|
|
|
if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8)
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0)
|
|
|
|
return false;
|
|
|
|
//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);
|
|
|
|
|
|
|
|
if (offset != sizeof(VocFileHeader))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// 0x100 is an invalid VOC version used by German version of DOTT (Disk) and
|
|
|
|
// French version of Simon the Sorcerer 2 (CD)
|
|
|
|
if (version != 0x010A && version != 0x0114 && version != 0x0100)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (code != ~version + 0x1234)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
class VocStream : public SeekableAudioStream {
|
|
|
|
public:
|
|
|
|
VocStream(Common::SeekableReadStream *stream, bool isUnsigned, DisposeAfterUse::Flag disposeAfterUse);
|
|
|
|
~VocStream();
|
|
|
|
|
|
|
|
virtual int readBuffer(int16 *buffer, const int numSamples);
|
|
|
|
|
|
|
|
virtual bool isStereo() const { return false; }
|
|
|
|
|
|
|
|
virtual int getRate() const { return _rate; }
|
|
|
|
|
|
|
|
virtual bool endOfData() const { return (_curBlock == _blocks.end()) && (_blockLeft == 0); }
|
|
|
|
|
|
|
|
virtual bool seek(const Timestamp &where);
|
|
|
|
|
|
|
|
virtual Timestamp getLength() const { return _length; }
|
|
|
|
private:
|
|
|
|
void preProcess();
|
|
|
|
|
|
|
|
Common::SeekableReadStream *const _stream;
|
|
|
|
const DisposeAfterUse::Flag _disposeAfterUse;
|
|
|
|
|
|
|
|
const bool _isUnsigned;
|
|
|
|
|
|
|
|
int _rate;
|
|
|
|
Timestamp _length;
|
|
|
|
|
|
|
|
struct Block {
|
|
|
|
uint8 code;
|
|
|
|
uint32 length;
|
|
|
|
|
|
|
|
union {
|
|
|
|
struct {
|
|
|
|
uint32 offset;
|
|
|
|
int rate;
|
|
|
|
int samples;
|
|
|
|
} sampleBlock;
|
|
|
|
|
|
|
|
struct {
|
|
|
|
int count;
|
|
|
|
} loopBlock;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef Common::List<Block> BlockList;
|
|
|
|
BlockList _blocks;
|
|
|
|
|
|
|
|
BlockList::const_iterator _curBlock;
|
|
|
|
uint32 _blockLeft;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Advance one block in the stream in case
|
|
|
|
* the current one is empty.
|
|
|
|
*/
|
|
|
|
void updateBlockIfNeeded();
|
|
|
|
|
|
|
|
// Do some internal buffering for systems with really slow slow disk i/o
|
|
|
|
enum {
|
|
|
|
/**
|
|
|
|
* How many samples we can buffer at once.
|
|
|
|
*
|
|
|
|
* TODO: Check whether this size suffices
|
|
|
|
* for systems with slow disk I/O.
|
|
|
|
*/
|
|
|
|
kSampleBufferLength = 2048
|
|
|
|
};
|
|
|
|
byte _buffer[kSampleBufferLength];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fill the temporary sample buffer used in readBuffer.
|
|
|
|
*
|
|
|
|
* @param maxSamples Maximum samples to read.
|
|
|
|
* @return actual count of samples read.
|
|
|
|
*/
|
|
|
|
int fillBuffer(int maxSamples);
|
|
|
|
};
|
|
|
|
|
|
|
|
VocStream::VocStream(Common::SeekableReadStream *stream, bool isUnsigned, DisposeAfterUse::Flag disposeAfterUse)
|
|
|
|
: _stream(stream), _disposeAfterUse(disposeAfterUse), _isUnsigned(isUnsigned), _rate(0),
|
|
|
|
_length(), _blocks(), _curBlock(_blocks.end()), _blockLeft(0), _buffer() {
|
|
|
|
preProcess();
|
|
|
|
}
|
|
|
|
|
|
|
|
VocStream::~VocStream() {
|
|
|
|
if (_disposeAfterUse == DisposeAfterUse::YES)
|
|
|
|
delete _stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
int VocStream::readBuffer(int16 *buffer, const int numSamples) {
|
|
|
|
int samplesLeft = numSamples;
|
|
|
|
while (samplesLeft > 0) {
|
|
|
|
// Try to read up to "samplesLeft" samples.
|
|
|
|
int len = fillBuffer(samplesLeft);
|
|
|
|
|
|
|
|
// In case we were not able to read any samples
|
|
|
|
// we will stop reading here.
|
|
|
|
if (!len)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Adjust the samples left to read.
|
|
|
|
samplesLeft -= len;
|
|
|
|
|
|
|
|
// Copy the data to the caller's buffer.
|
|
|
|
const byte *src = _buffer;
|
|
|
|
while (len-- > 0)
|
|
|
|
*buffer++ = (*src++ << 8) ^ (_isUnsigned ? 0x8000 : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return numSamples - samplesLeft;
|
|
|
|
}
|
|
|
|
|
|
|
|
void VocStream::updateBlockIfNeeded() {
|
|
|
|
// Have we now finished this block? If so, read the next block
|
|
|
|
if (_blockLeft == 0 && _curBlock != _blocks.end()) {
|
|
|
|
// Find the next sample block
|
|
|
|
while (true) {
|
|
|
|
// Next block
|
|
|
|
++_curBlock;
|
|
|
|
|
|
|
|
// Check whether we reached the end of the stream
|
|
|
|
// yet.
|
|
|
|
if (_curBlock == _blocks.end())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Skip all none sample blocks for now
|
|
|
|
if (_curBlock->code != 1 && _curBlock->code != 9)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
_stream->seek(_curBlock->sampleBlock.offset, SEEK_SET);
|
|
|
|
|
|
|
|
// In case of an error we will stop
|
|
|
|
// stream playback.
|
|
|
|
if (_stream->err()) {
|
|
|
|
_blockLeft = 0;
|
|
|
|
_curBlock = _blocks.end();
|
|
|
|
} else {
|
|
|
|
_blockLeft = _curBlock->sampleBlock.samples;
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int VocStream::fillBuffer(int maxSamples) {
|
|
|
|
int bufferedSamples = 0;
|
|
|
|
byte *dst = _buffer;
|
|
|
|
|
|
|
|
// We can only read up to "kSampleBufferLength" samples
|
|
|
|
// so we take this into consideration, when trying to
|
|
|
|
// read up to maxSamples.
|
|
|
|
maxSamples = MIN<int>(kSampleBufferLength, maxSamples);
|
|
|
|
|
|
|
|
// We will only read up to maxSamples
|
|
|
|
while (maxSamples > 0 && !endOfData()) {
|
|
|
|
// Calculate how many samples we can safely read
|
|
|
|
// from the current block.
|
|
|
|
const int len = MIN<int>(maxSamples, _blockLeft);
|
|
|
|
|
|
|
|
// Try to read all the sample data and update the
|
|
|
|
// destination pointer.
|
|
|
|
const int bytesRead = _stream->read(dst, len);
|
|
|
|
dst += bytesRead;
|
|
|
|
|
|
|
|
// Calculate how many samples we actually read.
|
|
|
|
const int samplesRead = bytesRead;
|
|
|
|
|
|
|
|
// Update all status variables
|
|
|
|
bufferedSamples += samplesRead;
|
|
|
|
maxSamples -= samplesRead;
|
|
|
|
_blockLeft -= samplesRead;
|
|
|
|
|
|
|
|
// In case of an error we will stop
|
|
|
|
// stream playback.
|
|
|
|
if (_stream->err()) {
|
|
|
|
_blockLeft = 0;
|
|
|
|
_curBlock = _blocks.end();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Advance to the next block in case the current
|
|
|
|
// one is already finished.
|
|
|
|
updateBlockIfNeeded();
|
|
|
|
}
|
|
|
|
|
|
|
|
return bufferedSamples;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VocStream::seek(const Timestamp &where) {
|
|
|
|
// Invalidate stream
|
|
|
|
_blockLeft = 0;
|
|
|
|
_curBlock = _blocks.end();
|
|
|
|
|
|
|
|
if (where > _length)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Search for the block containing the requested sample
|
|
|
|
const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames();
|
|
|
|
uint32 curSample = 0;
|
|
|
|
|
|
|
|
for (_curBlock = _blocks.begin(); _curBlock != _blocks.end(); ++_curBlock) {
|
|
|
|
// Skip all none sample blocks for now
|
|
|
|
if (_curBlock->code != 1 && _curBlock->code != 9)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
uint32 nextBlockSample = curSample + _curBlock->sampleBlock.samples;
|
|
|
|
|
|
|
|
if (nextBlockSample > seekSample)
|
|
|
|
break;
|
|
|
|
|
|
|
|
curSample = nextBlockSample;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_curBlock == _blocks.end()) {
|
|
|
|
return ((seekSample - curSample) == 0);
|
|
|
|
} else {
|
|
|
|
const uint32 offset = seekSample - curSample;
|
|
|
|
|
|
|
|
_stream->seek(_curBlock->sampleBlock.offset + offset, SEEK_SET);
|
|
|
|
|
|
|
|
// In case of an error we will stop
|
|
|
|
// stream playback.
|
|
|
|
if (_stream->err()) {
|
|
|
|
_blockLeft = 0;
|
|
|
|
_curBlock = _blocks.end();
|
|
|
|
} else {
|
|
|
|
_blockLeft = _curBlock->sampleBlock.samples - offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void VocStream::preProcess() {
|
|
|
|
Block block;
|
|
|
|
|
|
|
|
// Scan through the file and collect all blocks
|
|
|
|
while (true) {
|
|
|
|
block.code = _stream->readByte();
|
|
|
|
block.length = 0;
|
|
|
|
|
|
|
|
// If we hit EOS here we found the end of the VOC file.
|
|
|
|
// According to http://wiki.multimedia.cx/index.php?title=Creative_Voice
|
|
|
|
// there is no need for an "Terminator" block to be present.
|
|
|
|
// In case we hit a "Terminator" block we also break here.
|
|
|
|
if (_stream->eos() || block.code == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
block.length = _stream->readByte();
|
|
|
|
block.length |= _stream->readByte() << 8;
|
|
|
|
block.length |= _stream->readByte() << 16;
|
|
|
|
|
|
|
|
// Premature end of stream => error!
|
|
|
|
if (_stream->eos() || _stream->err())
|
|
|
|
return;
|
|
|
|
|
|
|
|
uint32 skip = 0;
|
|
|
|
|
|
|
|
switch (block.code) {
|
|
|
|
// Sound data
|
|
|
|
case 1:
|
|
|
|
// Sound data (New format)
|
|
|
|
case 9:
|
|
|
|
if (block.code == 1) {
|
|
|
|
// Read header data
|
|
|
|
int freqDiv = _stream->readByte();
|
|
|
|
// Prevent division through 0
|
|
|
|
if (freqDiv == 256)
|
|
|
|
return;
|
|
|
|
block.sampleBlock.rate = getSampleRateFromVOCRate(freqDiv);
|
|
|
|
|
|
|
|
int codec = _stream->readByte();
|
|
|
|
// We only support 8bit PCM
|
|
|
|
if (codec != 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
block.sampleBlock.samples = skip = block.length - 2;
|
|
|
|
block.sampleBlock.offset = _stream->pos();
|
|
|
|
|
|
|
|
// Check the last block if there is any
|
|
|
|
if (_blocks.size() > 0) {
|
|
|
|
BlockList::iterator lastBlock = _blocks.end();
|
|
|
|
--lastBlock;
|
|
|
|
// When we have found a block 8 as predecessor
|
|
|
|
// we need to use its settings
|
|
|
|
if (lastBlock->code == 8) {
|
|
|
|
block.sampleBlock.rate = lastBlock->sampleBlock.rate;
|
|
|
|
// Remove the block since we don't need it anymore
|
|
|
|
_blocks.erase(lastBlock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether we found a new highest rate
|
|
|
|
if (_rate < block.sampleBlock.rate)
|
|
|
|
_rate = block.sampleBlock.rate;
|
|
|
|
} else {
|
|
|
|
block.sampleBlock.rate = _stream->readUint32LE();
|
|
|
|
int bitsPerSample = _stream->readByte();
|
|
|
|
// We only support 8bit PCM
|
|
|
|
if (bitsPerSample != 8)
|
|
|
|
return;
|
|
|
|
int channels = _stream->readByte();
|
|
|
|
// We only support mono
|
|
|
|
if (channels != 1) {
|
|
|
|
warning("Unhandled channel count %d in VOC file", channels);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
int codec = _stream->readByte();
|
|
|
|
// We only support 8bit PCM
|
|
|
|
if (codec != 0) {
|
|
|
|
warning("Unhandled codec %d in VOC file", codec);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/*uint32 reserved = */_stream->readUint32LE();
|
|
|
|
block.sampleBlock.offset = _stream->pos();
|
|
|
|
block.sampleBlock.samples = skip = block.length - 12;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Silence
|
|
|
|
case 3: {
|
|
|
|
if (block.length != 3)
|
|
|
|
return;
|
|
|
|
|
|
|
|
block.sampleBlock.offset = 0;
|
|
|
|
|
|
|
|
block.sampleBlock.samples = _stream->readUint16LE() + 1;
|
|
|
|
int freqDiv = _stream->readByte();
|
|
|
|
// Prevent division through 0
|
|
|
|
if (freqDiv == 256)
|
|
|
|
return;
|
|
|
|
block.sampleBlock.rate = getSampleRateFromVOCRate(freqDiv);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
// Repeat start
|
|
|
|
case 6:
|
|
|
|
if (block.length != 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
block.loopBlock.count = _stream->readUint16LE() + 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Repeat end
|
|
|
|
case 7:
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Extra info
|
|
|
|
case 8: {
|
|
|
|
if (block.length != 4)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int freqDiv = _stream->readUint16LE();
|
|
|
|
// Prevent division through 0
|
|
|
|
if (freqDiv == 65536)
|
|
|
|
return;
|
|
|
|
|
|
|
|
int codec = _stream->readByte();
|
|
|
|
// We only support RAW 8bit PCM.
|
|
|
|
if (codec != 0) {
|
|
|
|
warning("Unhandled codec %d in VOC file", codec);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int channels = _stream->readByte() + 1;
|
|
|
|
// We only support mono sound right now
|
|
|
|
if (channels != 1) {
|
|
|
|
warning("Unhandled channel count %d in VOC file", channels);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
block.sampleBlock.offset = 0;
|
|
|
|
block.sampleBlock.samples = 0;
|
|
|
|
block.sampleBlock.rate = 256000000L / (65536L - freqDiv);
|
|
|
|
} break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
warning("Unhandled code %d in VOC file (len %d)", block.code, block.length);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Premature end of stream => error!
|
|
|
|
if (_stream->eos() || _stream->err())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Skip the rest of the block
|
|
|
|
if (skip)
|
|
|
|
_stream->skip(skip);
|
|
|
|
|
|
|
|
_blocks.push_back(block);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Since we determined the sample rate we need for playback now, we will
|
|
|
|
// initialize the play length.
|
|
|
|
_length = Timestamp(0, _rate);
|
|
|
|
|
|
|
|
// Calculate the total play time and do some more sanity checks
|
|
|
|
for (BlockList::const_iterator i = _blocks.begin(), end = _blocks.end(); i != end; ++i) {
|
|
|
|
// Check whether we found a block 8 which survived, this is not
|
|
|
|
// allowed to happen!
|
|
|
|
if (i->code == 8) {
|
|
|
|
warning("VOC file contains unused block 8");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For now only use blocks with actual samples
|
|
|
|
if (i->code != 1 && i->code != 9)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// Check the sample rate
|
|
|
|
if (i->sampleBlock.rate != _rate) {
|
|
|
|
warning("VOC file contains chunks with different sample rates (%d != %d)", _rate, i->sampleBlock.rate);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_length = _length.addFrames(i->sampleBlock.samples);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the current block to the first block in the stream
|
|
|
|
rewind();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // End of anonymous namespace
|
|
|
|
|
2004-01-03 00:33:14 +00:00
|
|
|
int getSampleRateFromVOCRate(int vocSR) {
|
2004-05-05 10:58:07 +00:00
|
|
|
if (vocSR == 0xa5 || vocSR == 0xa6) {
|
2004-01-03 00:33:14 +00:00
|
|
|
return 11025;
|
|
|
|
} else if (vocSR == 0xd2 || vocSR == 0xd3) {
|
|
|
|
return 22050;
|
|
|
|
} else {
|
|
|
|
int sr = 1000000L / (256L - vocSR);
|
2004-05-05 10:58:07 +00:00
|
|
|
// inexact sampling rates occur e.g. in the kitchen in Monkey Island,
|
2004-05-02 22:33:28 +00:00
|
|
|
// very easy to reach right from the start of the game.
|
|
|
|
//warning("inexact sample rate used: %i (0x%x)", sr, vocSR);
|
2004-01-03 00:33:14 +00:00
|
|
|
return sr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-10-09 18:57:25 +00:00
|
|
|
byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate) {
|
2004-04-10 01:46:38 +00:00
|
|
|
VocFileHeader fileHeader;
|
2003-12-26 12:15:23 +00:00
|
|
|
|
2009-11-02 21:42:21 +00:00
|
|
|
debug(2, "loadVOCFromStream");
|
|
|
|
|
2004-12-11 23:34:34 +00:00
|
|
|
if (stream.read(&fileHeader, 8) != 8)
|
2003-12-26 12:15:23 +00:00
|
|
|
goto invalid;
|
|
|
|
|
2004-04-10 01:46:38 +00:00
|
|
|
if (!memcmp(&fileHeader, "VTLK", 4)) {
|
2004-12-11 23:34:34 +00:00
|
|
|
if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader))
|
2004-04-10 01:46:38 +00:00
|
|
|
goto invalid;
|
|
|
|
} else if (!memcmp(&fileHeader, "Creative", 8)) {
|
2004-12-11 23:34:34 +00:00
|
|
|
if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8)
|
2004-04-10 01:46:38 +00:00
|
|
|
goto invalid;
|
2003-12-26 12:15:23 +00:00
|
|
|
} else {
|
|
|
|
invalid:;
|
2004-07-31 11:46:58 +00:00
|
|
|
warning("loadVOCFromStream: Invalid header");
|
2003-12-26 12:15:23 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2004-05-05 10:58:07 +00:00
|
|
|
if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0)
|
2004-07-31 11:46:58 +00:00
|
|
|
error("loadVOCFromStream: Invalid header");
|
2004-05-05 10:58:07 +00:00
|
|
|
if (fileHeader.desc[19] != 0x1A)
|
2004-07-31 11:46:58 +00:00
|
|
|
debug(3, "loadVOCFromStream: Partially invalid header");
|
2004-05-05 10:58:07 +00:00
|
|
|
|
2004-07-31 11:46:58 +00:00
|
|
|
int32 offset = FROM_LE_16(fileHeader.datablock_offset);
|
2004-04-10 01:46:38 +00:00
|
|
|
int16 version = FROM_LE_16(fileHeader.version);
|
|
|
|
int16 code = FROM_LE_16(fileHeader.id);
|
2004-07-31 11:46:58 +00:00
|
|
|
assert(offset == sizeof(VocFileHeader));
|
2004-08-03 10:11:54 +00:00
|
|
|
// 0x100 is an invalid VOC version used by German version of DOTT (Disk) and
|
|
|
|
// French version of Simon the Sorcerer 2 (CD)
|
2004-08-03 03:18:51 +00:00
|
|
|
assert(version == 0x010A || version == 0x0114 || version == 0x0100);
|
2004-04-10 01:46:38 +00:00
|
|
|
assert(code == ~version + 0x1234);
|
2003-12-26 12:15:23 +00:00
|
|
|
|
2004-04-10 22:34:07 +00:00
|
|
|
int len;
|
2004-04-10 01:46:38 +00:00
|
|
|
byte *ret_sound = 0;
|
|
|
|
size = 0;
|
2003-12-26 12:15:23 +00:00
|
|
|
|
2004-12-11 23:34:34 +00:00
|
|
|
while ((code = stream.readByte())) {
|
|
|
|
len = stream.readByte();
|
|
|
|
len |= stream.readByte() << 8;
|
|
|
|
len |= stream.readByte() << 16;
|
2004-04-10 22:34:07 +00:00
|
|
|
|
2009-11-02 21:42:21 +00:00
|
|
|
debug(2, "Block code %d, len %d", code, len);
|
|
|
|
|
2006-07-14 13:33:58 +00:00
|
|
|
switch (code) {
|
2006-11-03 21:03:12 +00:00
|
|
|
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();
|
2007-09-19 08:40:12 +00:00
|
|
|
stream.readUint32LE();
|
2006-11-03 21:03:12 +00:00
|
|
|
len -= 12;
|
|
|
|
}
|
2004-04-10 01:46:38 +00:00
|
|
|
debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len);
|
|
|
|
if (packing == 0) {
|
|
|
|
if (size) {
|
2011-06-03 19:43:16 +00:00
|
|
|
byte *tmp = (byte *)realloc(ret_sound, size + len);
|
|
|
|
if (!tmp)
|
|
|
|
error("Cannot reallocate memory for VOC Data Block");
|
|
|
|
|
|
|
|
ret_sound = tmp;
|
2004-04-10 01:46:38 +00:00
|
|
|
} else {
|
|
|
|
ret_sound = (byte *)malloc(len);
|
|
|
|
}
|
2004-12-11 23:34:34 +00:00
|
|
|
stream.read(ret_sound + size, len);
|
2004-04-10 01:46:38 +00:00
|
|
|
size += len;
|
|
|
|
} else {
|
|
|
|
warning("VOC file packing %d unsupported", packing);
|
|
|
|
}
|
|
|
|
} break;
|
2007-11-18 06:24:40 +00:00
|
|
|
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;
|
2004-07-31 11:46:58 +00:00
|
|
|
case 6: // begin of loop
|
|
|
|
assert(len == 2);
|
2011-10-09 18:57:25 +00:00
|
|
|
stream.readUint16LE();
|
2004-07-31 11:46:58 +00:00
|
|
|
break;
|
|
|
|
case 7: // end of loop
|
|
|
|
assert(len == 0);
|
|
|
|
break;
|
2010-01-09 18:51:25 +00:00
|
|
|
case 8: { // "Extended"
|
2010-01-25 01:39:44 +00:00
|
|
|
// This occures in the LoL Intro demo.
|
2010-01-09 18:51:25 +00:00
|
|
|
// 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))
|
2009-05-18 22:11:56 +00:00
|
|
|
assert(len == 4);
|
|
|
|
stream.readUint16LE();
|
2010-01-09 18:51:25 +00:00
|
|
|
uint8 codec = stream.readByte();
|
|
|
|
uint8 channels = stream.readByte() + 1;
|
|
|
|
assert(codec == 0 && channels == 1);
|
|
|
|
} break;
|
2004-04-10 01:46:38 +00:00
|
|
|
default:
|
2009-11-02 21:42:21 +00:00
|
|
|
warning("Unhandled code %d in VOC file (len %d)", code, len);
|
2004-04-10 22:34:07 +00:00
|
|
|
return ret_sound;
|
2004-04-10 01:46:38 +00:00
|
|
|
}
|
2003-12-26 12:15:23 +00:00
|
|
|
}
|
2004-04-10 01:46:38 +00:00
|
|
|
debug(4, "VOC Data Size : %d", size);
|
|
|
|
return ret_sound;
|
2003-12-26 12:15:23 +00:00
|
|
|
}
|
|
|
|
|
2010-03-08 10:27:42 +00:00
|
|
|
SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) {
|
2011-10-12 19:42:24 +00:00
|
|
|
if (!checkVOCHeader(*stream)) {
|
2010-03-08 10:27:42 +00:00
|
|
|
if (disposeAfterUse == DisposeAfterUse::YES)
|
|
|
|
delete stream;
|
2010-01-10 15:20:14 +00:00
|
|
|
return 0;
|
2010-03-08 10:27:42 +00:00
|
|
|
}
|
2010-01-10 15:20:14 +00:00
|
|
|
|
2011-10-12 19:42:24 +00:00
|
|
|
SeekableAudioStream *audioStream = new VocStream(stream, (flags & Audio::FLAG_UNSIGNED) != 0, disposeAfterUse);
|
|
|
|
|
|
|
|
if (audioStream && audioStream->endOfData()) {
|
|
|
|
delete audioStream;
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
return audioStream;
|
|
|
|
}
|
2010-01-10 15:20:14 +00:00
|
|
|
}
|
2006-04-29 22:33:31 +00:00
|
|
|
|
|
|
|
} // End of namespace Audio
|