Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
/* 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/mutex.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "audio/decoders/aiff.h"
#include "engines/grim/debug.h"
#include "engines/grim/resource.h"
#include "engines/grim/emi/sound/aifftrack.h"
namespace Grim {
AIFFTrack::AIFFTrack(Audio::Mixer::SoundType soundType) {
_soundType = soundType;
_looping = false;
// A preloaded AIFF track may be played multiple times, so we don't
// want to dispose after playing. The destructor of SoundTrack will
// take care of disposing the stream instead.
_disposeAfterPlaying = DisposeAfterUse::NO;
}
AIFFTrack::~AIFFTrack() {
stop();
if (_handle) {
g_system->getMixer()->stopHandle(*_handle);
delete _handle;
}
}
bool AIFFTrack::openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start) {
Common::SeekableReadStream *file = g_resourceloader->openNewStreamFile(filename, true);
if (!file) {
Debug::debug(Debug::Sound, "Stream for %s not open", soundName.c_str());
return false;
}
_soundName = soundName;
Audio::RewindableAudioStream *aiffStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES);
Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(aiffStream);
_stream = aiffStream;
if (start)
seekStream->seek(*start);
if (!_stream)
return false;
_handle = new Audio::SoundHandle();
return true;
}
void AIFFTrack::setLooping(bool looping) {
if (_looping == looping)
return;
_looping = looping;
if (looping && _stream) {
_stream = Audio::makeLoopingAudioStream(dynamic_cast<Audio::SeekableAudioStream *>(_stream), 0);
}
}
bool AIFFTrack::play() {
if (_stream) {
Audio::RewindableAudioStream *stream = dynamic_cast<Audio::RewindableAudioStream *>(_stream);
if (!_looping) {
stream->rewind();
}
return SoundTrack::play();
}
return false;
}
bool AIFFTrack::isPlaying() {
if (!_handle)
return false;
return g_system->getMixer()->isSoundHandleActive(*_handle);
}
Audio::Timestamp AIFFTrack::getPos() {
// FIXME: Return actual stream position.
return g_system->getMixer()->getSoundElapsedTime(*_handle);
}
} // end of namespace Grim

View File

@@ -0,0 +1,53 @@
/* 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/>.
*
*/
#ifndef GRIM_AIFFTRACK_H
#define GRIM_AIFFTRACK_H
#include "common/str.h"
#include "common/stream.h"
#include "engines/grim/emi/sound/track.h"
namespace Audio {
class AudioStream;
class SoundHandle;
}
namespace Grim {
class AIFFTrack : public SoundTrack {
public:
AIFFTrack(Audio::Mixer::SoundType soundType);
~AIFFTrack();
bool openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start = nullptr) override;
bool isPlaying() override;
bool isStreamOpen() { return _stream != NULL; }
void setLooping(bool looping) override;
bool isLooping() const override { return _looping; }
bool play() override;
Audio::Timestamp getPos() override;
private:
bool _looping;
};
}
#endif

View File

