scummvm/audio/decoders/voc.cpp
2021-12-26 18:48:43 +01:00

570 lines
15 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/endian.h"
#include "common/util.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "common/list.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/voc.h"
namespace Audio {
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, "checkVOCHeader: 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();
int readBuffer(int16 *buffer, const int numSamples) override;
bool isStereo() const override { return false; }
int getRate() const override { return _rate; }
bool endOfData() const override { return (_curBlock == _blocks.end()) && (_blockLeft == 0); }
bool seek(const Timestamp &where) override;
Timestamp getLength() const override { 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;
// We will allow invalid block numbers as terminators. This is needed,
// since some games ship broken VOC files. The following occasions are
// known:
// - 128 is used as terminator in Simon 1 Amiga CD32
// - Full Throttle contains a VOC file with an incorrect block length
// resulting in a sample (127) to be read as block code.
if (block.code > 9) {
warning("VocStream::preProcess: Caught %d as terminator", block.code);
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()) {
warning("VocStream::preProcess: Reading failed");
return;
}
uint32 skip = 0;
switch (block.code) {
// Sound data
case 1:
// Sound data (New format)
case 9:
if (block.code == 1) {
if (block.length < 2) {
warning("Invalid sound data block length %d in VOC file", block.length);
return;
}
// Read header data
int freqDiv = _stream->readByte();
// Prevent division through 0
if (freqDiv == 256) {
warning("Invalid frequency divisor 256 in VOC file");
return;
}
block.sampleBlock.rate = getSampleRateFromVOCRate(freqDiv);
int codec = _stream->readByte();
// We only support 8bit PCM
if (codec != 0) {
warning("Unhandled codec %d in VOC file", codec);
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);
}
}
} else {
if (block.length < 12) {
warning("Invalid sound data (wew format) block length %d in VOC file", block.length);
return;
}
block.sampleBlock.rate = _stream->readUint32LE();
int bitsPerSample = _stream->readByte();
// We only support 8bit PCM
if (bitsPerSample != 8) {
warning("Unhandled bits per sample %d in VOC file", bitsPerSample);
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->readUint16LE();
// 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;
}
// Check whether we found a new highest rate
if (_rate < block.sampleBlock.rate)
_rate = block.sampleBlock.rate;
break;
// Silence
case 3: {
if (block.length != 3) {
warning("Invalid silence block length %d in VOC file", block.length);
return;
}
block.sampleBlock.offset = 0;
block.sampleBlock.samples = _stream->readUint16LE() + 1;
int freqDiv = _stream->readByte();
// Prevent division through 0
if (freqDiv == 256) {
warning("Invalid frequency divisor 256 in VOC file");
return;
}
block.sampleBlock.rate = getSampleRateFromVOCRate(freqDiv);
} break;
// Repeat start
case 6:
if (block.length != 2) {
warning("Invalid repeat start block length %d in VOC file", block.length);
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) {
warning("Invalid frequency divisor 65536 in VOC file");
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);
// Skip the whole block and try to use the next one.
skip = block.length;
}
// Premature end of stream => error!
if (_stream->eos() || _stream->err()) {
warning("VocStream::preProcess: Reading failed");
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
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;
}
}
SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) {
if (!checkVOCHeader(*stream)) {
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
return nullptr;
}
SeekableAudioStream *audioStream = new VocStream(stream, (flags & Audio::FLAG_UNSIGNED) != 0, disposeAfterUse);
if (audioStream->endOfData()) {
delete audioStream;
return nullptr;
} else {
return audioStream;
}
}
} // End of namespace Audio