From d561fd8a833e885e0d7d622c55f793569c1c1b74 Mon Sep 17 00:00:00 2001 From: Evgeny Grechnikov Date: Tue, 16 Oct 2018 23:57:04 +0300 Subject: LASTEXPRESS: dynamic adjusting of sound volume Now it works just like in the original game, including fading where it is applicable (e.g. in a passengers list if closing the list while a sound is playing). By the way, p2s sequence is known as http://oeis.org/A000265 , p1s is 4 - A007814, and p2s[i]/2**p1s[i] is just i/16. It is time to get rid of these arrays. --- engines/lastexpress/data/snd.cpp | 85 ++++++++++++++++++++++--------------- engines/lastexpress/data/snd.h | 7 +-- engines/lastexpress/sound/entry.cpp | 55 ++++++++++++------------ engines/lastexpress/sound/entry.h | 24 ++++++----- engines/lastexpress/sound/queue.cpp | 10 ++--- engines/lastexpress/sound/sound.cpp | 4 +- 6 files changed, 103 insertions(+), 82 deletions(-) (limited to 'engines') diff --git a/engines/lastexpress/data/snd.cpp b/engines/lastexpress/data/snd.cpp index 4cff837193..7fb2c07706 100644 --- a/engines/lastexpress/data/snd.cpp +++ b/engines/lastexpress/data/snd.cpp @@ -342,9 +342,6 @@ static const int imaTable[1424] = { -20479, -28671, -32767, -32767, -32767, -32767 }; -static const int p1s[17] = { 0, 4, 3, 4, 2, 4, 3, 4, 1, 4, 3, 4, 2, 4, 3, 4, 0 }; -static const int p2s[17] = { 0, 1, 1, 3, 1, 5, 3, 7, 1, 9, 5, 11, 3, 13, 7, 15, 1 }; - #pragma endregion // Last Express ADPCM is similar to MS IMA mono, but inverts its nibbles @@ -352,12 +349,13 @@ static const int p2s[17] = { 0, 1, 1, 3, 1, 5, 3, 7, 1, 9, 5, 11, 3, 13, 7, 15, class LastExpress_ADPCMStream : public Audio::ADPCMStream { public: - LastExpress_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, uint32 blockSize, int32 filterId) : + LastExpress_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, uint32 blockSize, uint32 volume) : Audio::ADPCMStream(stream, disposeAfterUse, size, 44100, 1, blockSize) { - _currentFilterId = -1; - _nextFilterId = filterId; - _stepAdjust1 = 0; - _stepAdjust2 = 0; + _currentVolume = 0; + _nextVolume = volume; + _smoothChangeTarget = volume; + _volumeHoldBlocks = 0; + _running = true; } int readBuffer(int16 *buffer, const int numSamples) { @@ -369,24 +367,33 @@ public: assert(numSamples % 2 == 0); - while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + while (_running && samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { if (_blockPos[0] == _blockAlign) { // read block header _status.ima_ch[0].last = _stream->readSint16LE(); _status.ima_ch[0].stepIndex = _stream->readSint16LE() << 6; _blockPos[0] = 4; - // Get current filter - _currentFilterId = _nextFilterId; - //_nextFilterId = -1; // FIXME: the filter id should be recomputed based on the sound entry status for each block - - // No filter: skip decoding - if (_currentFilterId == -1) - break; - - // Compute step adjustment - _stepAdjust1 = p1s[_currentFilterId]; - _stepAdjust2 = p2s[_currentFilterId]; + // Smooth transition, if requested + // the original game clears kSoundFlagVolumeChanging here if _nextVolume == _smoothChangeTarget + if (_nextVolume != _smoothChangeTarget) { + if (_volumeHoldBlocks > 3) { + if (_nextVolume < _smoothChangeTarget) + ++_nextVolume; + else + --_nextVolume; + _volumeHoldBlocks = 0; + if (_nextVolume == 0) { + _running = false; + break; + } + } else { + _volumeHoldBlocks++; + } + } + + // Get current volume + _currentVolume = _nextVolume; } for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { @@ -397,26 +404,28 @@ public: idx = data >> 4; step = stepTable[idx + _status.ima_ch[0].stepIndex / 4]; sample = CLIP(imaTable[idx + _status.ima_ch[0].stepIndex / 4] + _status.ima_ch[0].last, -32767, 32767); - buffer[samples] = (_stepAdjust2 * sample) >> _stepAdjust1; + buffer[samples] = (sample * _currentVolume) >> 4; // Second nibble idx = data & 0xF; _status.ima_ch[0].stepIndex = stepTable[idx + step / 4]; _status.ima_ch[0].last = CLIP(imaTable[idx + step / 4] + sample, -32767, 32767); - buffer[samples + 1] = (_stepAdjust2 * _status.ima_ch[0].last) >> _stepAdjust1; + buffer[samples + 1] = (_status.ima_ch[0].last * _currentVolume) >> 4; } } return samples; } - void setFilterId(int32 filterId) { _nextFilterId = filterId; } + void setVolume(uint32 newVolume) { _smoothChangeTarget = _nextVolume = newVolume; } + void setVolumeSmoothly(uint32 newVolume) { _smoothChangeTarget = newVolume; } private: - int32 _currentFilterId; - int32 _nextFilterId; // the sound filter id, -1 for none - int32 _stepAdjust1; - int32 _stepAdjust2; + uint32 _currentVolume; + uint32 _nextVolume; + uint32 _smoothChangeTarget; + uint32 _volumeHoldBlocks; // smooth change of volume keeps volume on hold for 4 blocks = 133ms for every value; this is the counter + bool _running; }; ////////////////////////////////////////////////////////////////////////// @@ -442,8 +451,8 @@ void SimpleSound::loadHeader(Common::SeekableReadStream *in) { _blockSize = _size / _blocks; } -LastExpress_ADPCMStream *SimpleSound::makeDecoder(Common::SeekableReadStream *in, uint32 size, int32 filterId) const { - return new LastExpress_ADPCMStream(in, DisposeAfterUse::YES, size, _blockSize, filterId); +LastExpress_ADPCMStream *SimpleSound::makeDecoder(Common::SeekableReadStream *in, uint32 size, uint32 volume) const { + return new LastExpress_ADPCMStream(in, DisposeAfterUse::YES, size, _blockSize, volume); } void SimpleSound::play(Audio::AudioStream *as, DisposeAfterUse::Flag autofreeStream) { @@ -461,7 +470,7 @@ StreamedSound::~StreamedSound() { _as = NULL; } -bool StreamedSound::load(Common::SeekableReadStream *stream, int32 filterId) { +bool StreamedSound::load(Common::SeekableReadStream *stream, uint32 volume) { if (!stream) return false; @@ -474,7 +483,7 @@ bool StreamedSound::load(Common::SeekableReadStream *stream, int32 filterId) { delete _as; } // Start decoding the input stream - _as = makeDecoder(stream, _size, filterId); + _as = makeDecoder(stream, _size, volume); // Start playing the decoded audio stream play(_as, DisposeAfterUse::NO); @@ -491,11 +500,18 @@ bool StreamedSound::isFinished() { return !g_system->getMixer()->isSoundHandleActive(_handle); } -void StreamedSound::setFilterId(int32 filterId) { +void StreamedSound::setVolume(uint32 newVolume) { + if (!_as) + return; + + _as->setVolume(newVolume); +} + +void StreamedSound::setVolumeSmoothly(uint32 newVolume) { if (!_as) return; - _as->setFilterId(filterId); + _as->setVolumeSmoothly(newVolume); } ////////////////////////////////////////////////////////////////////////// @@ -531,8 +547,7 @@ void AppendableSound::queueBuffer(Common::SeekableReadStream *bufferIn) { // Setup the ADPCM decoder uint32 sizeIn = (uint32)bufferIn->size(); - LastExpress_ADPCMStream *adpcm = makeDecoder(bufferIn, sizeIn); - adpcm->setFilterId(16); + LastExpress_ADPCMStream *adpcm = makeDecoder(bufferIn, sizeIn, kVolumeFull); // Queue the stream _as->queueAudioStream(adpcm); diff --git a/engines/lastexpress/data/snd.h b/engines/lastexpress/data/snd.h index 23aae905ac..f6f2c5ec04 100644 --- a/engines/lastexpress/data/snd.h +++ b/engines/lastexpress/data/snd.h @@ -61,7 +61,7 @@ public: protected: void loadHeader(Common::SeekableReadStream *in); - LastExpress_ADPCMStream *makeDecoder(Common::SeekableReadStream *in, uint32 size, int32 filterId = -1) const; + LastExpress_ADPCMStream *makeDecoder(Common::SeekableReadStream *in, uint32 size, uint32 volume) const; void play(Audio::AudioStream *as, DisposeAfterUse::Flag autofreeStream); uint32 _size; ///< data size @@ -78,10 +78,11 @@ public: StreamedSound(); ~StreamedSound(); - bool load(Common::SeekableReadStream *stream, int32 filterId = -1); + bool load(Common::SeekableReadStream *stream, uint32 volume); virtual bool isFinished(); - void setFilterId(int32 filterId); + void setVolume(uint32 newVolume); + void setVolumeSmoothly(uint32 newVolume); private: LastExpress_ADPCMStream *_as; diff --git a/engines/lastexpress/sound/entry.cpp b/engines/lastexpress/sound/entry.cpp index ea9b767ec8..9f7395bdd3 100644 --- a/engines/lastexpress/sound/entry.cpp +++ b/engines/lastexpress/sound/entry.cpp @@ -54,7 +54,6 @@ SoundEntry::SoundEntry(LastExpressEngine *engine) : _engine(engine) { _field_34 = 0; _field_38 = 0; - _field_3C = 0; _volumeWithoutNIS = 0; _entity = kEntityPlayer; _initTimeMS = 0; @@ -90,9 +89,9 @@ void SoundEntry::open(Common::String name, SoundFlag flag, int priority) { } void SoundEntry::close() { - _status |= kSoundFlagCloseRequested; - + _status |= kSoundFlagClosed; // Loop until ready + // _status |= kSoundFlagCloseRequested; //while (!(_status & kSoundFlagClosed) && !(getSoundQueue()->getFlag() & 8) && (getSoundQueue()->getFlag() & 1)) // ; // empty loop body @@ -150,12 +149,12 @@ void SoundEntry::setType(SoundFlag flag) { case kSoundTypeAmbient: { SoundEntry *previous2 = getSoundQueue()->getEntry(kSoundType2); if (previous2) - previous2->update(0); + previous2->fade(); SoundEntry *previous = getSoundQueue()->getEntry(kSoundType1); if (previous) { previous->setType(kSoundType2); - previous->update(0); + previous->fade(); } _type = kSoundType1; @@ -166,7 +165,7 @@ void SoundEntry::setType(SoundFlag flag) { SoundEntry *previous = getSoundQueue()->getEntry(kSoundType3); if (previous) { previous->setType(kSoundType4); - previous->update(0); + previous->fade(); } _type = kSoundType11; @@ -230,28 +229,28 @@ void SoundEntry::loadStream(Common::String name) { _stream = getArchive("DEFAULT.SND"); if (!_stream) - _status = kSoundFlagCloseRequested; + _status = kSoundFlagClosed; } -void SoundEntry::update(uint val) { +void SoundEntry::setVolumeSmoothly(SoundFlag newVolume) { + assert((newVolume & kSoundVolumeMask) == newVolume); + if (_status & kSoundFlagFading) return; - int value2 = val; - - _status |= kSoundFlagVolumeChanging; - - if (val) { - if (getSoundQueue()->getFlag() & 32) { - _volumeWithoutNIS = val; - value2 = val / 2 + 1; - } + // the original game sets kSoundFlagVolumeChanging here + uint32 requestedVolume = (uint32)newVolume; - _field_3C = value2; - } else { - _field_3C = 0; + if (newVolume == kVolumeNone) { _status |= kSoundFlagFading; + } else if (getSoundQueue()->getFlag() & 32) { + _volumeWithoutNIS = requestedVolume; + requestedVolume = requestedVolume / 2 + 1; } + + _status = (_status & ~kSoundVolumeMask) | requestedVolume; + if (_soundStream) + _soundStream->setVolumeSmoothly(requestedVolume); } bool SoundEntry::updateSound() { @@ -281,7 +280,7 @@ bool SoundEntry::updateSound() { if (!(_status & kSoundFlagFixedVolume)) { if (_entity) { if (_entity < 0x80) { - updateEntryFlag(getSound()->getSoundFlag(_entity)); + setVolume(getSound()->getSoundFlag(_entity)); } } } @@ -295,19 +294,21 @@ bool SoundEntry::updateSound() { return result; } -void SoundEntry::updateEntryFlag(SoundFlag flag) { - if (flag) { +void SoundEntry::setVolume(SoundFlag newVolume) { + assert((newVolume & kSoundVolumeMask) == newVolume); + + if (newVolume) { if (getSoundQueue()->getFlag() & 0x20 && _type != kSoundType9 && _type != kSoundType7) - update(flag); + setVolumeSmoothly(newVolume); else - _status = flag + (_status & ~kSoundVolumeMask); + _status = newVolume + (_status & ~kSoundVolumeMask); } else { _volumeWithoutNIS = 0; _status |= kSoundFlagMuteRequested; _status &= ~(kSoundFlagVolumeChanging | kSoundVolumeMask); } if (_soundStream) - _soundStream->setFilterId(_status & kSoundVolumeMask); + _soundStream->setVolume(_status & kSoundVolumeMask); } void SoundEntry::adjustVolumeIfNISPlaying() { @@ -332,7 +333,7 @@ void SoundEntry::initDelayedActivate(unsigned activateDelay) { } void SoundEntry::reset() { - _status |= kSoundFlagCloseRequested; + _status |= kSoundFlagClosed; _entity = kEntityPlayer; if (_stream) { diff --git a/engines/lastexpress/sound/entry.h b/engines/lastexpress/sound/entry.h index fa970cd097..938bbaf19a 100644 --- a/engines/lastexpress/sound/entry.h +++ b/engines/lastexpress/sound/entry.h @@ -29,10 +29,11 @@ uint32 {4} - type uint32 {4} - blockCount uint32 {4} - time - uint32 {4} - ?? - uint32 {4} - ?? + uint32 {4} - LastExpress_ADPCMStream::_volumeHoldBlocks + (useless since the target volume is not saved) + uint32 {4} - ?? [copy of a field below with no references] uint32 {4} - entity - uint32 {4} - ?? + uint32 {4} - activate delay in sound ticks (30Hz timer) uint32 {4} - priority char {16} - name 1 char {16} - name 2 @@ -51,14 +52,15 @@ uint32 {4} - ?? uint32 {4} - archive structure pointer uint32 {4} - ?? - uint32 {4} - ?? - uint32 {4} - ?? - uint32 {4} - ?? + uint32 {4} - LastExpress_ADPCMStream::_volumeHoldBlocks + (used for smooth change of volume) + uint32 {4} - ?? [no references except for save/load] + uint32 {4} - target value for smooth change of volume uint32 {4} - base volume if NIS is playing (the actual volume is reduced in half for non-NIS sounds; this is used to restore the volume after NIS ends) uint32 {4} - entity - uint32 {4} - ?? + uint32 {4} - activate time in sound ticks (30Hz timer) uint32 {4} - priority char {16} - name 1 char {16} - name 2 @@ -91,10 +93,13 @@ public: void play(); void reset(); bool isFinished(); - void update(uint val); + void setVolumeSmoothly(SoundFlag newVolume); + // setVolumeSmoothly() treats kVolumeNone in a special way; + // fade() terminates the stream after the transition + void fade() { setVolumeSmoothly(kVolumeNone); } bool updateSound(); void adjustVolumeIfNISPlaying(); - void updateEntryFlag(SoundFlag flag); + void setVolume(SoundFlag newVolume); // activateDelay is measured in main ticks, 15Hz timer void initDelayedActivate(unsigned activateDelay); @@ -139,7 +144,6 @@ private: //int _archive; int _field_34; int _field_38; - int _field_3C; int _volumeWithoutNIS; EntityIndex _entity; // The original game uses one variable _activateTime = _initTime + _activateDelay diff --git a/engines/lastexpress/sound/queue.cpp b/engines/lastexpress/sound/queue.cpp index 5e8bf03b4a..8eef091f51 100644 --- a/engines/lastexpress/sound/queue.cpp +++ b/engines/lastexpress/sound/queue.cpp @@ -87,7 +87,7 @@ void SoundQueue::updateQueue() { getSound()->playLoopingSound(0x45); } else { if (getSound()->getData1() && getSound()->getData2() >= getSound()->getData1()) { - entry->update(getSound()->getData0()); + entry->setVolumeSmoothly((SoundFlag)getSound()->getData0()); getSound()->setData1(0); } } @@ -174,7 +174,7 @@ void SoundQueue::clearQueue() { ////////////////////////////////////////////////////////////////////////// void SoundQueue::clearStatus() { for (Common::List::iterator i = _soundList.begin(); i != _soundList.end(); ++i) - (*i)->addStatusFlag(kSoundFlagCloseRequested); + (*i)->addStatusFlag(kSoundFlagClosed); } ////////////////////////////////////////////////////////////////////////// @@ -189,7 +189,7 @@ void SoundQueue::setupEntry(SoundType type, EntityIndex index) { void SoundQueue::processEntry(EntityIndex entity) { SoundEntry *entry = getEntry(entity); if (entry) { - entry->update(0); + entry->fade(); entry->setEntity(kEntityPlayer); } } @@ -197,13 +197,13 @@ void SoundQueue::processEntry(EntityIndex entity) { void SoundQueue::processEntry(SoundType type) { SoundEntry *entry = getEntry(type); if (entry) - entry->update(0); + entry->fade(); } void SoundQueue::processEntry(Common::String filename) { SoundEntry *entry = getEntry(filename); if (entry) { - entry->update(0); + entry->fade(); entry->setEntity(kEntityPlayer); } } diff --git a/engines/lastexpress/sound/sound.cpp b/engines/lastexpress/sound/sound.cpp index 59e69613b4..b69121bbb5 100644 --- a/engines/lastexpress/sound/sound.cpp +++ b/engines/lastexpress/sound/sound.cpp @@ -1380,11 +1380,11 @@ void SoundManager::playLoopingSound(int param) { playSoundWithSubtitles(tmp, kSoundTypeAmbient | kSoundFlagLooped | kVolume1, kEntitySteam); if (entry) - entry->update(0); + entry->fade(); SoundEntry *entry1 = _queue->getEntry(kSoundType1); if (entry1) - entry1->update(7); + entry1->setVolumeSmoothly(kVolume7); } } } -- cgit v1.2.3