@@ -0,0 +1,207 @@
/* 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 "audio/audiostream.h"
#include "audio/decoders/xa.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "common/stream.h"
#include "engines/grim/emi/sound/codecs/scx.h"
namespace Grim {
SCXStream::SCXStream(Common::SeekableReadStream *stream, const Audio::Timestamp *start, DisposeAfterUse::Flag disposeAfterUse) {
static const uint32 stereoChannelNames[SCX_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[SCX_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 = (byte*)malloc(channelSize[0]);
byte *rightOut = (byte*)malloc(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);
}
_fileStreams[0] = new Common::MemoryReadStream(leftOut, channelSize[0], DisposeAfterUse::YES);
_fileStreams[1] = new Common::MemoryReadStream(rightOut, channelSize[1], DisposeAfterUse::YES);
_xaStreams[0] = Audio::makeXAStream(_fileStreams[0], _rate);
_xaStreams[1] = Audio::makeXAStream(_fileStreams[1], _rate);
delete[] buf;
delete leftStream;
delete rightStream;
} else {
_fileStreams[0] = stream->readStream(channelSize[0]);
_fileStreams[1] = nullptr;
_xaStreams[0] = Audio::makeXAStream(_fileStreams[0], _rate);
_xaStreams[1] = nullptr;
}
if (start) {
// Read data from the sound stream until we hit the desired start position.
// We do this instead of seeking so the loop point gets set up properly.
int samples = (int)((int64)start->msecs() * _rate / 1000);
int16 temp[1024];
while (samples > 0) {
samples -= _xaStreams[0]->readBuffer(temp, samples < 1024 ? samples : 1024);
if (_xaStreams[1]) {
_xaStreams[1]->readBuffer(temp, samples < 1024 ? samples : 1024);
}
}
}
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
}
SCXStream::~SCXStream() {
for (int i = 0; i < SCX_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);
(void)samplesDecodedRight;
// Now re-interleave the data
int samplesDecoded = 0;
int16 *leftSrc = leftSamples, *rightSrc = rightSamples;
for (; samplesDecoded < samplesDecodedLeft * 2; 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::Timestamp SCXStream::getPos() const {
int32 pos = _fileStreams[0]->pos();
// Each XA ADPCM block of 16 bytes decompresses to 28 samples.
int32 samples = pos * 28 / 16;
uint32 msecs = (uint32)((int64)samples * 1000 / _rate);
return Audio::Timestamp(msecs);
}
SCXStream *makeSCXStream(Common::SeekableReadStream *stream, const Audio::Timestamp *start, DisposeAfterUse::Flag disposeAfterUse) {
if (stream->readUint32BE() != MKTAG('S', 'C', 'R', 'X')) {
delete stream;
return nullptr;
}
stream->seek(0);
return new SCXStream(stream, start, disposeAfterUse);
}
} // End of namespace Grim

View File

@@ -0,0 +1,71 @@
/* 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/>.
*
*/
#ifndef GRIM_SCX_H
#define GRIM_SCX_H
namespace Common {
class SeekableReadStream;
}
namespace Grim {
// I've only ever seen up to two
#define SCX_MAX_CHANNELS 2
class SCXStream : public Audio::RewindableAudioStream {
public:
SCXStream(Common::SeekableReadStream *stream, const Audio::Timestamp *start, DisposeAfterUse::Flag disposeAfterUse);
~SCXStream();
bool isStereo() const override { return _channels == 2; }
bool endOfData() const override { return _xaStreams[0]->endOfData(); }
int getRate() const override { return _rate; }
int readBuffer(int16 *buffer, const int numSamples) override;
bool rewind() override;
Audio::Timestamp getPos() const;
private:
int _channels;
int _rate;
uint16 _blockSize;
Common::SeekableReadStream *_fileStreams[SCX_MAX_CHANNELS];
Audio::RewindableAudioStream *_xaStreams[SCX_MAX_CHANNELS];
};
/**
* Takes an input stream containing SCX sound data and creates
* a 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
*/
SCXStream *makeSCXStream(
Common::SeekableReadStream *stream,
const Audio::Timestamp *start,
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES);
} // End of namespace Grim
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
/* 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/>.
*
*/
#ifndef GRIM_MSS_H
#define GRIM_MSS_H
#include "audio/mixer.h"
#include "common/str.h"
#include "common/stack.h"
#include "common/mutex.h"
#include "common/hashmap.h"
#include "math/vector3d.h"
namespace Grim {
class SoundTrack;
class SaveGame;
struct MusicEntry {
int _x;
int _y;
int _sync;
int _trim;
int _id;
Common::String _type;
Common::String _name;
Common::String _filename;
};
// Currently this class only implements the exact functions called in iMuse
// from Actor, to allow for splitting that into EMI-sound and iMuse without
// changing iMuse.
class EMISound {
public:
EMISound(int fps);
~EMISound();
bool startVoice(const Common::String &soundName, int volume = static_cast<int>(Audio::Mixer::kMaxChannelVolume), int pan = 64);
bool startSfx(const Common::String &soundName, int volume = static_cast<int>(Audio::Mixer::kMaxChannelVolume), int pan = 64);
bool startSfxFrom(const Common::String &soundName, const Math::Vector3d &pos, int volume = static_cast<int>(Audio::Mixer::kMaxChannelVolume));
bool getSoundStatus(const Common::String &soundName);
void stopSound(const Common::String &soundName);
int32 getPosIn16msTicks(const Common::String &soundName);
void setVolume(const Common::String &soundName, int volume);
void setPan(const Common::String &soundName, int pan); /* pan: 0 .. 127 */
bool loadSfx(const Common::String &soundName, int &id);
void playLoadedSound(int id, bool looping);
void playLoadedSoundFrom(int id, const Math::Vector3d &pos, bool looping);
void setLoadedSoundLooping(int id, bool looping);
void stopLoadedSound(int id);
void freeLoadedSound(int id);
void setLoadedSoundVolume(int id, int volume);
void setLoadedSoundPan(int id, int pan);
void setLoadedSoundPosition(int id, const Math::Vector3d &pos);
bool getLoadedSoundStatus(int id);
int getLoadedSoundVolume(int id);
void setMusicState(int stateId);
void selectMusicSet(int setId);
bool stateHasLooped(int stateId);
bool stateHasEnded(int stateId);
void restoreState(SaveGame *savedState);
void saveState(SaveGame *savedState);
void pushStateToStack();
void popStateFromStack();
void flushStack();
void pause(bool paused);
void flushTracks();
uint32 getMsPos(int stateId);
void updateSoundPositions();
private:
struct StackEntry {
int _state;
SoundTrack *_track;
};
typedef Common::List<SoundTrack *> TrackList;
TrackList _playingTracks;
SoundTrack *_musicTrack;
MusicEntry *_musicTable;
Common::String _musicPrefix;
Common::Stack<StackEntry> _stateStack;
// A mutex to avoid concurrent modification of the sound channels by the engine thread
// and the timer callback, which may run in a different thread.
Common::Mutex _mutex;
typedef Common::HashMap<int, SoundTrack *> TrackMap;
TrackMap _preloadedTrackMap;
int _curMusicState;
int _numMusicStates;
int _callbackFps;
int _curTrackId;
static void timerHandler(void *refConf);
void removeItem(SoundTrack *item);
TrackList::iterator getPlayingTrackByName(const Common::String &name);
void freeChannel(int32 channel);
void initMusicTable();
void callback();
void updateTrack(SoundTrack *track);
void freePlayingSounds();
void freeLoadedSounds();
SoundTrack *initTrack(const Common::String &soundName, Audio::Mixer::SoundType soundType, const Audio::Timestamp *start = nullptr) const;
SoundTrack *restartTrack(SoundTrack *track);
bool startSound(const Common::String &soundName, Audio::Mixer::SoundType soundType, int volume, int pan);
bool startSoundFrom(const Common::String &soundName, Audio::Mixer::SoundType soundType, const Math::Vector3d &pos, int volume);
void saveTrack(SoundTrack *track, SaveGame *savedState);
SoundTrack *restoreTrack(SaveGame *savedState);
MusicEntry *initMusicTableDemo(const Common::String &filename);
void initMusicTableRetail(MusicEntry *table, const Common::String &filename);
};
extern EMISound *g_emiSound;
}
#endif

