Initial commit
This commit is contained in:
101
engines/grim/emi/sound/aifftrack.cpp
Normal file
101
engines/grim/emi/sound/aifftrack.cpp
Normal 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
|
||||
53
engines/grim/emi/sound/aifftrack.h
Normal file
53
engines/grim/emi/sound/aifftrack.h
Normal 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
|
||||
207
engines/grim/emi/sound/codecs/scx.cpp
Normal file
207
engines/grim/emi/sound/codecs/scx.cpp
Normal 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
|
||||
71
engines/grim/emi/sound/codecs/scx.h
Normal file
71
engines/grim/emi/sound/codecs/scx.h
Normal 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
|
||||
1094
engines/grim/emi/sound/emisound.cpp
Normal file
1094
engines/grim/emi/sound/emisound.cpp
Normal file
File diff suppressed because it is too large
Load Diff
146
engines/grim/emi/sound/emisound.h
Normal file
146
engines/grim/emi/sound/emisound.h
Normal 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
|
||||
223
engines/grim/emi/sound/mp3track.cpp
Normal file
223
engines/grim/emi/sound/mp3track.cpp
Normal 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
|
||||
64
engines/grim/emi/sound/mp3track.h
Normal file
64
engines/grim/emi/sound/mp3track.h
Normal 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
|
||||
95
engines/grim/emi/sound/scxtrack.cpp
Normal file
95
engines/grim/emi/sound/scxtrack.cpp
Normal 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
|
||||
52
engines/grim/emi/sound/scxtrack.h
Normal file
52
engines/grim/emi/sound/scxtrack.h
Normal 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
|
||||
154
engines/grim/emi/sound/track.cpp
Normal file
154
engines/grim/emi/sound/track.cpp
Normal 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
|
||||
106
engines/grim/emi/sound/track.h
Normal file
106
engines/grim/emi/sound/track.h
Normal 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
|
||||
260
engines/grim/emi/sound/vimatrack.cpp
Normal file
260
engines/grim/emi/sound/vimatrack.cpp
Normal 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
|
||||
57
engines/grim/emi/sound/vimatrack.h
Normal file
57
engines/grim/emi/sound/vimatrack.h
Normal 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
|
||||
Reference in New Issue
Block a user