aboutsummaryrefslogtreecommitdiff
path: root/sword2
diff options
context:
space:
mode:
Diffstat (limited to 'sword2')
-rw-r--r--sword2/driver/d_sound.cpp893
-rw-r--r--sword2/driver/d_sound.h43
-rw-r--r--sword2/function.cpp13
3 files changed, 478 insertions, 471 deletions
diff --git a/sword2/driver/d_sound.cpp b/sword2/driver/d_sound.cpp
index e4446359ec..c8bbd03b5f 100644
--- a/sword2/driver/d_sound.cpp
+++ b/sword2/driver/d_sound.cpp
@@ -36,18 +36,115 @@
namespace Sword2 {
+static AudioStream *makeCLUStream(File *fp, int size);
+
static File fpMus;
static void premix_proc(void *param, int16 *data, uint len) {
((Sound *) param)->streamMusic(data, len);
}
+static AudioStream *getAudioStream(File *fp, const char *base, int cd, uint32 id, uint32 *numSamples) {
+ struct {
+ const char *ext;
+ int mode;
+ } file_types[] = {
+#ifdef USE_MAD
+ { "cl3", kMP3Mode },
+#endif
+#ifdef USE_VORBIS
+ { "clg", kVorbisMode },
+#endif
+#ifdef USE_FLAC
+ { "clf", kFlacMode },
+#endif
+ { "clu", kCLUMode }
+ };
+
+ int soundMode = -1;
+ char filename[20];
+
+ for (int i = 0; i < ARRAYSIZE(file_types); i++) {
+ File f;
+
+ sprintf(filename, "%s%d.%s", base, cd, file_types[i].ext);
+ if (f.open(filename)) {
+ soundMode = file_types[i].mode;
+ break;
+ }
+
+ sprintf(filename, "%s.%s", base, file_types[i].ext);
+ if (f.open(filename)) {
+ soundMode = file_types[i].mode;
+ break;
+ }
+ }
+
+ if (soundMode == 0)
+ return NULL;
+
+ // The assumption here is that a sound file is closed when the sound
+ // finishes, and we never play sounds from two different files at the
+ // same time. Thus, if the file is already open it's the correct file,
+ // and the loop above was just needed to figure out the compression.
+ //
+ // This is to avoid having two file handles open to the same file at
+ // the same time. There was some speculation that some of our target
+ // systems may have trouble with that.
+
+ if (!fp->isOpen())
+ fp->open(filename);
+
+ if (!fp->isOpen())
+ return NULL;
+
+ fp->seek((id + 1) * ((soundMode == kCLUMode) ? 8 : 12), SEEK_SET);
+
+ uint32 pos = fp->readUint32LE();
+ uint32 len = fp->readUint32LE();
+ uint32 enc_len;
+
+ if (numSamples)
+ *numSamples = len;
+
+ if (soundMode != kCLUMode)
+ enc_len = fp->readUint32LE();
+ else
+ enc_len = len + 1;
+
+ if (!pos || !len) {
+ fp->close();
+ return NULL;
+ }
+
+ fp->seek(pos, SEEK_SET);
+
+ switch (soundMode) {
+ case kCLUMode:
+ return makeCLUStream(fp, enc_len);
+#ifdef USE_MAD
+ case kMP3Mode:
+ return makeMP3Stream(fp, enc_len);
+#endif
+#ifdef USE_VORBIS
+ case kVorbisMode:
+ return makeVorbisStream(fp, enc_len);
+#endif
+#ifdef USE_FLAC
+ case kFlacMode:
+ return makeFlacStream(fp, enc_len);
+#endif
+ default:
+ return NULL;
+ }
+}
+
+#define BUFFER_SIZE 4096
+
// ----------------------------------------------------------------------------
// Custom AudioStream class to handle Broken Sword II's audio compression.
// ----------------------------------------------------------------------------
-#define BUFFER_SIZE 4096
-
#define GetCompressedShift(n) ((n) >> 4)
#define GetCompressedSign(n) (((n) >> 3) & 1)
#define GetCompressedAmplitude(n) ((n) & 7)
@@ -55,6 +152,8 @@ static void premix_proc(void *param, int16 *data, uint len) {
class CLUInputStream : public AudioStream {
private:
File *_file;
+ bool _firstTime;
+ uint32 _file_pos;
uint32 _end_pos;
int16 _outbuf[BUFFER_SIZE];
byte _inbuf[BUFFER_SIZE];
@@ -66,7 +165,7 @@ private:
void refill();
inline bool eosIntern() const;
public:
- CLUInputStream(File *file, int duration);
+ CLUInputStream(File *file, int size);
~CLUInputStream();
int readBuffer(int16 *buffer, const int numSamples);
@@ -78,7 +177,7 @@ public:
};
CLUInputStream::CLUInputStream(File *file, int size)
- : _file(file), _bufferEnd(_outbuf + BUFFER_SIZE) {
+ : _file(file), _firstTime(true), _bufferEnd(_outbuf + BUFFER_SIZE) {
_file->incRef();
@@ -86,7 +185,7 @@ CLUInputStream::CLUInputStream(File *file, int size)
_end_pos = file->pos() + size;
// Read in initial data
- _prev = _file->readUint16LE();
+ _file_pos = _file->pos();
refill();
}
@@ -126,21 +225,35 @@ int CLUInputStream::readBuffer(int16 *buffer, const int numSamples) {
void CLUInputStream::refill() {
byte *in = _inbuf;
int16 *out = _outbuf;
+
+ _file->seek(_file_pos, SEEK_SET);
+
uint len_left = _file->read(in, MIN((uint32) BUFFER_SIZE, _end_pos - _file->pos()));
+ _file_pos = _file->pos();
+
while (len_left > 0) {
- uint16 delta = GetCompressedAmplitude(*in) << GetCompressedShift(*in);
uint16 sample;
- if (GetCompressedSign(*in))
- sample = _prev - delta;
- else
- sample = _prev + delta;
+ if (_firstTime) {
+ _firstTime = false;
+ _prev = READ_LE_UINT16(in);
+ sample = _prev;
+ len_left -= 2;
+ in += 2;
+ } else {
+ uint16 delta = GetCompressedAmplitude(*in) << GetCompressedShift(*in);
+ if (GetCompressedSign(*in))
+ sample = _prev - delta;
+ else
+ sample = _prev + delta;
+
+ _prev = sample;
+ len_left--;
+ in++;
+ }
- _prev = sample;
*out++ = sample;
- *in++;
- len_left--;
}
_pos = _outbuf;
@@ -148,154 +261,261 @@ void CLUInputStream::refill() {
}
AudioStream *makeCLUStream(File *file, int size) {
- assert(size >= 2);
return new CLUInputStream(file, size);
}
// ----------------------------------------------------------------------------
-// Main sound class
+// Another custom AudioStream class, to wrap around the various AudioStream
+// classes used for music decompression, and to add looping, fading, etc.
// ----------------------------------------------------------------------------
-Sound::Sound(Sword2Engine *vm) {
- _vm = vm;
- _mutex = _vm->_system->createMutex();
+// The length of a fade-in/out, in milliseconds.
+#define FADE_LENGTH 3000
- memset(_fx, 0, sizeof(_fx));
+class MusicInputStream : public AudioStream {
+private:
+ int _cd;
+ uint32 _musicId;
+ AudioStream *_decoder;
+ int16 _buffer[BUFFER_SIZE];
+ const int16 *_bufferEnd;
+ const int16 *_pos;
+ bool _remove;
+ uint32 _numSamples;
+ uint32 _samplesLeft;
+ bool _looping;
+ int32 _fading;
+ int32 _fadeSamples;
+ bool _paused;
- _soundOn = true;
+ void refill();
+ inline bool eosIntern() const;
+public:
+ MusicInputStream(int cd, uint32 musicId, bool looping);
+ ~MusicInputStream();
- _speechStatus = false;
- _speechPaused = false;
- _speechMuted = false;
- _speechVol = 14;
+ int readBuffer(int16 *buffer, const int numSamples);
- _fxPaused = false;
- _fxMuted = false;
- _fxVol = 14;
+ bool endOfData() const { return eosIntern(); }
+ bool isStereo() const { return _decoder->isStereo(); }
+ int getRate() const { return _decoder->getRate(); }
- _musicVol = 16;
- _musicMuted = false;
+ void fadeUp();
+ void fadeDown();
- for (int i = 0; i < MAXMUS; i++)
- _music[i]._converter = makeRateConverter(_music[i].getRate(), _vm->_mixer->getOutputRate(), _music[i].isStereo(), false);
+ bool isReady() { return _decoder != NULL; }
+ int32 isFading() { return _fading; }
- _vm->_mixer->setupPremix(premix_proc, this);
-}
+ bool readyToRemove();
+ int32 getTimeRemaining();
+};
-Sound::~Sound() {
- int i;
+MusicInputStream::MusicInputStream(int cd, uint32 musicId, bool looping)
+ : _cd(cd), _musicId(musicId), _bufferEnd(_buffer + BUFFER_SIZE),
+ _remove(false), _looping(looping), _fading(0) {
+ _decoder = getAudioStream(&fpMus, "music", _cd, _musicId, &_numSamples);
+ if (_decoder) {
+ _samplesLeft = _numSamples;
+ _fadeSamples = (getRate() * FADE_LENGTH) / 1000;
+ fadeUp();
- _vm->_mixer->setupPremix(0, 0);
+ // Read in initial data
+ refill();
+ }
+}
- for (i = 0; i < MAXMUS; i++)
- delete _music[i]._converter;
+MusicInputStream::~MusicInputStream() {
+ delete _decoder;
+};
- for (i = 0; i < MAXFX; i++)
- stopFxHandle(i);
+inline bool MusicInputStream::eosIntern() const {
+ if (_looping)
+ return false;
+ return _pos >= _bufferEnd;
+}
- if (_mutex)
- _vm->_system->deleteMutex(_mutex);
+int MusicInputStream::readBuffer(int16 *buffer, const int numSamples) {
+ if (!_decoder)
+ return 0;
+
+ int samples = 0;
+ while (samples < numSamples && !eosIntern()) {
+ const int len = MIN(numSamples - samples, (int) (_bufferEnd - _pos));
+ memcpy(buffer, _pos, len * 2);
+ buffer += len;
+ _pos += len;
+ samples += len;
+ if (_pos >= _bufferEnd) {
+ refill();
+ }
+ }
+ return samples;
}
-// FIXME: Merge openSoundFile() and getAudioStream() once compressed music has
-// been implemented?
+void MusicInputStream::refill() {
+ int16 *buf = _buffer;
+ uint32 numSamples = 0;
+ uint32 len_left;
+ bool endFade = false;
-int Sound::openSoundFile(File *fp, const char *base) {
- struct {
- const char *ext;
- int mode;
- } file_types[] = {
-#ifdef USE_MAD
- { "cl3", kMP3Mode },
-#endif
-#ifdef USE_VORBIS
- { "clg", kVorbisMode },
-#endif
-#ifdef USE_FLAC
- { "clf", kFlacMode },
-#endif
- { "clu", kCLUMode }
- };
+ len_left = BUFFER_SIZE;
- char filename[20];
- int cd = _vm->_resman->whichCd();
- int i;
+ if (_fading > 0 && (uint32) _fading < len_left)
+ len_left = _fading;
- for (i = 0; i < ARRAYSIZE(file_types); i++) {
- File f;
+ if (_samplesLeft < len_left)
+ len_left = _samplesLeft;
- sprintf(filename, "%s%d.%s", base, cd, file_types[i].ext);
- if (f.open(filename))
- break;
+ if (!_looping) {
+ // None-looping music is faded out at the end. If this fade
+ // out would have started somewhere within the len_left samples
+ // to read, we only read up to that point. This way, we can
+ // treat this fade as any other.
- sprintf(filename, "%s.%s", base, file_types[i].ext);
- if (f.open(filename))
- break;
+ if (!_fading) {
+ uint32 currentlyAt = _numSamples - _samplesLeft;
+ uint32 fadeOutAt = _numSamples - _fadeSamples;
+ uint32 readTo = currentlyAt + len_left;
+
+ if (fadeOutAt == currentlyAt)
+ fadeDown();
+ else if (fadeOutAt > currentlyAt && fadeOutAt <= readTo) {
+ len_left = fadeOutAt - currentlyAt;
+ endFade = true;
+ }
+ }
}
- if (i == ARRAYSIZE(file_types))
- return 0;
+ int desired = len_left - numSamples;
+ int len = _decoder->readBuffer(buf, desired);
- // The assumption here is that a sound file is closed when the sound
- // finishes, and we never play sounds from two different files at the
- // same time. Thus, if the file is already open it's the correct file,
- // and the loop above was just needed to figure out the compression.
- //
- // This is to avoid having two file handles open to the same file at
- // the same time. There was some speculation that some of our target
- // systems may have trouble with that.
+ // Shouldn't happen, but if it does it could cause an infinite loop.
+ // Of course there were bugs that caused it to happen several times
+ // during development. :-)
- if (!fp->isOpen())
- fp->open(filename);
+ if (len < desired) {
+ warning("Expected %d samples, but got none", desired);
+ if (!len)
+ return;
+ }
+
+ buf += len;
+ numSamples += len;
+ len_left -= len;
+ _samplesLeft -= len;
+
+ int16 *ptr;
+
+ if (_fading > 0) {
+ // Fade down
+ for (ptr = _buffer; ptr < buf; ptr++) {
+ if (--_fading <= 0) {
+ _looping = false;
+ _remove = true;
+ break;
+ }
+ *ptr = (*ptr * _fading) / _fadeSamples;
+ }
+ } else if (_fading < 0) {
+ // Fade up
+ for (ptr = _buffer; ptr < buf; ptr++) {
+ _fading--;
+ *ptr = -(*ptr * _fading) / _fadeSamples;
+ if (_fading <= -_fadeSamples) {
+ _fading = 0;
+ break;
+ }
+ }
+ }
- return file_types[i].mode;
+ if (endFade)
+ fadeDown();
+
+ if (!_samplesLeft) {
+ if (_looping) {
+ delete _decoder;
+ _decoder = getAudioStream(&fpMus, "music", _cd, _musicId, &_numSamples);
+ _samplesLeft = _numSamples;
+ } else
+ _remove = true;
+ }
+
+ _pos = _buffer;
+ _bufferEnd = buf;
}
-AudioStream *Sound::getAudioStream(File *fp, const char *base, uint32 id, uint32 *numSamples) {
- int soundMode = openSoundFile(fp, base);
+void MusicInputStream::fadeUp() {
+ if (_fading > 0)
+ _fading = -_fading;
+ else if (_fading == 0)
+ _fading = -1;
+}
- if (!soundMode)
- return NULL;
+void MusicInputStream::fadeDown() {
+ if (_fading < 0)
+ _fading = -_fading;
+ else if (_fading == 0)
+ _fading = _fadeSamples;
+}
- fp->seek((id + 1) * ((soundMode == kCLUMode) ? 8 : 12), SEEK_SET);
+bool MusicInputStream::readyToRemove() {
+ return _remove;
+}
- uint32 pos = fp->readUint32LE();
- uint32 len = fp->readUint32LE();
- uint32 enc_len;
+int32 MusicInputStream::getTimeRemaining() {
+ // This is far from exact, but it doesn't have to be.
+ return (_samplesLeft + BUFFER_SIZE) / getRate();
+}
- if (numSamples)
- *numSamples = len;
+// ----------------------------------------------------------------------------
+// Main sound class
+// ----------------------------------------------------------------------------
- if (soundMode != kCLUMode)
- enc_len = fp->readUint32LE();
- else
- enc_len = len + 1;
+Sound::Sound(Sword2Engine *vm) {
+ _vm = vm;
+ _mutex = _vm->_system->createMutex();
- if (!pos || !len) {
- fp->close();
- return NULL;
+ memset(_fx, 0, sizeof(_fx));
+
+ _soundOn = true;
+
+ _speechPaused = false;
+ _speechMuted = false;
+ _speechVol = 14;
+
+ _fxPaused = false;
+ _fxMuted = false;
+ _fxVol = 14;
+
+ _musicVol = 16;
+ _musicPaused = false;
+ _musicMuted = false;
+
+ for (int i = 0; i < MAXMUS; i++) {
+ _music[i] = NULL;
+ _converter[i] = NULL;
}
- fp->seek(pos, SEEK_SET);
+ _vm->_mixer->setupPremix(premix_proc, this);
+}
- switch (soundMode) {
- case kCLUMode:
- return makeCLUStream(fp, enc_len);
-#ifdef USE_MAD
- case kMP3Mode:
- return makeMP3Stream(fp, enc_len);
-#endif
-#ifdef USE_VORBIS
- case kVorbisMode:
- return makeVorbisStream(fp, enc_len);
-#endif
-#ifdef USE_FLAC
- case kFlacMode:
- return makeFlacStream(fp, enc_len);
-#endif
- default:
- return NULL;
+Sound::~Sound() {
+ int i;
+
+ _vm->_mixer->setupPremix(0, 0);
+
+ for (i = 0; i < MAXMUS; i++) {
+ delete _music[i];
+ delete _converter[i];
}
+
+ for (i = 0; i < MAXFX; i++)
+ stopFxHandle(i);
+
+ stopSpeech();
+
+ if (_mutex)
+ _vm->_system->deleteMutex(_mutex);
}
void Sound::streamMusic(int16 *data, uint len) {
@@ -305,16 +525,24 @@ void Sound::streamMusic(int16 *data, uint len) {
return;
for (int i = 0; i < MAXMUS; i++) {
- if (!_music[i]._streaming || _music[i]._paused)
- continue;
-
- st_volume_t volume = _musicMuted ? 0 : _musicVolTable[_musicVol];
+ if (_music[i] && _music[i]->readyToRemove()) {
+ delete _music[i];
+ delete _converter[i];
+ _music[i] = NULL;
+ _converter[i] = NULL;
+ }
+ }
- fpMus.seek(_music[i]._filePos, SEEK_SET);
- _music[i]._converter->flow(_music[i], data, len, volume, volume);
+ if (!_musicPaused) {
+ for (int i = 0; i < MAXMUS; i++) {
+ if (_music[i]) {
+ st_volume_t volume = _musicMuted ? 0 : _musicVolTable[_musicVol];
+ _converter[i]->flow(*_music[i], data, len, volume, volume);
+ }
+ }
}
- if (!_music[0]._streaming && !_music[1]._streaming && fpMus.isOpen())
+ if (!_music[0] && !_music[1] && fpMus.isOpen())
fpMus.close();
}
@@ -322,8 +550,6 @@ void Sound::streamMusic(int16 *data, uint len) {
* This function creates the pan table.
*/
-// FIXME: Could we use the FLAG_REVERSE_STEREO mixer flag instead?
-
void Sound::buildPanTable(bool reverse) {
int i;
@@ -344,166 +570,11 @@ void Sound::buildPanTable(bool reverse) {
// MUSIC
// ----------------------------------------------------------------------------
-// All music is encoded at 22050 Hz so this means fading takes 3 seconds.
-#define FADE_SAMPLES 66150
-
int32 Sound::_musicVolTable[17] = {
0, 15, 31, 47, 63, 79, 95, 111, 127,
143, 159, 175, 191, 207, 223, 239, 255
};
-void MusicHandle::fadeDown(void) {
- if (_streaming) {
- if (_fading < 0)
- _fading = -_fading;
- else if (_fading == 0)
- _fading = FADE_SAMPLES;
- }
-}
-
-void MusicHandle::fadeUp(void) {
- if (_streaming) {
- if (_fading > 0)
- _fading = -_fading;
- else if (_fading == 0)
- _fading = -1;
- }
-}
-
-int32 MusicHandle::play(uint32 musicId, bool looping) {
- fpMus.seek((musicId + 1) * 8, SEEK_SET);
- _fileStart = fpMus.readUint32LE();
-
- uint32 len = fpMus.readUint32LE();
-
- if (!_fileStart || !len)
- return RDERR_INVALIDID;
-
- _fileEnd = _fileStart + len;
- _filePos = _fileStart;
- _streaming = true;
- _firstTime = true;
- _looping = looping;
- fadeUp();
- return RD_OK;
-}
-
-void MusicHandle::stop(void) {
- _streaming = false;
- _looping = false;
- _fading = 0;
-}
-
-int MusicHandle::readBuffer(int16 *buffer, const int numSamples) {
- assert(numSamples > 0);
- int samples;
-
- // Assume the file handle has been correctly positioned already.
-
- for (samples = 0; samples < numSamples && !endOfData(); samples++) {
- int16 out;
- if (_firstTime) {
- _lastSample = fpMus.readUint16LE();
- _filePos += 2;
- _firstTime = false;
- out = _lastSample;
- } else {
- uint8 in = fpMus.readByte();
- uint16 delta = GetCompressedAmplitude(in) << GetCompressedShift(in);
-
- if (GetCompressedSign(in))
- out = _lastSample - delta;
- else
- out = _lastSample + delta;
-
- _filePos++;
- _lastSample = out;
-
- if (_looping) {
- if (_filePos >= _fileEnd) {
- _firstTime = true;
- _filePos = _fileStart;
- fpMus.seek(_filePos, SEEK_SET);
- }
- } else {
- // Fade out at the end of the music, unless it already is.
- if (_fileEnd - _filePos <= FADE_SAMPLES && _fading <= 0)
- fadeDown();
- }
-
- if (_fading > 0) {
- if (--_fading == 0) {
- _streaming = false;
- _looping = false;
- }
- out = (out * _fading) / FADE_SAMPLES;
- } else if (_fading < 0) {
- _fading--;
- out = -(out * _fading) / FADE_SAMPLES;
- if (_fading <= -FADE_SAMPLES)
- _fading = 0;
- }
- }
-
- *buffer++ = out;
- }
-
- return samples;
-}
-
-bool MusicHandle::endOfData(void) const {
- return (!_streaming || _filePos >= _fileEnd);
-}
-
-/**
- * Retrieve information about an in-memory WAV file.
- * @param data The WAV data
- * @param wavInfo Pointer to the WavInfo structure to fill with information.
- * @return True if the data appears to be a WAV file, otherwise false.
- */
-
-bool Sound::getWavInfo(byte *data, WavInfo *wavInfo) {
- uint32 wavLength;
- uint32 offset;
-
- if (READ_UINT32(data) != MKID('RIFF')) {
- warning("getWavInfo: No 'RIFF' header");
- return false;
- }
-
- wavLength = READ_LE_UINT32(data + 4) + 8;
-
- if (READ_UINT32(data + 8) != MKID('WAVE')) {
- warning("getWavInfo: No 'WAVE' header");
- return false;
- }
-
- if (READ_UINT32(data + 12) != MKID('fmt ')) {
- warning("getWavInfo: No 'fmt' header");
- return false;
- }
-
- wavInfo->channels = READ_LE_UINT16(data + 22);
- wavInfo->rate = READ_LE_UINT16(data + 24);
-
- offset = READ_LE_UINT32(data + 16) + 20;
-
- // It's almost certainly a WAV file, but we still need to find its
- // 'data' chunk.
-
- while (READ_UINT32(data + offset) != MKID('data')) {
- if (offset >= wavLength) {
- warning("getWavInfo: Can't find 'data' chunk");
- return false;
- }
- offset += (READ_LE_UINT32(data + offset + 4) + 8);
- }
-
- wavInfo->samples = READ_LE_UINT32(data + offset + 4);
- wavInfo->data = data + offset + 8;
- return true;
-}
-
/**
* Mutes/Unmutes the music.
* @param mute If mute is false, restore the volume to the last set master
@@ -550,10 +621,10 @@ uint8 Sound::getMusicVolume(void) {
void Sound::pauseMusic(void) {
Common::StackLock lock(_mutex);
- if (_soundOn) {
- for (int i = 0; i < MAXMUS; i++)
- _music[i]._paused = true;
- }
+ if (!_soundOn)
+ return;
+
+ _musicPaused = true;
}
/**
@@ -563,10 +634,10 @@ void Sound::pauseMusic(void) {
void Sound::unpauseMusic(void) {
Common::StackLock lock(_mutex);
- if (_soundOn) {
- for (int i = 0; i < MAXMUS; i++)
- _music[i]._paused = false;
- }
+ if (!_soundOn)
+ return;
+
+ _musicPaused = false;
}
/**
@@ -577,78 +648,8 @@ void Sound::stopMusic(void) {
Common::StackLock lock(_mutex);
for (int i = 0; i < MAXMUS; i++)
- _music[i].fadeDown();
-}
-
-/**
- * Save/Restore information about current music so that we can restore it
- * after the credits.
- */
-
-void Sound::saveMusicState(void) {
- Common::StackLock lock(_mutex);
-
- int saveStream;
-
- if (_music[0]._streaming && _music[0]._fading <= 0) {
- saveStream = 0;
- } else if (_music[1]._streaming && _music[0]._fading <= 0) {
- saveStream = 1;
- } else {
- _music[2]._streaming = false;
- savedMusicFilename = NULL;
- return;
- }
-
- _music[2]._streaming = true;
- _music[2]._fading = 0;
- _music[2]._looping = _music[saveStream]._looping;
- _music[2]._fileStart = _music[saveStream]._fileStart;
- _music[2]._filePos = _music[saveStream]._filePos;
- _music[2]._fileEnd = _music[saveStream]._fileEnd;
- _music[2]._lastSample = _music[saveStream]._lastSample;
-
- if (fpMus.isOpen())
- savedMusicFilename = strdup(fpMus.name());
- else
- savedMusicFilename = NULL;
-}
-
-void Sound::restoreMusicState(void) {
- Common::StackLock lock(_mutex);
-
- // Fade out any music that happens to be playing
-
- for (int i = 0; i < MAXMUS; i++)
- _music[i].fadeDown();
-
- if (!_music[2]._streaming)
- return;
-
- int restoreStream;
-
- if (!_music[0]._streaming)
- restoreStream = 0;
- else
- restoreStream = 1;
-
- _music[restoreStream]._streaming = true;
- _music[restoreStream]._fading = 0;
- _music[restoreStream]._looping = _music[2]._looping;
- _music[restoreStream]._fileStart = _music[2]._fileStart;
- _music[restoreStream]._filePos = _music[2]._filePos;
- _music[restoreStream]._fileEnd = _music[2]._fileEnd;
- _music[restoreStream]._lastSample = _music[2]._lastSample;
- _music[restoreStream].fadeUp();
-
- if (savedMusicFilename) {
- if (fpMus.isOpen())
- fpMus.close();
-
- fpMus.open(savedMusicFilename);
- free(savedMusicFilename);
- savedMusicFilename = NULL;
- }
+ if (_music[i])
+ _music[i]->fadeDown();
}
void Sound::waitForLeadOut(void) {
@@ -674,67 +675,71 @@ void Sound::waitForLeadOut(void) {
int32 Sound::streamCompMusic(uint32 musicId, bool looping) {
Common::StackLock lock(_mutex);
- int32 primaryStream = -1;
- int32 secondaryStream = -1;
+ int primary = -1;
+ int secondary = -1;
+
+ // If both music streams are active, one of them will have to go.
- // If both music streams are playing, one of them will have to go.
+ if (_music[0] && _music[1]) {
+ int32 fade0 = _music[0]->isFading();
+ int32 fade1 = _music[1]->isFading();
- if (_music[0]._streaming && _music[1]._streaming) {
- if (!_music[0]._fading && !_music[1]._fading) {
- // None of them are fading. This shouldn't happen, so
- // just pick one and be done with it.
- primaryStream = 0;
- } else if (_music[0]._fading && !_music[1]._fading) {
+ if (!fade0 && !fade1) {
+ // Neither is fading. This shouldn't happen, so just
+ // pick one and be done with it.
+ primary = 0;
+ } else if (fade0 && !fade1) {
// Stream 0 is fading, so pick that one.
- primaryStream = 0;
- } else if (!_music[0]._fading && _music[1]._fading) {
+ primary = 0;
+ } else if (!fade0 && fade1) {
// Stream 1 is fading, so pick that one.
- primaryStream = 1;
+ primary = 1;
} else {
// Both streams are fading. Pick the one that is
// closest to silent.
- if (ABS(_music[0]._fading) < ABS(_music[1]._fading))
- primaryStream = 0;
+ if (ABS(fade0) < ABS(fade1))
+ primary = 0;
else
- primaryStream = 1;
+ primary = 1;
}
- _music[primaryStream].stop();
+ delete _music[primary];
+ delete _converter[primary];
+ _music[primary] = NULL;
+ _converter[primary] = NULL;
}
// Pick the available music stream. If no music is playing it doesn't
- // matter which we use, so pick the first one.
+ // matter which we use.
- if (_music[0]._streaming || _music[1]._streaming) {
- if (_music[0]._streaming) {
- primaryStream = 1;
- secondaryStream = 0;
+ if (_music[0] || _music[1]) {
+ if (_music[0]) {
+ primary = 1;
+ secondary = 0;
} else {
- primaryStream = 0;
- secondaryStream = 1;
+ primary = 0;
+ secondary = 1;
}
} else
- primaryStream = 0;
+ primary = 0;
// Don't start streaming if the volume is off.
if (isMusicMute())
return RD_OK;
- // Start other music stream fading out
- if (secondaryStream != -1)
- _music[secondaryStream].fadeDown();
+ if (secondary != -1)
+ _music[secondary]->fadeDown();
- switch (openSoundFile(&fpMus, "music")) {
- case 0:
- return RDERR_INVALIDFILENAME;
- case kCLUMode:
- break;
- default:
- warning("Compressed music is not yet supported");
+ _music[primary] = new MusicInputStream(_vm->_resman->whichCd(), musicId, looping);
+
+ if (!_music[primary]->isReady()) {
+ delete _music[primary];
+ _music[primary] = NULL;
return RDERR_INVALIDFILENAME;
}
- return _music[primaryStream].play(musicId, looping);
+ _converter[primary] = makeRateConverter(_music[primary]->getRate(), _vm->_mixer->getOutputRate(), _music[primary]->isStereo(), false);
+ return RD_OK;
}
/**
@@ -745,8 +750,8 @@ int32 Sound::musicTimeRemaining(void) {
Common::StackLock lock(_mutex);
for (int i = 0; i < MAXMUS; i++) {
- if (_music[i]._streaming && _music[i]._fading <= 0)
- return (_music[i]._fileEnd - _music[i]._filePos) / _music[i].getRate();
+ if (_music[i] && _music[i]->isFading() <= 0)
+ return _music[i]->getTimeRemaining();
}
return 0;
@@ -765,7 +770,7 @@ int32 Sound::musicTimeRemaining(void) {
void Sound::muteSpeech(bool mute) {
_speechMuted = mute;
- if (getSpeechStatus() == RDSE_SAMPLEPLAYING) {
+ if (_soundHandleSpeech.isActive()) {
byte volume = mute ? 0 : 16 * _speechVol;
_vm->_mixer->setChannelVolume(_soundHandleSpeech, volume);
@@ -791,7 +796,7 @@ void Sound::setSpeechVolume(uint8 volume) {
_speechVol = volume;
- if (_soundHandleSpeech.isActive() && !_speechMuted && getSpeechStatus() == RDSE_SAMPLEPLAYING) {
+ if (_soundHandleSpeech.isActive() && !_speechMuted && _soundHandleSpeech.isActive()) {
_vm->_mixer->setChannelVolume(_soundHandleSpeech, 16 * _speechVol);
}
}
@@ -809,10 +814,8 @@ uint8 Sound::getSpeechVolume(void) {
*/
void Sound::pauseSpeech(void) {
- if (getSpeechStatus() == RDSE_SAMPLEPLAYING) {
- _speechPaused = true;
- _vm->_mixer->pauseHandle(_soundHandleSpeech, true);
- }
+ _speechPaused = true;
+ _vm->_mixer->pauseHandle(_soundHandleSpeech, true);
}
/**
@@ -820,10 +823,8 @@ void Sound::pauseSpeech(void) {
*/
void Sound::unpauseSpeech(void) {
- if (_speechPaused) {
- _speechPaused = false;
- _vm->_mixer->pauseHandle(_soundHandleSpeech, false);
- }
+ _speechPaused = false;
+ _vm->_mixer->pauseHandle(_soundHandleSpeech, false);
}
/**
@@ -834,9 +835,8 @@ int32 Sound::stopSpeech(void) {
if (!_soundOn)
return RD_OK;
- if (_speechStatus) {
+ if (_soundHandleSpeech.isActive()) {
_vm->_mixer->stopHandle(_soundHandleSpeech);
- _speechStatus = false;
return RD_OK;
}
return RDERR_SPEECHNOTPLAYING;
@@ -847,17 +847,7 @@ int32 Sound::stopSpeech(void) {
*/
int32 Sound::getSpeechStatus(void) {
- if (!_soundOn || !_speechStatus)
- return RDSE_SAMPLEFINISHED;
-
- if (_speechPaused)
- return RDSE_SAMPLEPLAYING;
-
- if (!_soundHandleSpeech.isActive()) {
- _speechStatus = false;
- return RDSE_SAMPLEFINISHED;
- }
- return RDSE_SAMPLEPLAYING;
+ return _soundHandleSpeech.isActive() ? RDSE_SAMPLEPLAYING : RDSE_SAMPLEFINISHED;
}
/**
@@ -883,7 +873,7 @@ uint32 Sound::preFetchCompSpeech(uint32 speechid, uint16 **buf) {
File fp;
uint32 numSamples;
- AudioStream *input = getAudioStream(&fp, "speech", speechid, &numSamples);
+ AudioStream *input = getAudioStream(&fp, "speech", _vm->_resman->whichCd(), speechid, &numSamples);
*buf = NULL;
@@ -922,7 +912,7 @@ int32 Sound::playCompSpeech(uint32 speechid, uint8 vol, int8 pan) {
return RDERR_SPEECHPLAYING;
File *fp = new File;
- AudioStream *input = getAudioStream(fp, "speech", speechid, NULL);
+ AudioStream *input = getAudioStream(fp, "speech", _vm->_resman->whichCd(), speechid, NULL);
// Make the AudioStream object the sole owner of the file so that it
// will die along with the AudioStream when the speech has finished.
@@ -937,11 +927,7 @@ int32 Sound::playCompSpeech(uint32 speechid, uint8 vol, int8 pan) {
int8 p = _panTable[pan + 16];
// Start the speech playing
- _speechPaused = true;
-
_vm->_mixer->playInputStream(&_soundHandleSpeech, input, false, volume, p);
- _speechStatus = true;
-
return RD_OK;
}
@@ -950,6 +936,55 @@ int32 Sound::playCompSpeech(uint32 speechid, uint8 vol, int8 pan) {
// ----------------------------------------------------------------------------
/**
+ * Retrieve information about an in-memory WAV file.
+ * @param data The WAV data
+ * @param wavInfo Pointer to the WavInfo structure to fill with information.
+ * @return True if the data appears to be a WAV file, otherwise false.
+ */
+
+bool Sound::getWavInfo(byte *data, WavInfo *wavInfo) {
+ uint32 wavLength;
+ uint32 offset;
+
+ if (READ_UINT32(data) != MKID('RIFF')) {
+ warning("getWavInfo: No 'RIFF' header");
+ return false;
+ }
+
+ wavLength = READ_LE_UINT32(data + 4) + 8;
+
+ if (READ_UINT32(data + 8) != MKID('WAVE')) {
+ warning("getWavInfo: No 'WAVE' header");
+ return false;
+ }
+
+ if (READ_UINT32(data + 12) != MKID('fmt ')) {
+ warning("getWavInfo: No 'fmt' header");
+ return false;
+ }
+
+ wavInfo->channels = READ_LE_UINT16(data + 22);
+ wavInfo->rate = READ_LE_UINT16(data + 24);
+
+ offset = READ_LE_UINT32(data + 16) + 20;
+
+ // It's almost certainly a WAV file, but we still need to find its
+ // 'data' chunk.
+
+ while (READ_UINT32(data + offset) != MKID('data')) {
+ if (offset >= wavLength) {
+ warning("getWavInfo: Can't find 'data' chunk");
+ return false;
+ }
+ offset += (READ_LE_UINT32(data + offset + 4) + 8);
+ }
+
+ wavInfo->samples = READ_LE_UINT32(data + offset + 4);
+ wavInfo->data = data + offset + 8;
+ return true;
+}
+
+/**
* @return the index of the sound effect with the ID passed in.
*/
diff --git a/sword2/driver/d_sound.h b/sword2/driver/d_sound.h
index 0ba542bdfe..eeed5ca77a 100644
--- a/sword2/driver/d_sound.h
+++ b/sword2/driver/d_sound.h
@@ -28,6 +28,8 @@ class RateConverter;
namespace Sword2 {
+class MusicInputStream;
+
// Max number of sound fx
#define MAXFX 16
#define MAXMUS 2
@@ -55,36 +57,6 @@ struct FxHandle {
PlayingSoundHandle _handle;
};
-class MusicHandle : public AudioStream {
-public:
- RateConverter *_converter;
- bool _firstTime;
- bool _streaming;
- bool _paused;
- bool _looping;
- int32 _fading;
- int32 _fileStart;
- int32 _filePos;
- int32 _fileEnd;
- uint16 _lastSample;
-
- bool isStereo(void) const { return false; }
- int getRate(void) const { return 22050; }
-
- void fadeDown(void);
- void fadeUp(void);
- int32 play(uint32 musicId, bool looping);
- void stop(void);
- int readBuffer(int16 *buffer, const int numSamples);
- bool endOfData(void) const;
- // This stream never 'ends'
- bool endOfStream(void) const { return false; }
-
- MusicHandle() : _firstTime(false), _streaming(false), _paused(false),
- _looping(false), _fading(0), _fileStart(0),
- _filePos(0), _fileEnd(0), _lastSample(0) {}
-};
-
class Sound {
private:
Sword2Engine *_vm;
@@ -95,13 +67,13 @@ private:
bool _soundOn;
static int32 _musicVolTable[17];
- MusicHandle _music[MAXMUS + 1];
- char *savedMusicFilename;
+ MusicInputStream *_music[MAXMUS];
+ RateConverter *_converter[MAXMUS];
+ bool _musicPaused;
bool _musicMuted;
uint8 _musicVol;
PlayingSoundHandle _soundHandleSpeech;
- bool _speechStatus;
bool _speechPaused;
bool _speechMuted;
uint8 _speechVol;
@@ -114,9 +86,6 @@ private:
int32 getFxIndex(int32 id);
void stopFxHandle(int i);
- int openSoundFile(File *fp, const char *base);
- AudioStream *getAudioStream(File *fp, const char *base, uint32 id, uint32 *numSamples);
-
public:
Sound(Sword2Engine *vm);
~Sound();
@@ -133,8 +102,6 @@ public:
void pauseMusic(void);
void unpauseMusic(void);
void stopMusic(void);
- void saveMusicState(void);
- void restoreMusicState(void);
void waitForLeadOut(void);
int32 streamCompMusic(uint32 musicId, bool looping);
int32 musicTimeRemaining(void);
diff --git a/sword2/function.cpp b/sword2/function.cpp
index 4f95b89852..6c275f9747 100644
--- a/sword2/function.cpp
+++ b/sword2/function.cpp
@@ -369,6 +369,8 @@ struct CreditsLine {
#define CREDITS_LINE_SPACING 20
int32 Logic::fnPlayCredits(int32 *params) {
+ uint32 loopingMusicId = _vm->_loopingMusicId;
+
// This function just quits the game if this is the playable demo, ie.
// credits are NOT played in the demo any more!
@@ -383,11 +385,8 @@ int32 Logic::fnPlayCredits(int32 *params) {
_vm->setMouse(0);
- _vm->_sound->saveMusicState();
-
_vm->_sound->muteFx(true);
_vm->_sound->muteSpeech(true);
- _vm->_sound->stopMusic();
_vm->_graphics->waitForFade();
_vm->_graphics->fadeDown();
@@ -703,10 +702,16 @@ int32 Logic::fnPlayCredits(int32 *params) {
if (_vm->_quit)
return IR_CONT;
- _vm->_sound->restoreMusicState();
_vm->_sound->muteFx(false);
_vm->_sound->muteSpeech(false);
+ if (loopingMusicId) {
+ pars[0] = loopingMusicId;
+ pars[1] = FX_LOOP;
+ fnPlayMusic(pars);
+ } else
+ fnStopMusic(NULL);
+
_vm->_thisScreen.new_palette = 99;
if (!_vm->_mouseStatus || _choosing)