View File

@@ -0,0 +1,223 @@
/* 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/mutex.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "audio/decoders/mp3.h"
#include "engines/grim/debug.h"
#include "engines/grim/resource.h"
#include "engines/grim/textsplit.h"
#include "engines/grim/emi/sound/mp3track.h"
namespace Grim {
/**
* This is a an extension of Audio::SubLooppingAudioStream that adds a start
* time parameter as well as a getter for the stream position.
*/
class EMISubLoopingAudioStream : public Audio::AudioStream {
public:
EMISubLoopingAudioStream(Audio::SeekableAudioStream *stream, uint loops,
const Audio::Timestamp start,
const Audio::Timestamp loopStart,
const Audio::Timestamp loopEnd,
DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES)
: _parent(stream, disposeAfterUse),
_pos(convertTimeToStreamPos(start, getRate(), isStereo())),
_loopStart(convertTimeToStreamPos(loopStart, getRate(), isStereo())),
_loopEnd(convertTimeToStreamPos(loopEnd, getRate(), isStereo())),
_done(false), _hasLooped(false) {
assert(loopStart < loopEnd);
if (!_parent->seek(_pos))
_done = true;
}
int readBuffer(int16 *buffer, const int numSamples) override {
if (_done)
return 0;
int framesLeft = MIN(_loopEnd.frameDiff(_pos), numSamples);
int framesRead = _parent->readBuffer(buffer, framesLeft);
_pos = _pos.addFrames(framesRead);
if (framesRead < framesLeft && _parent->endOfData()) {
// TODO: Proper error indication.
_done = true;
return framesRead;
}
else if (_pos == _loopEnd) {
if (!_parent->seek(_loopStart)) {
// TODO: Proper error indication.
_done = true;
return framesRead;
}
_pos = _loopStart;
framesLeft = numSamples - framesLeft;
_hasLooped = true;
return framesRead + readBuffer(buffer + framesRead, framesLeft);
}
else {
return framesRead;
}
}
bool hasLooped() const { return _hasLooped; }
bool endOfData() const override { return _done; }
bool isStereo() const override { return _parent->isStereo(); }
int getRate() const override { return _parent->getRate(); }
Audio::Timestamp getPos() const { return _pos; }
private:
Common::DisposablePtr<Audio::SeekableAudioStream> _parent;
Audio::Timestamp _pos;
Audio::Timestamp _loopStart, _loopEnd;
bool _done;
bool _hasLooped;
};
void MP3Track::parseRIFFHeader(Common::SeekableReadStream *data) {
uint32 tag = data->readUint32BE();
if (tag == MKTAG('R','I','F','F')) {
_endFlag = false;
data->seek(18, SEEK_CUR);
_channels = data->readByte();
data->readByte();
_freq = data->readUint32LE();
data->seek(6, SEEK_CUR);
_bits = data->readByte();
data->seek(5, SEEK_CUR);
_regionLength = data->readUint32LE();
_headerSize = 44;
} else {
error("Unknown file header");
}
}
MP3Track::JMMCuePoints MP3Track::parseJMMFile(const Common::String &filename) {
JMMCuePoints cuePoints;
Common::SeekableReadStream *stream = g_resourceloader->openNewStreamFile(filename);
if (stream) {
TextSplitter ts(filename, stream);
float startMs = 0.0f;
float loopStartMs = 0.0f, loopEndMs = 0.0f;
ts.scanString(".start %f", 1, &startMs);
if (ts.checkString(".jump"))
ts.scanString(".jump %f %f", 2, &loopEndMs, &loopStartMs);
// Use microsecond precision for the timestamps.
cuePoints._start = Audio::Timestamp(startMs / 1000, (int)(startMs * 1000) % 1000000, 1000000);
cuePoints._loopStart = Audio::Timestamp(loopStartMs / 1000, (int)(loopStartMs * 1000) % 1000000, 1000000);
cuePoints._loopEnd = Audio::Timestamp(loopEndMs / 1000, (int)(loopEndMs * 1000) % 1000000, 1000000);
}
delete stream;
return cuePoints;
}
MP3Track::MP3Track(Audio::Mixer::SoundType soundType) {
_soundType = soundType;
_headerSize = 0;
_regionLength = 0;
_freq = 0;
_bits = 0,
_channels = 0;
_endFlag = false;
_looping = false;
}
MP3Track::~MP3Track() {
stop();
if (_handle) {
g_system->getMixer()->stopHandle(*_handle);
delete _handle;
}
}
bool MP3Track::openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start) {
Common::SeekableReadStream *file = g_resourceloader->openNewStreamFile(filename);
if (!file) {
Debug::debug(Debug::Sound, "Stream for %s not open", soundName.c_str());
return false;
}
_soundName = soundName;
#ifndef USE_MAD
warning("Cannot open %s, MP3 support not enabled", soundName.c_str());
return true;
#else
parseRIFFHeader(file);
MP3Track::JMMCuePoints cuePoints;
if (soundName.size() > 4) {
cuePoints = parseJMMFile(Common::String(filename.c_str(), filename.size() - 4) + ".jmm");
}
if (start)
cuePoints._start = *start;
Audio::SeekableAudioStream *mp3Stream = Audio::makeMP3Stream(file, DisposeAfterUse::YES);
if (cuePoints._loopEnd <= cuePoints._loopStart) {
_stream = mp3Stream;
mp3Stream->seek(cuePoints._start);
_looping = false;
} else {
_stream = new EMISubLoopingAudioStream(mp3Stream, 0, cuePoints._start, cuePoints._loopStart, cuePoints._loopEnd);
_looping = true;
}
_handle = new Audio::SoundHandle();
return true;
#endif
}
bool MP3Track::hasLooped() {
if (!_stream || !_looping)
return false;
EMISubLoopingAudioStream *las = static_cast<EMISubLoopingAudioStream*>(_stream);
return las->hasLooped();
}
bool MP3Track::isPlaying() {
if (!_handle)
return false;
return g_system->getMixer()->isSoundHandleActive(*_handle);
}
Audio::Timestamp MP3Track::getPos() {
if (!_stream)
return Audio::Timestamp(0);
if (_looping) {
EMISubLoopingAudioStream *slas = static_cast<EMISubLoopingAudioStream*>(_stream);
return slas->getPos();
} else {
return g_system->getMixer()->getSoundElapsedTime(*_handle);
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,64 @@
/* 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/>.
*
*/
#ifndef GRIM_MP3TRACK_H
#define GRIM_MP3TRACK_H
#include "common/str.h"
#include "common/stream.h"
#include "audio/timestamp.h"
#include "engines/grim/emi/sound/track.h"
namespace Audio {
class AudioStream;
class SoundHandle;
}
namespace Grim {
class MP3Track : public SoundTrack {
struct JMMCuePoints {
Audio::Timestamp _start;
Audio::Timestamp _loopStart;
Audio::Timestamp _loopEnd;
};
uint32 _headerSize;
uint32 _regionLength;
uint32 _freq;
char _bits;
char _channels;
bool _endFlag;
bool _looping;
void parseRIFFHeader(Common::SeekableReadStream *data);
JMMCuePoints parseJMMFile(const Common::String &filename);
public:
MP3Track(Audio::Mixer::SoundType soundType);
~MP3Track();
bool openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start = nullptr) override;
bool hasLooped() override;
bool isPlaying() override;
Audio::Timestamp getPos() override;
};
}
#endif

View File

@@ -0,0 +1,95 @@
/* 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/mutex.h"
#include "common/textconsole.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "engines/grim/debug.h"
#include "engines/grim/resource.h"
#include "engines/grim/emi/sound/codecs/scx.h"
#include "engines/grim/emi/sound/scxtrack.h"
namespace Grim {
SCXTrack::SCXTrack(Audio::Mixer::SoundType soundType) {
_disposeAfterPlaying = DisposeAfterUse::NO;
_soundType = soundType;
_looping = false;
}
SCXTrack::~SCXTrack() {
stop();
if (_handle) {
g_system->getMixer()->stopHandle(*_handle);
delete _handle;
}
}
bool SCXTrack::openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start) {
Common::SeekableReadStream *file = g_resourceloader->openNewStreamFile(filename);
if (!file) {
Debug::debug(Debug::Sound, "Stream for %s not open", soundName.c_str());
return false;
}
_soundName = soundName;
Audio::RewindableAudioStream *scxStream = makeSCXStream(file, start, DisposeAfterUse::YES);
_stream = scxStream;
_handle = new Audio::SoundHandle();
return true;
}
bool SCXTrack::isPlaying() {
if (!_handle)
return false;
return g_system->getMixer()->isSoundHandleActive(*_handle);
}
Audio::Timestamp SCXTrack::getPos() {
if (!_stream || _looping)
return Audio::Timestamp(0);
return dynamic_cast<SCXStream*>(_stream)->getPos();
}
bool SCXTrack::play() {
if (_stream) {
Audio::RewindableAudioStream *stream = dynamic_cast<Audio::RewindableAudioStream *>(_stream);
if (!_looping) {
stream->rewind();
}
return SoundTrack::play();
}
return false;
}
void SCXTrack::setLooping(bool looping) {
if (_looping == looping)
return;
_looping = looping;
if (looping && _stream) {
_stream = Audio::makeLoopingAudioStream(dynamic_cast<Audio::RewindableAudioStream *>(_stream), 0);
}
}
} // end of namespace Grim

View File

@@ -0,0 +1,52 @@
/* 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/>.
*
*/
#ifndef GRIM_SCXTRACK_H
#define GRIM_SCXTRACK_H
#include "common/str.h"
#include "common/stream.h"
#include "engines/grim/emi/sound/track.h"
namespace Audio {
class AudioStream;
class SoundHandle;
}
namespace Grim {
class SCXTrack : public SoundTrack {
public:
SCXTrack(Audio::Mixer::SoundType soundType);
~SCXTrack();
bool openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start = nullptr) override;
bool isPlaying() override;
Audio::Timestamp getPos() override;
bool play() override;
void setLooping(bool looping) override;
private:
bool _looping;
};
}
#endif

