mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-26 22:38:17 +00:00
BLADERUNNER: Remove timerprocs for next track and fadeout
The timers are replaced with "application" timers, which are checked in AudioMixer::tick() The timerprocs for next and fadeout could potentially cause a game freeze. This is because they could, under certain race conditions, come to a deadend while trying to lock the mutexes for access to AudioMixer and Music methods. The particular use case (non-deterministic) for this was loading a save game while playing a game that had a music track playing (eg. at MA05, McCoy's balcony). Here's a rough explanation of a possible lock situation (others could also be possible) The "Main Thread" and "SDLTimer" threads (as reported from MSVC debug) could become hardlocked. ::Main Thread:: A. LoadGame() calls _music->stop(0u); A1. Locks a Music::_mutex A2. Calls vm->_audioMixer->stop() A3. AudioMixer::stop() locks AudioMixer::_mutex [THIS DONE] A4. calls the endCallback for the music which is Music::ended() at Music::ended, A41. lock a Music::_mutex (presumably this is ok, even though it's a second lock, because it's the same thread(?)) A42. call installTimerProc() A421. which tries to lock DefaultTimerManager::_mutex from default-timer [THIS FREEZES] - conflict with B11 ::SDLTimer:: B. calls timer_handler, B1. calls DefaultTimerManager::handler() B11. locks the DefaultTimerManager::_mutex from default-timer [THIS IS DONE] B12. we try to service the callbacks from TimerProcs that are currently entered in the TimerSlots queue B121. Such a slot is the AudioMixer BladeRunnerAudioMixerTimer, which has the AudioMixer::timerCallback() B1211. which calls the at AudioMixer::tick(). tick() tries to lock the AudioMixer::_mutex [THIS FREEZES] conflict with A3
This commit is contained in:
parent
346fe3722d
commit
1a25c2b69a
@ -23,6 +23,7 @@
|
||||
#include "bladerunner/audio_mixer.h"
|
||||
|
||||
#include "bladerunner/bladerunner.h"
|
||||
#include "bladerunner/music.h"
|
||||
#include "bladerunner/time.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
@ -37,12 +38,19 @@ AudioMixer::AudioMixer(BladeRunnerEngine *vm) {
|
||||
for (int i = 0; i < kChannels; ++i) {
|
||||
_channels[i].isPresent = false;
|
||||
}
|
||||
#if !BLADERUNNER_ORIGINAL_BUGS
|
||||
for (int i = 0; i < kAudioMixerAppTimersNum; ++i) {
|
||||
_audioMixerAppTimers[i].started = false;
|
||||
_audioMixerAppTimers[i].lastFired = 0u;
|
||||
_audioMixerAppTimers[i].intervalMillis = 0u;
|
||||
}
|
||||
#endif // BLADERUNNER_ORIGINAL_BUGS
|
||||
_vm->getTimerManager()->installTimerProc(timerCallback, (1000 / kUpdatesPerSecond) * 1000, this, "BladeRunnerAudioMixerTimer");
|
||||
}
|
||||
|
||||
AudioMixer::~AudioMixer() {
|
||||
for (int i = 0; i < kChannels; ++i) {
|
||||
stop(i, 0);
|
||||
stop(i, 0u);
|
||||
}
|
||||
_vm->getTimerManager()->removeTimerProc(timerCallback);
|
||||
}
|
||||
@ -69,7 +77,7 @@ int AudioMixer::play(Audio::Mixer::SoundType type, Audio::RewindableAudioStream
|
||||
return -1;
|
||||
}
|
||||
//debug("Stopping lowest priority channel %d with lower prio %d!", lowestPriorityChannel, lowestPriority);
|
||||
stop(lowestPriorityChannel, 0);
|
||||
stop(lowestPriorityChannel, 0u);
|
||||
channel = lowestPriorityChannel;
|
||||
}
|
||||
|
||||
@ -82,6 +90,7 @@ int AudioMixer::playMusic(Audio::RewindableAudioStream *stream, int volume, void
|
||||
return playInChannel(kMusicChannel, Audio::Mixer::kMusicSoundType, stream, 100, false, volume, 0, endCallback, callbackData, trackDurationMs);
|
||||
}
|
||||
|
||||
// Note: time tends to be the requested time in seconds multiplied by 60u
|
||||
void AudioMixer::stop(int channel, uint32 time) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
@ -93,6 +102,7 @@ void AudioMixer::stop(int channel, uint32 time) {
|
||||
if (_channels[channel].sentToMixer) {
|
||||
_vm->_mixer->stopHandle(_channels[channel].handle);
|
||||
}
|
||||
|
||||
if (_channels[channel].endCallback != nullptr) {
|
||||
_channels[channel].endCallback(channel, _channels[channel].callbackData);
|
||||
}
|
||||
@ -151,6 +161,7 @@ void AudioMixer::timerCallback(void *self) {
|
||||
((AudioMixer *)self)->tick();
|
||||
}
|
||||
|
||||
// Note: time tends to be the requested time in seconds multiplied by 60u
|
||||
void AudioMixer::adjustVolume(int channel, int newVolume, uint32 time) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
@ -160,6 +171,7 @@ void AudioMixer::adjustVolume(int channel, int newVolume, uint32 time) {
|
||||
}
|
||||
}
|
||||
|
||||
// Note: time tends to be the requested time in seconds multiplied by 60u
|
||||
void AudioMixer::adjustPan(int channel, int newPan, uint32 time) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
@ -180,9 +192,11 @@ void AudioMixer::tick() {
|
||||
}
|
||||
|
||||
if (channel->volumeDelta != 0.0f) {
|
||||
// apply volumeDelta to volume (common use for adjustVolume or stop playing - ie mainly for fadeIn, fadeOut)
|
||||
channel->volume = CLIP(channel->volume + channel->volumeDelta, 0.0f, 100.0f);
|
||||
|
||||
if ((channel->volumeDelta < 0 && channel->volume <= channel->volumeTarget) || (channel->volumeDelta > 0 && channel->volume >= channel->volumeTarget)) {
|
||||
if ((channel->volumeDelta < 0 && channel->volume <= channel->volumeTarget)
|
||||
|| (channel->volumeDelta > 0 && channel->volume >= channel->volumeTarget)) {
|
||||
channel->volumeDelta = 0.0f;
|
||||
}
|
||||
|
||||
@ -191,11 +205,12 @@ void AudioMixer::tick() {
|
||||
}
|
||||
|
||||
if (channel->volume <= 0.0f) {
|
||||
stop(i, 0);
|
||||
stop(i, 0u);
|
||||
}
|
||||
}
|
||||
|
||||
if (channel->panDelta != 0.0) {
|
||||
// apply panDelta to pan (common use for adjusting pan)
|
||||
channel->pan = CLIP(channel->pan + channel->panDelta, -100.0f, 100.0f);
|
||||
|
||||
if ((channel->panDelta < 0 && channel->pan <= channel->panTarget) || (channel->panDelta > 0 && channel->pan >= channel->panTarget)) {
|
||||
@ -211,9 +226,57 @@ void AudioMixer::tick() {
|
||||
|| channel->stream->endOfStream()
|
||||
|| (!channel->sentToMixer && !channel->loop && _vm->_time->currentSystem() - channel->timeStarted >= channel->trackDurationMs)
|
||||
) {
|
||||
stop(i, 0);
|
||||
stop(i, 0u);
|
||||
}
|
||||
}
|
||||
|
||||
#if !BLADERUNNER_ORIGINAL_BUGS
|
||||
// piggyback the realtime triggered tick() actions, with a check for the virtual timers (app timers)
|
||||
for (int i = 0; i < kAudioMixerAppTimersNum; ++i) {
|
||||
if (_audioMixerAppTimers[i].started
|
||||
&& _vm->_time->currentSystem() - _audioMixerAppTimers[i].lastFired > _audioMixerAppTimers[i].intervalMillis) {
|
||||
// We actually need to have the _vm->_time->currentSystem() check in the if clause
|
||||
// and not use a var that stores the current time before we enter the loop
|
||||
// because the functions for these timers may affect the lastFired, by setting it to the a current system time
|
||||
// and then lastFired would have been greater than our stored system time here.
|
||||
_audioMixerAppTimers[i].lastFired = _vm->_time->currentSystem();
|
||||
switch (i) {
|
||||
case kAudioMixerAppTimerMusicNext:
|
||||
_vm->_music->next();
|
||||
break;
|
||||
case kAudioMixerAppTimerMusicFadeOut:
|
||||
_vm->_music->fadeOut();
|
||||
break;
|
||||
default:
|
||||
// error - but probably won't happen
|
||||
error("Unknown Audio Mixer App Timer Id");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !BLADERUNNER_ORIGINAL_BUGS
|
||||
}
|
||||
|
||||
#if !BLADERUNNER_ORIGINAL_BUGS
|
||||
void AudioMixer::startAppTimerProc(int audioMixAppTimerId, uint32 intervalMillis) {
|
||||
// Attempt to lock the mutex, since we reach here from another thread (main thread)
|
||||
Common::StackLock lock(_mutex);
|
||||
if (audioMixAppTimerId < 0 || audioMixAppTimerId >= kAudioMixerAppTimersNum) {
|
||||
return;
|
||||
}
|
||||
_audioMixerAppTimers[audioMixAppTimerId].started = true;
|
||||
_audioMixerAppTimers[audioMixAppTimerId].intervalMillis = intervalMillis;
|
||||
_audioMixerAppTimers[audioMixAppTimerId].lastFired = _vm->_time->currentSystem();
|
||||
}
|
||||
|
||||
void AudioMixer::stopAppTimerProc(int audioMixAppTimerId) {
|
||||
// Attempt to lock the mutex, since we reach here from another thread (main thread)
|
||||
Common::StackLock lock(_mutex);
|
||||
if (audioMixAppTimerId < 0 || audioMixAppTimerId >= kAudioMixerAppTimersNum) {
|
||||
return;
|
||||
}
|
||||
_audioMixerAppTimers[audioMixAppTimerId].started = false;
|
||||
}
|
||||
#endif // !BLADERUNNER_ORIGINAL_BUGS
|
||||
|
||||
} // End of namespace BladeRunner
|
||||
|
@ -32,6 +32,13 @@ namespace BladeRunner {
|
||||
|
||||
class BladeRunnerEngine;
|
||||
|
||||
#if !BLADERUNNER_ORIGINAL_BUG
|
||||
enum audioMixerAppTimers {
|
||||
kAudioMixerAppTimerMusicNext = 0,
|
||||
kAudioMixerAppTimerMusicFadeOut = 1
|
||||
};
|
||||
#endif
|
||||
|
||||
class AudioMixer {
|
||||
#if BLADERUNNER_ORIGINAL_BUGS
|
||||
static const int kChannels = 9;
|
||||
@ -41,6 +48,8 @@ class AudioMixer {
|
||||
static const int kChannels = 15;
|
||||
static const int kUsableChannels = 14;
|
||||
static const int kMusicChannel = 14;
|
||||
|
||||
static const int kAudioMixerAppTimersNum = 2;
|
||||
#endif // BLADERUNNER_ORIGINAL_BUGS
|
||||
static const int kUpdatesPerSecond = 40;
|
||||
|
||||
@ -68,17 +77,31 @@ class AudioMixer {
|
||||
Channel _channels[kChannels];
|
||||
Common::Mutex _mutex;
|
||||
|
||||
#if !BLADERUNNER_ORIGINAL_BUGS
|
||||
struct audioMixerAppTimer {
|
||||
bool started;
|
||||
uint32 intervalMillis; // expiration interval in milliseconds
|
||||
uint32 lastFired; // time of last time the timer expired in milliseconds
|
||||
};
|
||||
|
||||
audioMixerAppTimer _audioMixerAppTimers[kAudioMixerAppTimersNum];
|
||||
#endif // !BLADERUNNER_ORIGINAL_BUGS
|
||||
|
||||
public:
|
||||
AudioMixer(BladeRunnerEngine *vm);
|
||||
~AudioMixer();
|
||||
|
||||
int play(Audio::Mixer::SoundType type, Audio::RewindableAudioStream *stream, int priority, bool loop, int volume, int pan, void(*endCallback)(int, void *), void *callbackData, uint32 trackDurationMs);
|
||||
int playMusic(Audio::RewindableAudioStream *stream, int volume, void(*endCallback)(int, void *), void *callbackData, uint32 trackDurationMs);
|
||||
void stop(int channel, uint32 delay);
|
||||
void stop(int channel, uint32 time);
|
||||
|
||||
void adjustVolume(int channel, int newVolume, uint32 time);
|
||||
void adjustPan(int channel, int newPan, uint32 time);
|
||||
|
||||
#if !BLADERUNNER_ORIGINAL_BUGS
|
||||
void startAppTimerProc(int audioMixAppTimerId, uint32 intervalMillis);
|
||||
void stopAppTimerProc(int audioMixAppTimerId);
|
||||
#endif // !BLADERUNNER_ORIGINAL_BUGS
|
||||
// TODO Are these completely unused?
|
||||
// void resume(int channel, uint32 delay);
|
||||
// void pause(int channel, uint32 delay);
|
||||
|
@ -45,16 +45,22 @@ Music::Music(BladeRunnerEngine *vm) {
|
||||
}
|
||||
|
||||
Music::~Music() {
|
||||
stop(0);
|
||||
stop(0u);
|
||||
while (isPlaying()) {
|
||||
// wait for the mixer to finish
|
||||
}
|
||||
|
||||
#if BLADERUNNER_ORIGINAL_BUGS
|
||||
_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
|
||||
_vm->getTimerManager()->removeTimerProc(timerCallbackNext);
|
||||
#else
|
||||
// probably not really needed, but tidy up anyway
|
||||
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
|
||||
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicNext);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Music::play(const Common::String &trackName, int volume, int pan, int32 timeFadeIn, int32 timePlay, int loop, int32 timeFadeOut) {
|
||||
bool Music::play(const Common::String &trackName, int volume, int pan, int32 timeFadeInSeconds, int32 timePlaySeconds, int loop, int32 timeFadeOutSeconds) {
|
||||
//Common::StackLock lock(_mutex);
|
||||
|
||||
if (_musicVolume <= 0) {
|
||||
@ -63,34 +69,48 @@ bool Music::play(const Common::String &trackName, int volume, int pan, int32 tim
|
||||
|
||||
int volumeAdjusted = volume * _musicVolume / 100;
|
||||
int volumeStart = volumeAdjusted;
|
||||
if (timeFadeIn > 0) {
|
||||
if (timeFadeInSeconds > 0) {
|
||||
volumeStart = 1;
|
||||
}
|
||||
|
||||
// Queuing mechanism:
|
||||
// if a music track is already playing, then:
|
||||
// if the requested track is a different track
|
||||
// queue it as "next", to play after the current one.
|
||||
// However, if a "next" track already exists,
|
||||
// then stop the _current track after 2 seconds (force stop current playing track)
|
||||
// Also the previous "next" track still gets replaced by the new requested one.
|
||||
// This can be best test at Animoid Row, Hawker's Circle moving from Izo's Pawn Shop to the Bar.
|
||||
// if the requested track is the same as the currently playing,
|
||||
// update the loop int value of the _current to the new one
|
||||
// and adjust its fadeIn and balance/pan
|
||||
// In these both cases above, the _current track is not (yet) changed.
|
||||
if (isPlaying()) {
|
||||
if (!_current.name.equalsIgnoreCase(trackName)) {
|
||||
_next.name = trackName;
|
||||
_next.volume = volume;
|
||||
_next.pan = pan;
|
||||
_next.timeFadeIn = timeFadeIn;
|
||||
_next.timePlay = timePlay;
|
||||
_next.volume = volume; // Don't store the adjustedVolume - This is a "target" value for the volume
|
||||
_next.pan = pan; // This is a "target" value for the pan (balance)
|
||||
_next.timeFadeInSeconds = timeFadeInSeconds;
|
||||
_next.timePlaySeconds = timePlaySeconds;
|
||||
_next.loop = loop;
|
||||
_next.timeFadeOut = timeFadeOut;
|
||||
_next.timeFadeOutSeconds = timeFadeOutSeconds;
|
||||
if (_isNextPresent) {
|
||||
stop(2);
|
||||
stop(2u);
|
||||
}
|
||||
_isNextPresent = true;
|
||||
} else {
|
||||
_current.loop = loop;
|
||||
if (timeFadeIn < 0) {
|
||||
timeFadeIn = 0;
|
||||
if (timeFadeInSeconds < 0) {
|
||||
timeFadeInSeconds = 0;
|
||||
}
|
||||
adjustVolume(volumeAdjusted, timeFadeIn);
|
||||
adjustPan(volumeAdjusted, timeFadeIn);
|
||||
adjustVolume(volumeAdjusted, timeFadeInSeconds);
|
||||
adjustPan(volumeAdjusted, timeFadeInSeconds);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we reach here, there is no music track currently playing
|
||||
// So we load it from the game's resources
|
||||
_data = getData(trackName);
|
||||
if (_data == nullptr) {
|
||||
return false;
|
||||
@ -107,28 +127,40 @@ bool Music::play(const Common::String &trackName, int volume, int pan, int32 tim
|
||||
|
||||
return false;
|
||||
}
|
||||
if (timeFadeIn > 0) {
|
||||
adjustVolume(volumeAdjusted, timeFadeIn);
|
||||
if (timeFadeInSeconds > 0) {
|
||||
adjustVolume(volumeAdjusted, timeFadeInSeconds);
|
||||
}
|
||||
_current.name = trackName;
|
||||
if (timePlay > 0) {
|
||||
if (timePlaySeconds > 0) {
|
||||
// Removes any previous fadeout timer and installs a new one.
|
||||
// Uses the timeFadeOutSeconds value (see Music::fadeOut())
|
||||
#if BLADERUNNER_ORIGINAL_BUGS
|
||||
_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
|
||||
_vm->getTimerManager()->installTimerProc(timerCallbackFadeOut, timePlay * 1000 * 1000, this, "BladeRunnerMusicFadeoutTimer");
|
||||
} else if (timeFadeOut > 0) {
|
||||
_vm->getTimerManager()->installTimerProc(timerCallbackFadeOut, timePlaySeconds * 1000 * 1000, this, "BladeRunnerMusicFadeoutTimer");
|
||||
#else
|
||||
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
|
||||
_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicFadeOut, timePlaySeconds * 1000u);
|
||||
#endif //BLADERUNNER_ORIGINAL_BUGS
|
||||
} else if (timeFadeOutSeconds > 0) {
|
||||
#if BLADERUNNER_ORIGINAL_BUGS
|
||||
_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
|
||||
_vm->getTimerManager()->installTimerProc(timerCallbackFadeOut, (_stream->getLength() - timeFadeOut * 1000) * 1000, this, "BladeRunnerMusicFadeoutTimer");
|
||||
_vm->getTimerManager()->installTimerProc(timerCallbackFadeOut, (_stream->getLength() - timeFadeOutSeconds * 1000) * 1000, this, "BladeRunnerMusicFadeoutTimer");
|
||||
#else
|
||||
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
|
||||
_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicFadeOut, (_stream->getLength() - timeFadeOutSeconds * 1000u));
|
||||
#endif //BLADERUNNER_ORIGINAL_BUGS
|
||||
}
|
||||
_isPlaying = true;
|
||||
_current.volume = volume;
|
||||
_current.pan = pan;
|
||||
_current.timeFadeIn = timeFadeIn;
|
||||
_current.timePlay = timePlay;
|
||||
_current.volume = volume; // Don't store the adjustedVolume - This is a "target" value for the volume
|
||||
_current.pan = pan; // This is a "target" value for the pan (balance)
|
||||
_current.timeFadeInSeconds = timeFadeInSeconds;
|
||||
_current.timePlaySeconds = timePlaySeconds;
|
||||
_current.loop = loop;
|
||||
_current.timeFadeOut = timeFadeOut;
|
||||
_current.timeFadeOutSeconds = timeFadeOutSeconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Music::stop(uint32 delay) {
|
||||
void Music::stop(uint32 delaySeconds) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (_channel < 0) {
|
||||
@ -140,16 +172,17 @@ void Music::stop(uint32 delay) {
|
||||
_isNextPresent = false;
|
||||
#endif
|
||||
|
||||
_current.loop = false;
|
||||
_vm->_audioMixer->stop(_channel, 60u * delay);
|
||||
_current.loop = 0;
|
||||
_vm->_audioMixer->stop(_channel, 60u * delaySeconds);
|
||||
}
|
||||
|
||||
void Music::adjust(int volume, int pan, uint32 delay) {
|
||||
void Music::adjust(int volume, int pan, uint32 delaySeconds) {
|
||||
if (volume != -1) {
|
||||
adjustVolume(_musicVolume * volume/ 100, delay);
|
||||
adjustVolume(_musicVolume * volume/ 100, delaySeconds);
|
||||
}
|
||||
// -101 is used as a special value to skip adjusting pan
|
||||
if (pan != -101) {
|
||||
adjustPan(pan, delay);
|
||||
adjustPan(pan, delaySeconds);
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,9 +193,10 @@ bool Music::isPlaying() {
|
||||
void Music::setVolume(int volume) {
|
||||
_musicVolume = volume;
|
||||
if (volume <= 0) {
|
||||
stop(2);
|
||||
stop(2u);
|
||||
} else if (isPlaying()) {
|
||||
_vm->_audioMixer->adjustVolume(_channel, _musicVolume * _current.volume / 100, 120);
|
||||
// delay is 2 seconds (multiplied by 60u as expected by AudioMixer::adjustVolume())
|
||||
_vm->_audioMixer->adjustVolume(_channel, _musicVolume * _current.volume / 100, 120u);
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,17 +217,17 @@ void Music::save(SaveFileWriteStream &f) {
|
||||
f.writeStringSz(_current.name, 13);
|
||||
f.writeInt(_current.volume);
|
||||
f.writeInt(_current.pan);
|
||||
f.writeInt(_current.timeFadeIn);
|
||||
f.writeInt(_current.timePlay);
|
||||
f.writeInt(_current.timeFadeInSeconds);
|
||||
f.writeInt(_current.timePlaySeconds);
|
||||
f.writeInt(_current.loop);
|
||||
f.writeInt(_current.timeFadeOut);
|
||||
f.writeInt(_current.timeFadeOutSeconds);
|
||||
f.writeStringSz(_next.name, 13);
|
||||
f.writeInt(_next.volume);
|
||||
f.writeInt(_next.pan);
|
||||
f.writeInt(_next.timeFadeIn);
|
||||
f.writeInt(_next.timePlay);
|
||||
f.writeInt(_next.timeFadeInSeconds);
|
||||
f.writeInt(_next.timePlaySeconds);
|
||||
f.writeInt(_next.loop);
|
||||
f.writeInt(_next.timeFadeOut);
|
||||
f.writeInt(_next.timeFadeOutSeconds);
|
||||
}
|
||||
|
||||
void Music::load(SaveFileReadStream &f) {
|
||||
@ -203,50 +237,50 @@ void Music::load(SaveFileReadStream &f) {
|
||||
_current.name = f.readStringSz(13);
|
||||
_current.volume = f.readInt();
|
||||
_current.pan = f.readInt();
|
||||
_current.timeFadeIn = f.readInt();
|
||||
_current.timePlay = f.readInt();
|
||||
_current.timeFadeInSeconds = f.readInt();
|
||||
_current.timePlaySeconds = f.readInt();
|
||||
_current.loop = f.readInt();
|
||||
_current.timeFadeOut = f.readInt();
|
||||
_current.timeFadeOutSeconds = f.readInt();
|
||||
_next.name = f.readStringSz(13);
|
||||
_next.volume = f.readInt();
|
||||
_next.pan = f.readInt();
|
||||
_next.timeFadeIn = f.readInt();
|
||||
_next.timePlay = f.readInt();
|
||||
_next.timeFadeInSeconds = f.readInt();
|
||||
_next.timePlaySeconds = f.readInt();
|
||||
_next.loop = f.readInt();
|
||||
_next.timeFadeOut = f.readInt();
|
||||
_next.timeFadeOutSeconds = f.readInt();
|
||||
|
||||
stop(2);
|
||||
stop(2u);
|
||||
if (_isPlaying) {
|
||||
if (_channel == -1) {
|
||||
play(_current.name,
|
||||
_current.volume,
|
||||
_current.pan,
|
||||
_current.timeFadeIn,
|
||||
_current.timePlay,
|
||||
_current.timeFadeInSeconds,
|
||||
_current.timePlaySeconds,
|
||||
_current.loop,
|
||||
_current.timeFadeOut);
|
||||
_current.timeFadeOutSeconds);
|
||||
} else {
|
||||
_isNextPresent = true;
|
||||
_next.name = _current.name;
|
||||
_next.volume = _current.volume;
|
||||
_next.pan = _current.pan;
|
||||
_next.timeFadeIn = _current.timeFadeIn;
|
||||
_next.timePlay = _current.timePlay;
|
||||
_next.timeFadeInSeconds = _current.timeFadeInSeconds;
|
||||
_next.timePlaySeconds = _current.timePlaySeconds;
|
||||
_next.loop = _current.loop;
|
||||
_next.timeFadeOut = _current.timeFadeOut;
|
||||
_next.timeFadeOutSeconds = _current.timeFadeOutSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Music::adjustVolume(int volume, uint32 delay) {
|
||||
void Music::adjustVolume(int volume, uint32 delaySeconds) {
|
||||
if (_channel >= 0) {
|
||||
_vm->_audioMixer->adjustVolume(_channel, volume, delay);
|
||||
_vm->_audioMixer->adjustVolume(_channel, volume, delaySeconds);
|
||||
}
|
||||
}
|
||||
|
||||
void Music::adjustPan(int pan, uint32 delay) {
|
||||
void Music::adjustPan(int pan, uint32 delaySeconds) {
|
||||
if (_channel >= 0) {
|
||||
_vm->_audioMixer->adjustPan(_channel, pan, delay);
|
||||
_vm->_audioMixer->adjustPan(_channel, pan, delaySeconds);
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,40 +293,30 @@ void Music::ended() {
|
||||
delete[] _data;
|
||||
_data = nullptr;
|
||||
|
||||
// The timer that checks for a next track is started here.
|
||||
// When it expires, it should check for queued music (_isNextPresent) or looping music (_current.loop)
|
||||
#if BLADERUNNER_ORIGINAL_BUGS
|
||||
_vm->getTimerManager()->installTimerProc(timerCallbackNext, 100 * 1000, this, "BladeRunnerMusicNextTimer");
|
||||
#else
|
||||
_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicNext, 100u);
|
||||
#endif // BLADERUNNER_ORIGINAL_BUGS
|
||||
}
|
||||
|
||||
void Music::fadeOut() {
|
||||
#if BLADERUNNER_ORIGINAL_BUGS
|
||||
_vm->getTimerManager()->removeTimerProc(timerCallbackFadeOut);
|
||||
#else
|
||||
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicFadeOut);
|
||||
#endif // BLADERUNNER_ORIGINAL_BUGS
|
||||
if (_channel >= 0) {
|
||||
if (_current.timeFadeOut < 0) {
|
||||
_current.timeFadeOut = 0;
|
||||
if (_current.timeFadeOutSeconds < 0) {
|
||||
_current.timeFadeOutSeconds = 0;
|
||||
}
|
||||
_vm->_audioMixer->stop(_channel, 60u * _current.timeFadeOut);
|
||||
}
|
||||
}
|
||||
|
||||
void Music::next() {
|
||||
_vm->getTimerManager()->removeTimerProc(timerCallbackNext);
|
||||
|
||||
if (_isNextPresent) {
|
||||
if (_isPaused) {
|
||||
_vm->getTimerManager()->installTimerProc(timerCallbackNext, 2000 * 1000, this, "BladeRunnerMusicNextTimer");
|
||||
} else {
|
||||
play(_next.name.c_str(), _next.volume, _next.pan, _next.timeFadeIn, _next.timePlay, _next.loop, _next.timeFadeOut);
|
||||
}
|
||||
_current.loop = false;
|
||||
} else if (_current.loop) {
|
||||
play(_current.name.c_str(), _current.volume, _current.pan, _current.timeFadeIn, _current.timePlay, _current.loop, _current.timeFadeOut);
|
||||
}
|
||||
}
|
||||
|
||||
void Music::mixerChannelEnded(int channel, void *data) {
|
||||
if (data != nullptr) {
|
||||
((Music *)data)->ended();
|
||||
_vm->_audioMixer->stop(_channel, 60u * _current.timeFadeOutSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
#if BLADERUNNER_ORIGINAL_BUGS
|
||||
void Music::timerCallbackFadeOut(void *refCon) {
|
||||
((Music *)refCon)->fadeOut();
|
||||
}
|
||||
@ -301,6 +325,45 @@ void Music::timerCallbackNext(void *refCon) {
|
||||
((Music *)refCon)->next();
|
||||
}
|
||||
|
||||
void Music::next() {
|
||||
_vm->getTimerManager()->removeTimerProc(timerCallbackNext);
|
||||
if (_isNextPresent) {
|
||||
if (_isPaused) {
|
||||
// postpone loading the next track (re-arm the BladeRunnerMusicNextTimer timer)
|
||||
_vm->getTimerManager()->installTimerProc(timerCallbackNext, 2000 * 1000, this, "BladeRunnerMusicNextTimer");
|
||||
} else {
|
||||
play(_next.name.c_str(), _next.volume, _next.pan, _next.timeFadeInSeconds, _next.timePlaySeconds, _next.loop, _next.timeFadeOutSeconds);
|
||||
}
|
||||
// This should not come after a possible call to play() which could swap the "_current" for the new (_next) track
|
||||
// Setting the loop to 0 here, would then make the new track non-looping, even if it is supposed to be looping
|
||||
_current.loop = 0;
|
||||
} else if (_current.loop) {
|
||||
play(_current.name.c_str(), _current.volume, _current.pan, _current.timeFadeInSeconds, _current.timePlaySeconds, _current.loop, _current.timeFadeOutSeconds);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void Music::next() {
|
||||
_vm->_audioMixer->stopAppTimerProc(kAudioMixerAppTimerMusicNext);
|
||||
if (_isNextPresent) {
|
||||
if (_isPaused) {
|
||||
// postpone loading the next track (re-arm the BladeRunnerMusicNextTimer timer)
|
||||
_vm->_audioMixer->startAppTimerProc(kAudioMixerAppTimerMusicNext, 2000u);
|
||||
} else {
|
||||
play(_next.name.c_str(), _next.volume, _next.pan, _next.timeFadeInSeconds, _next.timePlaySeconds, _next.loop, _next.timeFadeOutSeconds);
|
||||
}
|
||||
_current.loop = 0;
|
||||
} else if (_current.loop) {
|
||||
play(_current.name.c_str(), _current.volume, _current.pan, _current.timeFadeInSeconds, _current.timePlaySeconds, _current.loop, _current.timeFadeOutSeconds);
|
||||
}
|
||||
}
|
||||
#endif // BLADERUNNER_ORIGINAL_BUGS
|
||||
|
||||
void Music::mixerChannelEnded(int channel, void *data) {
|
||||
if (data != nullptr) {
|
||||
((Music *)data)->ended();
|
||||
}
|
||||
}
|
||||
|
||||
byte *Music::getData(const Common::String &name) {
|
||||
// NOTE: This is not part original game, loading data is done in the mixer and its using buffering to limit memory usage
|
||||
Common::SeekableReadStream *stream = _vm->getResourceStream(name);
|
||||
|
@ -36,12 +36,12 @@ class SaveFileWriteStream;
|
||||
class Music {
|
||||
struct Track {
|
||||
Common::String name;
|
||||
int volume;
|
||||
int pan;
|
||||
int32 timeFadeIn;
|
||||
int32 timePlay;
|
||||
int loop;
|
||||
int32 timeFadeOut;
|
||||
int volume; // A value between 0 and 100 - It is the set volume for the track regardless of fadeIn and fadeOut transitions
|
||||
int pan; // A value between -100 and 100 (right?) (0 is center) - It is the set pan/balance for the track regardless of any ongoing adjustments
|
||||
int32 timeFadeInSeconds; // how long will it take for the track to reach target volume (in seconds)
|
||||
int32 timePlaySeconds; // how long the track will play before starting fading out (in seconds) - uses timeFadeOutSeconds for fadeout
|
||||
int loop; // 0: do not loop, 1: loop track
|
||||
int32 timeFadeOutSeconds; // how long the fade out will be for the track at its end (in seconds)
|
||||
};
|
||||
|
||||
BladeRunnerEngine *_vm;
|
||||
@ -61,9 +61,9 @@ public:
|
||||
Music(BladeRunnerEngine *vm);
|
||||
~Music();
|
||||
|
||||
bool play(const Common::String &trackName, int volume, int pan, int32 timeFadeIn, int32 timePlay, int loop, int32 timeFadeOut);
|
||||
void stop(uint32 delay);
|
||||
void adjust(int volume, int pan, uint32 delay);
|
||||
bool play(const Common::String &trackName, int volume, int pan, int32 timeFadeInSeconds, int32 timePlaySeconds, int loop, int32 timeFadeOutSeconds);
|
||||
void stop(uint32 delaySeconds);
|
||||
void adjust(int volume, int pan, uint32 delaySeconds);
|
||||
bool isPlaying();
|
||||
|
||||
void setVolume(int volume);
|
||||
@ -73,17 +73,26 @@ public:
|
||||
void save(SaveFileWriteStream &f);
|
||||
void load(SaveFileReadStream &f);
|
||||
|
||||
private:
|
||||
void adjustVolume(int volume, uint32 delay);
|
||||
void adjustPan(int pan, uint32 delay);
|
||||
|
||||
void ended();
|
||||
#if !BLADERUNNER_ORIGINAL_BUGS
|
||||
// moved to public access
|
||||
void fadeOut();
|
||||
void next();
|
||||
#endif // !BLADERUNNER_ORIGINAL_BUGS
|
||||
|
||||
static void mixerChannelEnded(int channel, void *data);
|
||||
|
||||
private:
|
||||
void adjustVolume(int volume, uint32 delaySeconds);
|
||||
void adjustPan(int pan, uint32 delaySeconds);
|
||||
|
||||
void ended();
|
||||
#if BLADERUNNER_ORIGINAL_BUGS
|
||||
void fadeOut();
|
||||
void next();
|
||||
static void timerCallbackFadeOut(void *refCon);
|
||||
static void timerCallbackNext(void *refCon);
|
||||
#endif // BLADERUNNER_ORIGINAL_BUGS
|
||||
|
||||
static void mixerChannelEnded(int channel, void *data);
|
||||
|
||||
byte *getData(const Common::String &name);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user