diff options
author | Evgeny Grechnikov | 2018-10-20 16:35:23 +0300 |
---|---|---|
committer | Evgeny Grechnikov | 2018-10-20 16:35:23 +0300 |
commit | af580eaa853b2434c0d237cff49f7f273444a06d (patch) | |
tree | 295dd4dfb9fadb348bd46bdc8512e16b19555c58 /engines/lastexpress/sound | |
parent | 9f7ae73a7b7318311e5cc52d36e451cacfba8a02 (diff) | |
download | scummvm-rg350-af580eaa853b2434c0d237cff49f7f273444a06d.tar.gz scummvm-rg350-af580eaa853b2434c0d237cff49f7f273444a06d.tar.bz2 scummvm-rg350-af580eaa853b2434c0d237cff49f7f273444a06d.zip |
LASTEXPRESS: save/load sound state
Warning: breaks compatibility with previous savefiles.
They were mostly broken anyway, locking any NPC who
waited for kActionEndSound when savefile was created.
Diffstat (limited to 'engines/lastexpress/sound')
-rw-r--r-- | engines/lastexpress/sound/entry.cpp | 224 | ||||
-rw-r--r-- | engines/lastexpress/sound/entry.h | 21 | ||||
-rw-r--r-- | engines/lastexpress/sound/queue.cpp | 65 | ||||
-rw-r--r-- | engines/lastexpress/sound/queue.h | 7 | ||||
-rw-r--r-- | engines/lastexpress/sound/sound.cpp | 10 |
5 files changed, 176 insertions, 151 deletions
diff --git a/engines/lastexpress/sound/entry.cpp b/engines/lastexpress/sound/entry.cpp index 8d9a496fe1..d73642d92a 100644 --- a/engines/lastexpress/sound/entry.cpp +++ b/engines/lastexpress/sound/entry.cpp @@ -46,6 +46,7 @@ SoundEntry::SoundEntry(LastExpressEngine *engine) : _engine(engine) { _tag = kSoundTagNone; _blockCount = 0; + _startTime = 0; _stream = NULL; @@ -82,11 +83,12 @@ void SoundEntry::open(Common::String name, SoundFlag flag, int priority) { } void SoundEntry::close() { + if (_soundStream) { + delete _soundStream; // stops the sound in destructor + _soundStream = NULL; + _stream = NULL; // disposed by _soundStream + } _status |= kSoundFlagClosed; - // Loop until ready - // _status |= kSoundFlagCloseRequested; - //while (!(_status & kSoundFlagClosed) && !(getSoundQueue()->getFlag() & 8) && (getSoundQueue()->getFlag() & 1)) - // ; // empty loop body // The original game remove the entry from the cache here, // but since we are called from within an iterator loop @@ -106,34 +108,41 @@ void SoundEntry::close() { } } -void SoundEntry::play() { +void SoundEntry::play(uint32 startTime) { + if (_status & kSoundFlagClosed) + return; // failed to load sound file + if (!_stream) error("[SoundEntry::play] stream has been disposed"); // Prepare sound stream - if (!_soundStream) - _soundStream = new StreamedSound(); + if (_soundStream) + error("[SoundEntry::play] already playing"); + + // BUG: the original game never checks for sound type when loading subtitles. + // NIS files and LNK files have the same base name, + // so without extra caution NIS subtitles would be loaded for LNK sounds as well. + // The original game instead separates calls to play() and setSubtitles() + // and does not call setSubtitles() for linked-after sounds. + // Unfortunately, that does not work well with save/load. + if ((_status & kSoundTypeMask) != kSoundTypeLink && (_status & kSoundTypeMask) != kSoundTypeConcert) { + // Get subtitles name + uint32 size = (_name.size() > 4 ? _name.size() - 4 : _name.size()); + setSubtitles(Common::String(_name.c_str(), size)); + } + + _soundStream = new StreamedSound(); _stream->seek(0); // Load the stream and start playing - _soundStream->load(_stream, _status & kSoundVolumeMask, (_status & kSoundFlagLooped) != 0); -} - -bool SoundEntry::isFinished() { - if (!_stream) - return true; - - if (!_soundStream) - return false; + _soundStream->load(_stream, _status & kSoundVolumeMask, (_status & kSoundFlagLooped) != 0, startTime); - // TODO check that all data has been queued - return _soundStream->isFinished(); + _status |= kSoundFlagPlaying; } void SoundEntry::setupTag(SoundFlag flag) { switch (flag & kSoundTypeMask) { - default: case kSoundTypeNormal: _tag = getSoundQueue()->generateNextTag(); break; @@ -160,7 +169,7 @@ void SoundEntry::setupTag(SoundFlag flag) { previous->fade(); } - _tag = kSoundTagIntro; + _tag = kSoundTagWalla; } break; @@ -199,14 +208,18 @@ void SoundEntry::setupTag(SoundFlag flag) { _tag = kSoundTagMenu; } break; + + default: + assert(false); + break; } } void SoundEntry::setupStatus(SoundFlag flag) { _status = flag; - if ((_status & kSoundVolumeMask) == kVolumeNone) - _status |= kSoundFlagMuteRequested; + // set the flag for the case that our savefiles + // will be ever loaded by the original game if (!(_status & kSoundFlagLooped)) _status |= kSoundFlagCloseOnDataEnd; } @@ -222,6 +235,14 @@ void SoundEntry::loadStream(Common::String name) { if (!_stream) _status = kSoundFlagClosed; + + // read total count of sound blocks for the case that our savefiles + // will be ever loaded by the original game + if (_stream) { + _stream->readUint32LE(); + _blockCount = _stream->readUint16LE(); + _status |= kSoundFlagHeaderProcessed; + } } void SoundEntry::setVolumeSmoothly(SoundFlag newVolume) { @@ -246,59 +267,46 @@ void SoundEntry::setVolumeSmoothly(SoundFlag newVolume) { } bool SoundEntry::update() { - assert(_name.size() < 16); + if (_soundStream && _soundStream->isFinished()) + _status |= kSoundFlagClosed; - bool result; - char sub[16]; + if (_status & kSoundFlagClosed) + return false; - if (_status & kSoundFlagClosed) { - result = false; + if (_status & kSoundFlagDelayedActivate) { + // counter overflow is processed correctly + if (_engine->_system->getMillis() - _initTimeMS >= _activateDelayMS) { + _status &= ~kSoundFlagDelayedActivate; + play(); + } } else { - if (_status & kSoundFlagDelayedActivate) { - // counter overflow is processed correctly - if (_engine->_system->getMillis() - _initTimeMS >= _activateDelayMS) { - _status &= ~kSoundFlagDelayedActivate; - play(); - - // drop .SND extension - strcpy(sub, _name.c_str()); - int l = _name.size(); - if (l > 4) - sub[l - 4] = 0; - showSubtitle(sub); - } - } else { - if (!(getSoundQueue()->getFlag() & 0x20)) { - if (!(_status & kSoundFlagFixedVolume)) { - if (_entity) { - if (_entity < 0x80) { - setVolume(getSound()->getSoundFlag(_entity)); - } + if (!(getSoundQueue()->getFlag() & 0x20)) { + if (!(_status & kSoundFlagFixedVolume)) { + if (_entity) { + if (_entity < 0x80) { + setVolume(getSound()->getSoundFlag(_entity)); } } } - //if (_status & kSoundFlagHasUnreadData && !(_status & kSoundFlagMute) && v1->soundBuffer) - // Sound_FillSoundBuffer(v1); } - result = true; + //if (_status & kSoundFlagHasUnreadData && !(_status & kSoundFlagMute) && v1->soundBuffer) + // Sound_FillSoundBuffer(v1); } - return result; + return true; } void SoundEntry::setVolume(SoundFlag newVolume) { assert((newVolume & kSoundVolumeMask) == newVolume); - if (newVolume) { - if (getSoundQueue()->getFlag() & 0x20 && _tag != kSoundTagNIS && _tag != kSoundTagLink) - setVolumeSmoothly(newVolume); - else - _status = newVolume + (_status & ~kSoundVolumeMask); - } else { + if (newVolume == kVolumeNone) { _volumeWithoutNIS = 0; - _status |= kSoundFlagMuteRequested; - _status &= ~(kSoundFlagVolumeChanging | kSoundVolumeMask); + } else if (getSoundQueue()->getFlag() & 0x20 && _tag != kSoundTagNIS && _tag != kSoundTagLink) { + setVolumeSmoothly(newVolume); + return; } + + _status = (_status & ~kSoundVolumeMask) | newVolume; if (_soundStream) _soundStream->setVolume(_status & kSoundVolumeMask); } @@ -324,24 +332,7 @@ void SoundEntry::initDelayedActivate(unsigned activateDelay) { _status |= kSoundFlagDelayedActivate; } -void SoundEntry::kill() { - _status |= kSoundFlagClosed; - _entity = kEntityPlayer; - - if (_stream) { - if (!_soundStream) { - SAFE_DELETE(_stream); - } else { - // the original stream will be disposed - _soundStream->stop(); - SAFE_DELETE(_soundStream); - } - - _stream = NULL; - } -} - -void SoundEntry::showSubtitle(Common::String filename) { +void SoundEntry::setSubtitles(Common::String filename) { _subtitle = new SubtitleEntry(_engine); _subtitle->load(filename, this); @@ -354,32 +345,79 @@ void SoundEntry::showSubtitle(Common::String filename) { } void SoundEntry::saveLoadWithSerializer(Common::Serializer &s) { - assert(_name.size() <= 16); + if (s.isLoading()) { + // load the fields + uint32 blocksLeft; - if (_name.matchString("NISSND?") && ((_status & kSoundTypeMask) != kSoundTypeMenu)) { s.syncAsUint32LE(_status); s.syncAsUint32LE(_tag); - s.syncAsUint32LE(_blockCount); // field_8; + s.syncAsUint32LE(blocksLeft); + s.syncAsUint32LE(_startTime); + uint32 unused; + s.syncAsUint32LE(unused); + s.syncAsUint32LE(unused); + s.syncAsUint32LE(_entity); + + uint32 activateDelay; + s.syncAsUint32LE(activateDelay); + s.syncAsUint32LE(_priority); + + char name[16]; + s.syncBytes((byte *)name, 16); // _linkAfter name, should be always empty for entries in savefile + s.syncBytes((byte *)name, 16); // real name + name[15] = 0; + + // load the sound + _blockCount = blocksLeft + _startTime; + + // if we are loading a savefile from the original game + // and this savefile has been saved at a bizarre moment, + // we can see transient flags here. + // Let's pretend that the IRQ handler has run once. + if (_status & kSoundFlagPlayRequested) + _status |= kSoundFlagPlaying; + if (_status & (kSoundFlagCloseRequested | kSoundFlagFading)) + _status |= kSoundFlagClosed; + _status &= kSoundVolumeMask + | kSoundFlagPlaying + | kSoundFlagClosed + | kSoundFlagCloseOnDataEnd + | kSoundFlagLooped + | kSoundFlagDelayedActivate + | kSoundFlagHasSubtitles + | kSoundFlagFixedVolume + | kSoundFlagHeaderProcessed + | kSoundTypeMask; + + loadStream(name); // also sets _name + if (_status & kSoundFlagPlaying) + play((_status & kSoundFlagLooped) ? 0 : _startTime); // also loads subtitles + + _initTimeMS = _engine->_system->getMillis(); + _activateDelayMS = activateDelay * 1000 / 30; + + } else { + assert(_name.size() < 16); + assert(needSaving()); + // we can save our flags as is + // the original game can reconstruct kSoundFlagMute, kSoundFlagCyclicBuffer, kSoundFlagHasUnreadData, + // and we set other important flags correctly + s.syncAsUint32LE(_status); + s.syncAsUint32LE(_tag); uint32 time = getTime(); + uint32 blocksLeft = _blockCount - time; + s.syncAsUint32LE(blocksLeft); s.syncAsUint32LE(time); uint32 unused = 0; s.syncAsUint32LE(unused); s.syncAsUint32LE(unused); s.syncAsUint32LE(_entity); - if (s.isLoading()) { - uint32 delta; - s.syncAsUint32LE(delta); - _initTimeMS = _engine->_system->getMillis(); - _activateDelayMS = delta * 1000 / 15; - } else { - uint32 deltaMS = _initTimeMS + _activateDelayMS - _engine->_system->getMillis(); - if (deltaMS > 0x8000000u) // sanity check against overflow - deltaMS = 0; - uint32 delta = deltaMS * 15 / 1000; - s.syncAsUint32LE(delta); - } - + uint32 deltaMS = _initTimeMS + _activateDelayMS - _engine->_system->getMillis(); + if (deltaMS > 0x8000000u) // sanity check against overflow + deltaMS = 0; + uint32 delta = deltaMS * 30 / 1000; + s.syncAsUint32LE(delta); s.syncAsUint32LE(_priority); char name[16] = {0}; diff --git a/engines/lastexpress/sound/entry.h b/engines/lastexpress/sound/entry.h index cb12214d4a..998d1e53e8 100644 --- a/engines/lastexpress/sound/entry.h +++ b/engines/lastexpress/sound/entry.h @@ -96,9 +96,13 @@ public: void open(Common::String name, SoundFlag flag, int priority); void close(); - void play(); - void kill(); - bool isFinished(); + // startTime is measured in sound ticks, 30Hz timer + // [used for restoring the entry from savefile] + void play(uint32 startTime = 0); + void kill() { + _entity = kEntityPlayer; // no kActionEndSound notifications + close(); + } void setVolumeSmoothly(SoundFlag newVolume); // setVolumeSmoothly() treats kVolumeNone in a special way; // fade() terminates the stream after the transition @@ -110,18 +114,20 @@ public: void initDelayedActivate(unsigned activateDelay); // Subtitles - void showSubtitle(Common::String filename); + void setSubtitles(Common::String filename); // Serializable void saveLoadWithSerializer(Common::Serializer &ser); // Accessors - void addStatusFlag(SoundFlag flag) { _status |= flag; } void setEntity(EntityIndex entity) { _entity = entity; } + bool needSaving() const { + return (_name != "NISSND?" && (_status & kSoundTypeMask) != kSoundTypeMenu); + } uint32 getStatus() { return _status; } int32 getTag() { return _tag; } - uint32 getTime() { return _soundStream ? (_soundStream->getTimeMS() * 30 / 1000) : 0; } + uint32 getTime() { return _soundStream ? (_soundStream->getTimeMS() * 30 / 1000) + _startTime : 0; } EntityIndex getEntity() { return _entity; } uint32 getPriority() { return _priority; } const Common::String& getName(){ return _name; } @@ -138,9 +144,10 @@ private: uint32 _status; int32 _tag; // member of SoundTag for special sounds, unique value for normal sounds //byte *_bufferStart, *_bufferEnd, *_decodePointer, *_buffer, *_readPointer; - // the original game uses uint32 _blocksLeft, _time instead of _totalBlocks + // the original game uses uint32 _blocksLeft, _time instead of _blockCount // we ask the backend for sound time uint32 _blockCount; + uint32 _startTime; //uint32 _bufferSize; //union { uint32 _streamPos; enum StreamCloseReason _streamCloseReason; }; Common::SeekableReadStream *_stream; // The file stream diff --git a/engines/lastexpress/sound/queue.cpp b/engines/lastexpress/sound/queue.cpp index 3a10a7a89b..58b11f2402 100644 --- a/engines/lastexpress/sound/queue.cpp +++ b/engines/lastexpress/sound/queue.cpp @@ -79,8 +79,6 @@ void SoundQueue::stop(Common::String filename) { } void SoundQueue::updateQueue() { - ++_flag; - if (getAmbientState() & kAmbientSoundEnabled) { SoundEntry *entry = getEntry(kSoundTagAmbient); if (!entry || getFlags()->flag_3 || (entry && entry->getTime() > getSound()->getAmbientSoundDuration())) { @@ -100,32 +98,23 @@ void SoundQueue::updateQueue() { // Original removes the entry data from the cache and sets the archive as not loaded // and if the sound data buffer is not full, loads a new entry to be played based on - // its priority and filter id + // its priority and volume if (!entry->update() && !(entry->getStatus() & kSoundFlagKeepAfterFinish)) { entry->close(); SAFE_DELETE(entry); it = _soundList.reverse_erase(it); - continue; - } - - // When the entry has stopped playing, we remove his buffer - if (entry->isFinished()) { - entry->close(); - SAFE_DELETE(entry); - it = _soundList.reverse_erase(it); - continue; } } // Original update the current entry, loading another set of samples to be decoded getFlags()->flag_3 = false; - - --_flag; } void SoundQueue::stopAmbient() { + _ambientState = 0; + for (Common::List<SoundEntry *>::iterator i = _soundList.begin(); i != _soundList.end(); ++i) { if ((*i)->getTag() == kSoundTagAmbient) { (*i)->kill(); @@ -141,12 +130,12 @@ void SoundQueue::stopAmbient() { } } -void SoundQueue::stopAllExcept(SoundTag type1, SoundTag type2) { - if (!type2) - type2 = type1; +void SoundQueue::stopAllExcept(SoundTag tag1, SoundTag tag2) { + if (!tag2) + tag2 = tag1; for (Common::List<SoundEntry *>::iterator i = _soundList.begin(); i != _soundList.end(); ++i) { - if ((*i)->getTag() != type1 && (*i)->getTag() != type2) + if ((*i)->getTag() != tag1 && (*i)->getTag() != tag2) (*i)->kill(); } } @@ -160,7 +149,7 @@ void SoundQueue::destroyAllSound() { error("[SoundQueue::destroyAllSound] Invalid entry found in sound queue"); // Delete entry - entry->close(); + entry->kill(); SAFE_DELETE(entry); i = _soundList.reverse_erase(i); @@ -174,7 +163,7 @@ void SoundQueue::destroyAllSound() { ////////////////////////////////////////////////////////////////////////// void SoundQueue::stopAll() { for (Common::List<SoundEntry *>::iterator i = _soundList.begin(); i != _soundList.end(); ++i) - (*i)->addStatusFlag(kSoundFlagClosed); + (*i)->close(); } ////////////////////////////////////////////////////////////////////////// @@ -194,8 +183,8 @@ void SoundQueue::fade(EntityIndex entity) { } } -void SoundQueue::fade(SoundTag type) { - SoundEntry *entry = getEntry(type); +void SoundQueue::fade(SoundTag tag) { + SoundEntry *entry = getEntry(tag); if (entry) entry->fade(); } @@ -236,9 +225,9 @@ SoundEntry *SoundQueue::getEntry(Common::String name) { return NULL; } -SoundEntry *SoundQueue::getEntry(SoundTag type) { +SoundEntry *SoundQueue::getEntry(SoundTag tag) { for (Common::List<SoundEntry *>::iterator i = _soundList.begin(); i != _soundList.end(); ++i) { - if ((*i)->getTag() == type) + if ((*i)->getTag() == tag) return *i; } @@ -321,24 +310,22 @@ void SoundQueue::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsUint32LE(_ambientState); s.syncAsUint32LE(_currentTag); - // Compute the number of entries to save - uint32 numEntries = count(); - s.syncAsUint32LE(numEntries); - // Save or load each entry data if (s.isSaving()) { + // Compute the number of entries to save + uint32 numEntries = count(); + s.syncAsUint32LE(numEntries); + for (Common::List<SoundEntry *>::iterator i = _soundList.begin(); i != _soundList.end(); ++i) - (*i)->saveLoadWithSerializer(s); + if ((*i)->needSaving()) + (*i)->saveLoadWithSerializer(s); } else { - warning("[Sound::saveLoadWithSerializer] Loading not implemented"); - - uint32 unusedDataSize = numEntries * 64; - if (s.isLoading()) { - byte *empty = (byte *)malloc(unusedDataSize); - s.syncBytes(empty, unusedDataSize); - free(empty); - } else { - s.skip(unusedDataSize); + uint32 numEntries; + s.syncAsUint32LE(numEntries); + for (uint32 i = 0; i < numEntries; i++) { + SoundEntry* entry = new SoundEntry(_engine); + entry->saveLoadWithSerializer(s); + addToQueue(entry); } } } @@ -347,7 +334,7 @@ void SoundQueue::saveLoadWithSerializer(Common::Serializer &s) { uint32 SoundQueue::count() { uint32 numEntries = 0; for (Common::List<SoundEntry *>::iterator i = _soundList.begin(); i != _soundList.end(); ++i) - if ((*i)->getName().matchString("NISSND?")) + if ((*i)->needSaving()) ++numEntries; return numEntries; diff --git a/engines/lastexpress/sound/queue.h b/engines/lastexpress/sound/queue.h index df8c945ee9..63ff091d80 100644 --- a/engines/lastexpress/sound/queue.h +++ b/engines/lastexpress/sound/queue.h @@ -46,7 +46,7 @@ public: void stop(EntityIndex entity); void updateQueue(); void stopAmbient(); - void stopAllExcept(SoundTag type1, SoundTag type2 = kSoundTagNone); + void stopAllExcept(SoundTag tag1, SoundTag tag2 = kSoundTagNone); void destroyAllSound(); // State @@ -58,10 +58,10 @@ public: // Entries void assignNISLink(EntityIndex index); void fade(EntityIndex entity); - void fade(SoundTag type); + void fade(SoundTag tag); void fade(Common::String filename); void endAmbient(); - SoundEntry *getEntry(SoundTag type); + SoundEntry *getEntry(SoundTag tag); SoundEntry *getEntry(EntityIndex index); SoundEntry *getEntry(Common::String name); uint32 getEntryTime(EntityIndex index); @@ -96,7 +96,6 @@ private: // State & shared data int _ambientState; int32 _currentTag; - // TODO: this seems to be a synchronization flag for the sound timer uint32 _flag; // Entries diff --git a/engines/lastexpress/sound/sound.cpp b/engines/lastexpress/sound/sound.cpp index 597edd3b11..013a166ea8 100644 --- a/engines/lastexpress/sound/sound.cpp +++ b/engines/lastexpress/sound/sound.cpp @@ -137,7 +137,7 @@ void SoundManager::playSound(EntityIndex entity, Common::String filename, SoundF if (_queue->isBuffered(entity) && entity && entity < kEntityTrain) _queue->stop(entity); - SoundFlag currentFlag = (flag == kSoundVolumeEntityDefault) ? getSoundFlag(entity) : (SoundFlag)(flag | 0x80000); + SoundFlag currentFlag = (flag == kSoundVolumeEntityDefault) ? getSoundFlag(entity) : (SoundFlag)(flag | kSoundFlagFixedVolume); // Add .SND at the end of the filename if needed if (!filename.contains('.')) @@ -164,12 +164,6 @@ bool SoundManager::playSoundWithSubtitles(Common::String filename, uint32 flag, if (activateDelay) { entry->initDelayedActivate(activateDelay); } else { - // Get subtitles name - uint32 size = filename.size(); - while (filename.size() > size - 4) - filename.deleteLastChar(); - - entry->showSubtitle(filename); entry->play(); } @@ -323,7 +317,7 @@ void SoundManager::playSteam(CityIndex index) { // Get the new sound entry and show subtitles SoundEntry *entry = _queue->getEntry(kSoundTagAmbient); if (entry) - entry->showSubtitle(cities[index]); + entry->setSubtitles(cities[index]); } void SoundManager::playFightSound(byte action, byte a4) { |