View File

@@ -0,0 +1,154 @@
/* 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/mutex.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "audio/mixer.h"
#include "audio/audiostream.h"
#include "engines/grim/savegame.h"
#include "engines/grim/emi/sound/track.h"
#include "engines/grim/grim.h"
#include "engines/grim/set.h"
namespace Grim {
SoundTrack::SoundTrack() {
_stream = nullptr;
_handle = nullptr;
_paused = false;
_positioned = false;
_balance = 0;
_volume = Audio::Mixer::kMaxChannelVolume;
_disposeAfterPlaying = DisposeAfterUse::YES;
_sync = 0;
_fadeMode = FadeNone;
_fade = 1.0f;
_attenuation = 1.0f;
// Initialize to a plain sound for now
_soundType = Audio::Mixer::kPlainSoundType;
}
SoundTrack::~SoundTrack() {
if (_stream && (_disposeAfterPlaying == DisposeAfterUse::NO || !_handle))
delete _stream;
}
Common::String SoundTrack::getSoundName() {
return _soundName;
}
void SoundTrack::setSoundName(const Common::String &name) {
_soundName = name;
}
void SoundTrack::setVolume(int volume) {
_volume = MIN(volume, static_cast<int>(Audio::Mixer::kMaxChannelVolume));
if (_handle) {
g_system->getMixer()->setChannelVolume(*_handle, (byte)getEffectiveVolume());
}
}
void SoundTrack::setPosition(bool positioned, const Math::Vector3d &pos) {
_positioned = positioned;
_pos = pos;
updatePosition();
}
void SoundTrack::updatePosition() {
if (!_positioned)
return;
Set *set = g_grim->getCurrSet();
Set::Setup *setup = set->getCurrSetup();
Math::Vector3d cameraPos = setup->_pos;
Math::Vector3d vector = _pos - cameraPos;
float distance = vector.getMagnitude();
if (_volume == 0) {
_attenuation = 0.0f;
} else {
_attenuation = MAX(0.0f, 1.0f - distance / (_volume * 100.0f / Audio::Mixer::kMaxChannelVolume));
}
Math::Matrix4 worldRot = setup->_rot;
Math::Vector3d relPos = (_pos - setup->_pos);
Math::Vector3d p(relPos);
p = p * worldRot.getRotation();
float angle = atan2(p.x(), p.z());
float pan = sin(angle);
_balance = (int)(pan * 127.0f);
if (_handle) {
g_system->getMixer()->setChannelBalance(*_handle, _balance);
g_system->getMixer()->setChannelVolume(*_handle, (byte)getEffectiveVolume());
}
}
void SoundTrack::setBalance(int balance) {
if (_positioned)
return;
_balance = balance;
if (_handle) {
g_system->getMixer()->setChannelBalance(*_handle, _balance);
}
}
bool SoundTrack::play() {
if (_stream) {
if (isPlaying()) {
warning("sound: %s already playing, don't start again!", _soundName.c_str());
return true;
}
// If _disposeAfterPlaying is NO, the destructor will take care of the stream.
g_system->getMixer()->playStream(_soundType, _handle, _stream, -1, (byte)getEffectiveVolume(), _balance, _disposeAfterPlaying);
return true;
}
return false;
}
void SoundTrack::pause() {
_paused = !_paused;
if (_stream) {
g_system->getMixer()->pauseHandle(*_handle, _paused);
}
}
void SoundTrack::stop() {
if (_handle)
g_system->getMixer()->stopHandle(*_handle);
}
void SoundTrack::setFade(float fade) {
_fade = fade;
if (_handle) {
g_system->getMixer()->setChannelVolume(*_handle, (byte)getEffectiveVolume());
}
}
int SoundTrack::getEffectiveVolume() {
return _volume * _attenuation * _fade;
}
} // end of namespace Grim

View File

@@ -0,0 +1,106 @@
/* 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/>.
*
*/
#ifndef GRIM_SOUNDTRACK_H
#define GRIM_SOUNDTRACK_H
#include "audio/mixer.h"
#include "audio/timestamp.h"
#include "math/vector3d.h"
namespace Common {
class String;
}
namespace Audio {
class AudioStream;
class SoundHandle;
}
namespace Grim {
class SaveGame;
/**
* @class Super-class for the different codecs used in EMI
*/
class SoundTrack {
public:
enum FadeMode {
FadeNone,
FadeIn,
FadeOut
};
protected:
Common::String _soundName;
Audio::AudioStream *_stream;
Audio::SoundHandle *_handle;
Audio::Mixer::SoundType _soundType;
DisposeAfterUse::Flag _disposeAfterPlaying;
bool _paused;
bool _positioned;
Math::Vector3d _pos;
FadeMode _fadeMode;
float _fade;
float _attenuation;
int _balance;
int _volume;
int _sync;
public:
SoundTrack();
virtual ~SoundTrack();
virtual bool openSound(const Common::String &filename, const Common::String &voiceName, const Audio::Timestamp *start = nullptr) = 0;
virtual bool isPlaying() = 0;
virtual bool play();
virtual void pause();
virtual void stop();
void fadeIn() { _fadeMode = FadeIn; }
void fadeOut() { _fadeMode = FadeOut; }
void setFadeMode(FadeMode fadeMode) { _fadeMode = fadeMode; }
void setFade(float fade);
float getFade() const { return _fade; }
FadeMode getFadeMode() const { return _fadeMode; }
void setBalance(int balance);
void setVolume(int volume);
void setPosition(bool positioned, const Math::Vector3d &pos = Math::Vector3d());
void updatePosition();
void setSync(int sync) { _sync = sync; }
int getEffectiveVolume();
int getVolume() const { return _volume; }
int getBalance() const { return _balance; }
int getSync() const { return _sync; }
virtual Audio::Timestamp getPos() = 0;
Common::String getSoundName();
void setSoundName(const Common::String &name);
virtual bool hasLooped() { return false; }
virtual void setLooping(bool looping) { }
virtual bool isLooping() const { return false; }
bool isPaused() const { return _paused; }
bool isPositioned() const { return _positioned; }
Math::Vector3d getWorldPos() const { return _pos; }
Audio::Mixer::SoundType getSoundType() const { return _soundType; }
};
}
#endif

