Merge pull request #336 from clone2727/emi-ps2-audio

GRIM: Add support for EMI PS2 audio
This commit is contained in:
Pawel Kolodziejski 2011-08-28 19:56:32 -07:00
commit 1c32722e59
6 changed files with 326 additions and 56 deletions

View File

@ -20,79 +20,78 @@
*
*/
#include "audio/decoders/vag.h"
#include "audio/decoders/xa.h"
#include "audio/audiostream.h"
#include "common/stream.h"
namespace Audio {
class VagStream : public Audio::RewindableAudioStream {
class XAStream : public Audio::RewindableAudioStream {
public:
VagStream(Common::SeekableReadStream *stream, int rate);
~VagStream();
XAStream(Common::SeekableReadStream *stream, int rate, DisposeAfterUse::Flag disposeAfterUse);
~XAStream();
bool isStereo() const { return false; }
bool endOfData() const { return _stream->pos() == _stream->size(); }
bool endOfData() const { return _endOfData && _samplesRemaining == 0; }
int getRate() const { return _rate; }
int readBuffer(int16 *buffer, const int numSamples);
bool rewind();
private:
Common::SeekableReadStream *_stream;
DisposeAfterUse::Flag _disposeAfterUse;
void seekToPos(uint pos);
byte _predictor;
double _samples[28];
byte _samplesRemaining;
int _rate;
double _s1, _s2;
uint _loopPoint;
bool _endOfData;
};
VagStream::VagStream(Common::SeekableReadStream *stream, int rate) : _stream(stream) {
XAStream::XAStream(Common::SeekableReadStream *stream, int rate, DisposeAfterUse::Flag disposeAfterUse)
: _stream(stream), _disposeAfterUse(disposeAfterUse) {
_samplesRemaining = 0;
_predictor = 0;
_s1 = _s2 = 0.0;
_rate = rate;
_loopPoint = 0;
_endOfData = false;
}
VagStream::~VagStream() {
delete _stream;
XAStream::~XAStream() {
if (_disposeAfterUse == DisposeAfterUse::YES)
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 }
};
static const double s_xaDataTable[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) {
int XAStream::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;
for (int i = 28 - _samplesRemaining; i < 28 && samplesDecoded < numSamples; i++) {
_samples[i] = _samples[i] + _s1 * s_xaDataTable[_predictor][0] + _s2 * s_xaDataTable[_predictor][1];
_s2 = _s1;
_s1 = _samples[i];
int16 d = (int) (_samples[i] + 0.5);
buffer[samplesDecoded] = d;
samplesDecoded++;
_samplesRemaining--;
}
if (endOfData())
return samplesDecoded;
while (samplesDecoded < numSamples) {
byte i = 0;
@ -100,8 +99,19 @@ int VagStream::readBuffer(int16 *buffer, const int numSamples) {
byte shift = _predictor & 0xf;
_predictor >>= 4;
if (_stream->readByte() == 7)
byte flags = _stream->readByte();
if (flags == 3) {
// Loop
seekToPos(_loopPoint);
continue;
} else if (flags == 6) {
// Set loop point
_loopPoint = _stream->pos() - 2;
} else if (flags == 7) {
// End of stream
_endOfData = true;
return samplesDecoded;
}
for (i = 0; i < 28; i += 2) {
byte d = _stream->readByte();
@ -116,7 +126,7 @@ int VagStream::readBuffer(int16 *buffer, const int numSamples) {
}
for (i = 0; i < 28 && samplesDecoded < numSamples; i++) {
_samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1];
_samples[i] = _samples[i] + _s1 * s_xaDataTable[_predictor][0] + _s2 * s_xaDataTable[_predictor][1];
_s2 = _s1;
_s1 = _samples[i];
int16 d = (int) (_samples[i] + 0.5);
@ -124,24 +134,31 @@ int VagStream::readBuffer(int16 *buffer, const int numSamples) {
samplesDecoded++;
}
if (i != 27)
if (i != 28)
_samplesRemaining = 28 - i;
if (_stream->pos() >= _stream->size())
_endOfData = true;
}
return samplesDecoded;
}
bool VagStream::rewind() {
_stream->seek(0);
_samplesRemaining = 0;
_predictor = 0;
_s1 = _s2 = 0.0;
bool XAStream::rewind() {
seekToPos(0);
return true;
}
RewindableAudioStream *makeVagStream(Common::SeekableReadStream *stream, int rate) {
return new VagStream(stream, rate);
void XAStream::seekToPos(uint pos) {
_stream->seek(pos);
_samplesRemaining = 0;
_predictor = 0;
_s1 = _s2 = 0.0;
_endOfData = false;
}
RewindableAudioStream *makeXAStream(Common::SeekableReadStream *stream, int rate, DisposeAfterUse::Flag disposeAfterUse) {
return new XAStream(stream, rate, disposeAfterUse);
}
} // End of namespace Audio

View File

@ -28,8 +28,10 @@
* - tinsel (PSX port of the game)
*/
#ifndef SOUND_VAG_H
#define SOUND_VAG_H
#ifndef AUDIO_DECODERS_XA_H
#define AUDIO_DECODERS_XA_H
#include "common/types.h"
namespace Common {
class SeekableReadStream;
@ -40,17 +42,19 @@ namespace Audio {
class RewindableAudioStream;
/**
* Takes an input stream containing Vag sound data and creates
* Takes an input stream containing XA ADPCM sound data and creates
* an RewindableAudioStream from that.
*
* @param stream the SeekableReadStream from which to read the ADPCM data
* @param stream the SeekableReadStream from which to read the XA ADPCM data
* @param rate the sampling rate
* @param disposeAfterUse whether to delete the stream after use.
* @return a new RewindableAudioStream, or NULL, if an error occurred
*/
RewindableAudioStream *makeVagStream(
RewindableAudioStream *makeXAStream(
Common::SeekableReadStream *stream,
int rate = 11025);
int rate,
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
} // End of namespace Sword1
} // End of namespace Audio
#endif

View File

@ -13,8 +13,8 @@ MODULE_OBJS := \
decoders/flac.o \
decoders/mp3.o \
decoders/raw.o \
decoders/vag.o \
decoders/vorbis.o \
decoders/xa.o \
softsynth/adlib.o \
softsynth/cms.o \
softsynth/opl/dbopl.o \

View File

@ -71,6 +71,7 @@ MODULE_OBJS := \
resource.o \
savegame.o \
scene.o \
scx.o \
sector.o \
textobject.o \
textsplit.o \

200
engines/grim/scx.cpp Normal file
View File

@ -0,0 +1,200 @@
/* Residual - A 3D game interpreter
*
* Residual 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 library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#include "audio/audiostream.h"
#include "audio/decoders/xa.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "common/stream.h"
#include "grim/scx.h"
namespace Grim {
// I've only ever seen up to two
#define MAX_CHANNELS 2
class SCXStream : public Audio::RewindableAudioStream {
public:
SCXStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse);
~SCXStream();
bool isStereo() const { return _channels == 2; }
bool endOfData() const { return _xaStreams[0]->endOfData(); }
int getRate() const { return _rate; }
int readBuffer(int16 *buffer, const int numSamples);
bool rewind();
private:
int _channels;
int _rate;
uint16 _blockSize;
Audio::RewindableAudioStream *_xaStreams[MAX_CHANNELS];
};
SCXStream::SCXStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
static const uint32 stereoChannelNames[MAX_CHANNELS] = { MKTAG('L', 'E', 'F', 'T'), MKTAG('R', 'G', 'H', 'T') };
stream->readUint32BE(); // 'SCRX'
stream->readUint32LE();
_blockSize = stream->readUint16LE();
/* totalBlockSize = */ stream->readUint16LE();
if (_blockSize & 0xf)
error("Bad SCX block size %04x", _blockSize);
// Base our channel count based off the block size
_channels = (_blockSize == 0) ? 1 : 2;
stream->skip(12);
uint32 channelSize[MAX_CHANNELS];
for (int i = 0; i < _channels; i++) {
uint32 tag = stream->readUint32BE();
if (isStereo()) {
if (tag != stereoChannelNames[i])
error("Bad stereo channel tag found '%s'", tag2str(tag));
} else if (tag != MKTAG('M', 'O', 'N', 'O'))
error("Bad mono channel tag found '%s'", tag2str(tag));
channelSize[i] = stream->readUint32LE();
}
stream->seek(0x80);
uint32 leftRate = 0, rightRate = 0;
for (int i = 0; i < _channels; i++) {
if (stream->readUint32BE() != MKTAG('V', 'A', 'G', 'p'))
error("Bad VAG header");
/* uint32 version = */ stream->readUint32BE();
stream->readUint32BE();
stream->readUint32BE();
if (i == 0)
leftRate = stream->readUint32BE();
else
rightRate = stream->readUint32BE();
stream->skip(12); // skip useless info
stream->skip(16); // skip name
stream->skip(16); // skip zeroes
}
if (isStereo() && leftRate != rightRate)
error("Mismatching SCX rates");
_rate = leftRate;
if (isStereo()) {
// TODO: Make XAStream allow for appending data (similar to how ScummVM
// handles AAC/QDM2. For now, we de-interleave the XA ADPCM data and then
// re-interleave in readBuffer().
// Of course, in doing something that does better streaming, it would
// screw up the XA loop points. So, I'm not really sure what is best atm.
byte *leftOut = new byte[channelSize[0]];
byte *rightOut = new byte[channelSize[1]];
Common::MemoryWriteStream *leftStream = new Common::MemoryWriteStream(leftOut, channelSize[0]);
Common::MemoryWriteStream *rightStream = new Common::MemoryWriteStream(rightOut, channelSize[1]);
byte *buf = new byte[_blockSize];
while (stream->pos() < stream->size()) {
stream->read(buf, _blockSize);
leftStream->write(buf, _blockSize);
stream->read(buf, _blockSize);
rightStream->write(buf, _blockSize);
}
_xaStreams[0] = Audio::makeXAStream(new Common::MemoryReadStream(leftOut, channelSize[0], DisposeAfterUse::YES), _rate);
_xaStreams[1] = Audio::makeXAStream(new Common::MemoryReadStream(rightOut, channelSize[1], DisposeAfterUse::YES), _rate);
delete[] buf;
delete leftStream;
delete rightStream;
} else {
_xaStreams[0] = Audio::makeXAStream(stream->readStream(channelSize[0]), _rate);
_xaStreams[1] = 0;
}
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
}
SCXStream::~SCXStream() {
for (int i = 0; i < MAX_CHANNELS; i++)
delete _xaStreams[i];
}
int SCXStream::readBuffer(int16 *buffer, const int numSamples) {
if (isStereo()) {
// Needs to be divisible by the channel count
assert((numSamples % 2) == 0);
// TODO: As per above, this probably should do more actual streaming
// Decode enough data from each channel
int samplesPerChannel = numSamples / 2;
int16 *leftSamples = new int16[samplesPerChannel];
int16 *rightSamples = new int16[samplesPerChannel];
int samplesDecodedLeft = _xaStreams[0]->readBuffer(leftSamples, samplesPerChannel);
int samplesDecodedRight = _xaStreams[1]->readBuffer(rightSamples, samplesPerChannel);
assert(samplesDecodedLeft == samplesDecodedRight);
// Now re-interleave the data
int samplesDecoded = 0;
int16 *leftSrc = leftSamples, *rightSrc = rightSamples;
for (; samplesDecoded < numSamples; samplesDecoded += 2) {
*buffer++ = *leftSrc++;
*buffer++ = *rightSrc++;
}
delete[] leftSamples;
delete[] rightSamples;
return samplesDecoded;
}
// Just read from the stream directly for mono
return _xaStreams[0]->readBuffer(buffer, numSamples);
}
bool SCXStream::rewind() {
if (!_xaStreams[0]->rewind())
return false;
return !isStereo() || _xaStreams[1]->rewind();
}
Audio::RewindableAudioStream *makeSCXStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
if (stream->readUint32BE() != MKTAG('S', 'C', 'R', 'X')) {
delete stream;
return 0;
}
stream->seek(0);
return new SCXStream(stream, disposeAfterUse);
}
} // End of namespace Grim

48
engines/grim/scx.h Normal file
View File

@ -0,0 +1,48 @@
/* Residual - A 3D game interpreter
*
* Residual 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 library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
#ifndef GRIM_SCX_H
#define GRIM_SCX_H
namespace Common {
class SeekableReadStream;
}
namespace Grim {
class Audio::RewindableAudioStream;
/**
* Takes an input stream containing SCX sound data and creates
* an RewindableAudioStream from that.
*
* @param stream the SeekableReadStream from which to read the SCX data
* @param disposeAfterUse whether to delete the stream after use
* @return a new RewindableAudioStream, or NULL, if an error occurred
*/
Audio::RewindableAudioStream *makeSCXStream(
Common::SeekableReadStream *stream,
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
} // End of namespace Grim
#endif