mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 21:59:17 +00:00
LASTEXPRESS: support for delay-activated sounds
Not very obvious, but noticeable e.g. when knocking on harem doors. I suppose this is the problem that wiki describes as "improper triggering of actions on sound end".
This commit is contained in:
parent
cc5d858169
commit
29f6ce1d9a
@ -55,16 +55,15 @@ SoundEntry::SoundEntry(LastExpressEngine *engine) : _engine(engine) {
|
||||
_field_34 = 0;
|
||||
_field_38 = 0;
|
||||
_field_3C = 0;
|
||||
_variant = 0;
|
||||
_volumeWithoutNIS = 0;
|
||||
_entity = kEntityPlayer;
|
||||
_field_48 = 0;
|
||||
_initTimeMS = 0;
|
||||
_activateDelayMS = 0;
|
||||
_priority = 0;
|
||||
|
||||
_subtitle = NULL;
|
||||
|
||||
_soundStream = NULL;
|
||||
|
||||
_queued = false;
|
||||
}
|
||||
|
||||
SoundEntry::~SoundEntry() {
|
||||
@ -123,27 +122,17 @@ void SoundEntry::play() {
|
||||
if (!_soundStream)
|
||||
_soundStream = new StreamedSound();
|
||||
|
||||
// Compute current filter id
|
||||
int32 filterId = _status & kSoundVolumeMask;
|
||||
// TODO adjust status (based on stepIndex)
|
||||
_stream->seek(0);
|
||||
|
||||
if (_queued) {
|
||||
_soundStream->setFilterId(filterId);
|
||||
} else {
|
||||
_stream->seek(0);
|
||||
|
||||
// Load the stream and start playing
|
||||
_soundStream->load(_stream, filterId);
|
||||
|
||||
_queued = true;
|
||||
}
|
||||
// Load the stream and start playing
|
||||
_soundStream->load(_stream, _status & kSoundVolumeMask);
|
||||
}
|
||||
|
||||
bool SoundEntry::isFinished() {
|
||||
if (!_stream)
|
||||
return true;
|
||||
|
||||
if (!_soundStream || !_queued)
|
||||
if (!_soundStream)
|
||||
return false;
|
||||
|
||||
// TODO check that all data has been queued
|
||||
@ -254,8 +243,8 @@ void SoundEntry::update(uint val) {
|
||||
|
||||
if (val) {
|
||||
if (getSoundQueue()->getFlag() & 32) {
|
||||
_variant = val;
|
||||
value2 = val * 2 + 1;
|
||||
_volumeWithoutNIS = val;
|
||||
value2 = val / 2 + 1;
|
||||
}
|
||||
|
||||
_field_3C = value2;
|
||||
@ -266,7 +255,7 @@ void SoundEntry::update(uint val) {
|
||||
}
|
||||
|
||||
bool SoundEntry::updateSound() {
|
||||
assert(_name2.size() <= 16);
|
||||
assert(_name2.size() < 16);
|
||||
|
||||
bool result;
|
||||
char sub[16];
|
||||
@ -275,15 +264,16 @@ bool SoundEntry::updateSound() {
|
||||
result = false;
|
||||
} else {
|
||||
if (_status & kSoundFlagDelayedActivate) {
|
||||
if (_field_48 <= getSound()->getData2()) {
|
||||
_status |= kSoundFlagPlayRequested;
|
||||
// counter overflow is processed correctly
|
||||
if (_engine->_system->getMillis() - _initTimeMS >= _activateDelayMS) {
|
||||
_status &= ~kSoundFlagDelayedActivate;
|
||||
strcpy(sub, _name2.c_str());
|
||||
play();
|
||||
|
||||
// FIXME: Rewrite and document expected behavior
|
||||
int l = strlen(sub) + 1;
|
||||
if (l - 1 > 4)
|
||||
sub[l - (1 + 4)] = 0;
|
||||
// drop .SND extension
|
||||
strcpy(sub, _name2.c_str());
|
||||
int l = _name2.size();
|
||||
if (l > 4)
|
||||
sub[l - 4] = 0;
|
||||
showSubtitle(sub);
|
||||
}
|
||||
} else {
|
||||
@ -312,25 +302,33 @@ void SoundEntry::updateEntryFlag(SoundFlag flag) {
|
||||
else
|
||||
_status = flag + (_status & ~kSoundVolumeMask);
|
||||
} else {
|
||||
_variant = 0;
|
||||
_volumeWithoutNIS = 0;
|
||||
_status |= kSoundFlagMuteRequested;
|
||||
_status &= ~(kSoundFlagVolumeChanging | kSoundVolumeMask);
|
||||
}
|
||||
if (_soundStream)
|
||||
_soundStream->setFilterId(_status & kSoundVolumeMask);
|
||||
}
|
||||
|
||||
void SoundEntry::updateState() {
|
||||
void SoundEntry::adjustVolumeIfNISPlaying() {
|
||||
if (getSoundQueue()->getFlag() & 32) {
|
||||
if (_type != kSoundType9 && _type != kSoundType7 && _type != kSoundType5) {
|
||||
uint32 variant = _status & kSoundVolumeMask;
|
||||
uint32 baseVolume = _status & kSoundVolumeMask;
|
||||
uint32 actualVolume = baseVolume / 2 + 1;
|
||||
|
||||
assert((actualVolume & kSoundVolumeMask) == actualVolume);
|
||||
|
||||
_volumeWithoutNIS = baseVolume;
|
||||
_status &= ~kSoundVolumeMask;
|
||||
|
||||
_variant = variant;
|
||||
_status |= variant * 2 + 1;
|
||||
_status |= actualVolume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_status |= kSoundFlagPlayRequested;
|
||||
void SoundEntry::initDelayedActivate(unsigned activateDelay) {
|
||||
_initTimeMS = _engine->_system->getMillis();
|
||||
_activateDelayMS = activateDelay * 1000 / 15;
|
||||
_status |= kSoundFlagDelayedActivate;
|
||||
}
|
||||
|
||||
void SoundEntry::reset() {
|
||||
@ -375,10 +373,18 @@ void SoundEntry::saveLoadWithSerializer(Common::Serializer &s) {
|
||||
s.syncAsUint32LE(_field_38); // field_14;
|
||||
s.syncAsUint32LE(_entity);
|
||||
|
||||
uint32 delta = (uint32)_field_48 - getSound()->getData2();
|
||||
if (delta > 0x8000000u) // sanity check against overflow
|
||||
delta = 0;
|
||||
s.syncAsUint32LE(delta);
|
||||
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);
|
||||
}
|
||||
|
||||
s.syncAsUint32LE(_priority);
|
||||
|
||||
|
@ -54,7 +54,9 @@
|
||||
uint32 {4} - ??
|
||||
uint32 {4} - ??
|
||||
uint32 {4} - ??
|
||||
uint32 {4} - ??
|
||||
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} - priority
|
||||
@ -91,8 +93,10 @@ public:
|
||||
bool isFinished();
|
||||
void update(uint val);
|
||||
bool updateSound();
|
||||
void updateState();
|
||||
void adjustVolumeIfNISPlaying();
|
||||
void updateEntryFlag(SoundFlag flag);
|
||||
// activateDelay is measured in main ticks, 15Hz timer
|
||||
void initDelayedActivate(unsigned activateDelay);
|
||||
|
||||
// Subtitles
|
||||
void showSubtitle(Common::String filename);
|
||||
@ -101,10 +105,9 @@ public:
|
||||
void saveLoadWithSerializer(Common::Serializer &ser);
|
||||
|
||||
// Accessors
|
||||
void setStatus(uint32 status) { _status = status; }
|
||||
void addStatusFlag(SoundFlag flag) { _status |= flag; }
|
||||
void setType(SoundType type) { _type = type; }
|
||||
void setEntity(EntityIndex entity) { _entity = entity; }
|
||||
void setField48(int val) { _field_48 = val; }
|
||||
|
||||
uint32 getStatus() { return _status; }
|
||||
SoundType getType() { return _type; }
|
||||
@ -137,9 +140,13 @@ private:
|
||||
int _field_34;
|
||||
int _field_38;
|
||||
int _field_3C;
|
||||
int _variant;
|
||||
int _volumeWithoutNIS;
|
||||
EntityIndex _entity;
|
||||
int _field_48;
|
||||
// The original game uses one variable _activateTime = _initTime + _activateDelay
|
||||
// and measures everything in sound ticks (30Hz timer).
|
||||
// We use milliseconds and two variables to deal with possible overflow
|
||||
// (probably paranoid, but nothing really complicated).
|
||||
uint32 _initTimeMS, _activateDelayMS;
|
||||
uint32 _priority;
|
||||
Common::String _name1; //char[16];
|
||||
Common::String _name2; //char[16];
|
||||
@ -147,7 +154,6 @@ private:
|
||||
SubtitleEntry *_subtitle;
|
||||
|
||||
// Sound buffer & stream
|
||||
bool _queued;
|
||||
StreamedSound *_soundStream; // the filtered sound stream
|
||||
|
||||
void setType(SoundFlag flag);
|
||||
|
@ -116,9 +116,6 @@ void SoundQueue::updateQueue() {
|
||||
it = _soundList.reverse_erase(it);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Queue the entry data, applying filtering
|
||||
entry->play();
|
||||
}
|
||||
|
||||
// Original update the current entry, loading another set of samples to be decoded
|
||||
@ -177,7 +174,7 @@ void SoundQueue::clearQueue() {
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void SoundQueue::clearStatus() {
|
||||
for (Common::List<SoundEntry *>::iterator i = _soundList.begin(); i != _soundList.end(); ++i)
|
||||
(*i)->setStatus((*i)->getStatus() | kSoundFlagCloseRequested);
|
||||
(*i)->addStatusFlag(kSoundFlagCloseRequested);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -135,7 +135,7 @@ SoundManager::~SoundManager() {
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Sound-related functions
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void SoundManager::playSound(EntityIndex entity, Common::String filename, SoundFlag flag, byte a4) {
|
||||
void SoundManager::playSound(EntityIndex entity, Common::String filename, SoundFlag flag, byte activateDelay) {
|
||||
if (_queue->isBuffered(entity) && entity && entity < kEntityTrain)
|
||||
_queue->removeFromQueue(entity);
|
||||
|
||||
@ -145,20 +145,26 @@ void SoundManager::playSound(EntityIndex entity, Common::String filename, SoundF
|
||||
if (!filename.contains('.'))
|
||||
filename += ".SND";
|
||||
|
||||
if (!playSoundWithSubtitles(filename, currentFlag, entity, a4))
|
||||
if (!playSoundWithSubtitles(filename, currentFlag, entity, activateDelay))
|
||||
if (entity)
|
||||
getSavePoints()->push(kEntityPlayer, entity, kActionEndSound);
|
||||
}
|
||||
|
||||
bool SoundManager::playSoundWithSubtitles(Common::String filename, uint32 flag, EntityIndex entity, byte a4) {
|
||||
bool SoundManager::playSoundWithSubtitles(Common::String filename, uint32 flag, EntityIndex entity, unsigned activateDelay) {
|
||||
SoundEntry *entry = new SoundEntry(_engine);
|
||||
|
||||
entry->open(filename, (SoundFlag)flag, 30);
|
||||
entry->setEntity(entity);
|
||||
|
||||
if (a4) {
|
||||
entry->setField48(_data2 + 2 * a4);
|
||||
entry->setStatus(entry->getStatus() | kSoundFlagDelayedActivate);
|
||||
// BUG: the original game skips adjustVolumeIfNISPlaying() for delayed-activate sounds.
|
||||
// (the original code is structured in a slightly different way)
|
||||
// Not sure whether it can be actually triggered,
|
||||
// most delayed-activate sounds originate from user actions,
|
||||
// all user actions are disabled while NIS is playing.
|
||||
entry->adjustVolumeIfNISPlaying();
|
||||
|
||||
if (activateDelay) {
|
||||
entry->initDelayedActivate(activateDelay);
|
||||
} else {
|
||||
// Get subtitles name
|
||||
uint32 size = filename.size();
|
||||
@ -166,7 +172,7 @@ bool SoundManager::playSoundWithSubtitles(Common::String filename, uint32 flag,
|
||||
filename.deleteLastChar();
|
||||
|
||||
entry->showSubtitle(filename);
|
||||
entry->updateState();
|
||||
entry->play();
|
||||
}
|
||||
|
||||
// Add entry to sound list
|
||||
@ -175,7 +181,7 @@ bool SoundManager::playSoundWithSubtitles(Common::String filename, uint32 flag,
|
||||
return (entry->getType() != kSoundTypeNone);
|
||||
}
|
||||
|
||||
void SoundManager::playSoundEvent(EntityIndex entity, byte action, byte a3) {
|
||||
void SoundManager::playSoundEvent(EntityIndex entity, byte action, byte activateDelay) {
|
||||
int values[5];
|
||||
|
||||
if (getEntityData(entity)->car != getEntityData(kEntityPlayer)->car)
|
||||
@ -193,14 +199,14 @@ void SoundManager::playSoundEvent(EntityIndex entity, byte action, byte a3) {
|
||||
|
||||
if (_param3 > 7) {
|
||||
_data0 = (uint)_param3;
|
||||
_data1 = _data2 + 2 * a3;
|
||||
_data1 = _data2 + 2 * activateDelay;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 37:
|
||||
_data0 = 7;
|
||||
_data1 = _data2 + 2 * a3;
|
||||
_data1 = _data2 + 2 * activateDelay;
|
||||
break;
|
||||
|
||||
case 150:
|
||||
@ -298,7 +304,7 @@ void SoundManager::playSoundEvent(EntityIndex entity, byte action, byte a3) {
|
||||
}
|
||||
|
||||
if (_action && flag)
|
||||
playSoundWithSubtitles(Common::String::format("LIB%03d.SND", _action), flag, kEntityPlayer, a3);
|
||||
playSoundWithSubtitles(Common::String::format("LIB%03d.SND", _action), flag, kEntityPlayer, activateDelay);
|
||||
}
|
||||
|
||||
void SoundManager::playSteam(CityIndex index) {
|
||||
|
@ -38,9 +38,10 @@ public:
|
||||
~SoundManager();
|
||||
|
||||
// Sound playing
|
||||
void playSound(EntityIndex entity, Common::String filename, SoundFlag flag = kSoundVolumeEntityDefault, byte a4 = 0);
|
||||
bool playSoundWithSubtitles(Common::String filename, uint32 flag, EntityIndex entity, byte a4 = 0);
|
||||
void playSoundEvent(EntityIndex entity, byte action, byte a3 = 0);
|
||||
// the original game uses byte in playSound but unsigned in playSoundWithSubtitles for activateDelay, no idea why
|
||||
void playSound(EntityIndex entity, Common::String filename, SoundFlag flag = kSoundVolumeEntityDefault, byte activateDelay = 0);
|
||||
bool playSoundWithSubtitles(Common::String filename, uint32 flag, EntityIndex entity, unsigned activateDelay = 0);
|
||||
void playSoundEvent(EntityIndex entity, byte action, byte activateDelay = 0);
|
||||
void playDialog(EntityIndex entity, EntityIndex entityDialog, SoundFlag flag, byte a4);
|
||||
void playSteam(CityIndex index);
|
||||
void playFightSound(byte action, byte a4);
|
||||
|
Loading…
Reference in New Issue
Block a user