View File

@@ -0,0 +1,260 @@
/* 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/stream.h"
#include "common/mutex.h"
#include "common/textconsole.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"
#include "engines/grim/debug.h"
#include "engines/grim/resource.h"
#include "engines/grim/imuse/imuse_mcmp_mgr.h"
#include "engines/grim/emi/sound/vimatrack.h"
namespace Grim {
struct Region {
int32 offset; // offset of region
int32 length; // length of region
};
struct SoundDesc {
uint16 freq; // frequency
byte channels; // stereo or mono
byte bits; // 8, 12, 16
int numRegions; // number of Regions
Region *region;
bool endFlag;
bool inUse;
char name[32];
McmpMgr *mcmpMgr;
int type;
int volGroupId;
bool mcmpData;
uint32 headerSize;
Common::SeekableReadStream *inStream;
};
bool VimaTrack::isPlaying() {
// FIXME: Actually clean up the data better
// (we don't currently handle the case where it isn't asked for through isPlaying, or deleted explicitly).
if (!_handle)
return false;
if (g_system->getMixer()->isSoundHandleActive(*_handle)) {
if (_stream->endOfData()) {
g_system->getMixer()->stopHandle(*_handle);
return false;
} else {
return true;
}
}
return false;
}
bool VimaTrack::openSound(const Common::String &filename, const Common::String &voiceName, const Audio::Timestamp *start) {
Common::SeekableReadStream *file = g_resourceloader->openNewStreamFile(filename);
if (!file) {
Debug::debug(Debug::Sound, "Stream for %s not open", voiceName.c_str());
return false;
}
_soundName = voiceName;
_mcmp = new McmpMgr();
_desc = new SoundDesc();
_desc->inStream = file;
_desc->mcmpData = true;
_desc->mcmpMgr = _mcmp;
int headerSize = 0;
if (_mcmp->openSound(voiceName.c_str(), file, headerSize)) {
parseSoundHeader(_desc, headerSize);
_stream = Audio::makeQueuingAudioStream(_desc->freq, (false));
playTrack(start);
return true;
} else {
return false;
}
}
void VimaTrack::parseSoundHeader(SoundDesc *sound, int &headerSize) {
Common::SeekableReadStream *data = sound->inStream;
uint32 tag = data->readUint32BE();
if (tag == MKTAG('R','I','F','F')) {
sound->endFlag = false;
sound->region = new Region[1];
sound->numRegions = 1;
sound->region[0].offset = 0;
data->seek(18, SEEK_CUR);
sound->channels = data->readByte();
data->readByte();
sound->freq = data->readUint32LE();
data->seek(6, SEEK_CUR);
sound->bits = data->readByte();
data->seek(5, SEEK_CUR);
sound->region[0].length = data->readUint32LE();
headerSize = 44;
} else {
assert(tag != MKTAG('i','M','U','S'));
error("VimaTrack::parseSoundHeader() Unknown sound format");
}
}
int32 VimaTrack::getDataFromRegion(SoundDesc *sound, int region, byte **buf, int32 offset, int32 size, int32 *flags) {
//assert(checkForProperHandle(sound));
assert(buf && offset >= 0 && size >= 0);
assert(region >= 0 && region < sound->numRegions);
int32 region_offset = sound->region[region].offset;
int32 region_length = sound->region[region].length;
if (offset + size > region_length) {
size = region_length - offset;
sound->endFlag = true;
} else {
sound->endFlag = false;
}
if (sound->mcmpData) {
size = sound->mcmpMgr->decompressSample(region_offset + offset, size, buf);
*flags |= Audio::FLAG_LITTLE_ENDIAN;
} else {
*buf = new byte[size];
sound->inStream->seek(region_offset + offset + sound->headerSize, SEEK_SET);
sound->inStream->read(*buf, size);
*flags &= ~Audio::FLAG_LITTLE_ENDIAN;
}
return size;
}
void VimaTrack::playTrack(const Audio::Timestamp *start) {
//Common::StackLock lock(_mutex);
if (!_stream) {
error("Stream not loaded");
}
byte *data = nullptr;
int32 result = 0;
int32 curRegion = -1;
int32 regionOffset = 0;
int32 mixerFlags = Audio::FLAG_16BITS;
curRegion++;
int channels = _desc->channels;
//int32 mixer_size = track->feedSize / _callbackFps;
int32 mixer_size = _desc->freq * channels * 2;
if (start) {
regionOffset = (start->msecs() * mixer_size) / 1000;
regionOffset = (regionOffset / 2) * 2; // Ensure that the offset is divisible by 2.
while (regionOffset > _desc->region[curRegion].length) {
regionOffset -= _desc->region[curRegion].length;
++curRegion;
}
if (curRegion > _desc->numRegions - 1)
return;
}
if (_stream->endOfData()) { // FIXME: Currently we just allocate a bunch here, try to find the correct size instead.
mixer_size *= 8;
}
if (channels == 1)
mixer_size &= ~1;
if (channels == 2)
mixer_size &= ~3;
if (mixer_size == 0)
return;
do {
result = getDataFromRegion(_desc, curRegion, &data, regionOffset, mixer_size, &mixerFlags);
if (channels == 1) {
result &= ~1;
}
if (channels == 2) {
result &= ~3;
}
if (result > mixer_size)
result = mixer_size;
if (g_system->getMixer()->isReady()) {
((Audio::QueuingAudioStream *)_stream)->queueBuffer(data, result, DisposeAfterUse::YES, mixerFlags);
regionOffset += result;
} else
delete[] data;
if (curRegion >= 0 && curRegion < _desc->numRegions - 1) {
curRegion++;
regionOffset = 0;
if (!_stream) {
return;
}
}
mixer_size -= result;
assert(mixer_size >= 0);
} while (mixer_size && !_desc->endFlag);
if (g_system->getMixer()->isReady()) {
//g_system->getMixer()->setChannelVolume(track->handle, track->getVol());
//g_system->getMixer()->setChannelBalance(track->handle, track->getPan());
}
}
Audio::Timestamp VimaTrack::getPos() {
// FIXME: Return actual stream position.
return g_system->getMixer()->getSoundElapsedTime(*_handle);
}
VimaTrack::VimaTrack() {
_soundType = Audio::Mixer::kSpeechSoundType;
_handle = new Audio::SoundHandle();
_file = nullptr;
_mcmp = nullptr;
_desc = nullptr;
}
VimaTrack::~VimaTrack() {
stop();
delete _mcmp;
if (_desc) {
delete[] _desc->region;
delete _desc->inStream;
}
if (_handle) {
g_system->getMixer()->stopHandle(*_handle);
delete _handle;
}
delete _desc;
}
} // end of namespace Grim

View File

@@ -0,0 +1,57 @@
/* 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/>.
*
*/
#ifndef GRIM_VIMATRACK_H
#define GRIM_VIMATRACK_H
#include "common/str.h"
#include "engines/grim/emi/sound/track.h"
namespace Grim {
struct SoundDesc;
class McmpMgr;
/**
* @class Vima-implementation for the EMI-sound system
* Vima is used for voices in the PC version of EMI, a
* similar implementation for SCX will be required for PS2-support.
*/
class VimaTrack : public SoundTrack {
Common::SeekableReadStream *_file;
void parseSoundHeader(SoundDesc *sound, int &headerSize);
int32 getDataFromRegion(SoundDesc *sound, int region, byte **buf, int32 offset, int32 size, int32 *flags);
public:
VimaTrack();
virtual ~VimaTrack();
bool isPlaying() override;
bool openSound(const Common::String &filename, const Common::String &soundName, const Audio::Timestamp *start = nullptr) override;
void playTrack(const Audio::Timestamp *start);
Audio::Timestamp getPos() override;
SoundDesc *_desc;
McmpMgr *_mcmp;
};
}
#endif