diff options
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | sword2/driver/d_sound.cpp | 893 | ||||
-rw-r--r-- | sword2/driver/d_sound.h | 43 | ||||
-rw-r--r-- | sword2/function.cpp | 13 |
4 files changed, 479 insertions, 472 deletions
@@ -34,7 +34,7 @@ For a more comprehensive changelog for the latest experimental CVS code, see: Sword2: - Simplified memory/resource management - Simplified sound effects handling - - Added support for compressed speech. + - Added support for compressed speech and music. - Fixed various minor bugs. 0.6.1b (2004-08-03) 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) |