From 924a62b40b271f065528f68fa86a28dcb0d0f9a6 Mon Sep 17 00:00:00 2001 From: AndywinXp Date: Thu, 12 Nov 2020 06:40:43 +0100 Subject: [PATCH] SCUMM: COMI: Fix #4635/#10473 and #4391 (iMUSE Digital) (#2596) * SCUMM: COMI: Fix iMUSE Digital #4635/#10473 bugs * SCUMM: COMI: Fix #4391 iMUSE Digital fades Co-authored-by: Andrea Boscarino --- engines/scumm/imuse_digi/dimuse.cpp | 90 +++++++++---- engines/scumm/imuse_digi/dimuse.h | 5 +- engines/scumm/imuse_digi/dimuse_music.cpp | 2 - engines/scumm/imuse_digi/dimuse_script.cpp | 4 +- engines/scumm/imuse_digi/dimuse_track.cpp | 139 +++++++++++++++++++-- 5 files changed, 203 insertions(+), 37 deletions(-) diff --git a/engines/scumm/imuse_digi/dimuse.cpp b/engines/scumm/imuse_digi/dimuse.cpp index de5b1634edb..28cf2c79422 100644 --- a/engines/scumm/imuse_digi/dimuse.cpp +++ b/engines/scumm/imuse_digi/dimuse.cpp @@ -218,29 +218,64 @@ void IMuseDigital::callback() { return; if (track->volFadeUsed) { - if (track->volFadeStep < 0) { - if (track->vol > track->volFadeDest) { - track->vol += track->volFadeStep; - if (track->vol < track->volFadeDest) { - track->vol = track->volFadeDest; - track->volFadeUsed = false; + if (_vm->_game.id == GID_CMI) { + if (track->vol == track->volFadeDest) // Sanity check + track->volFadeUsed = false; + + if (track->volFadeStep < 0) { // Fade out + if (track->vol > track->volFadeDest) { + int tempVolume = transformVolumeEqualPowToLinear(track->vol, 1); // Equal power to linear... + tempVolume += track->volFadeStep; // Remove step... + track->vol = transformVolumeLinearToEqualPow(tempVolume, 1); // Linear to equal power... + + if (track->vol <= track->volFadeDest) { + track->vol = track->volFadeDest; + track->volFadeUsed = false; + flushTrack(track); + continue; + } + if (track->vol == 0) { + // Fade out complete -> remove this track + flushTrack(track); + continue; + } } - if (track->vol == 0) { - // Fade out complete -> remove this track - flushTrack(track); - continue; + } else if (track->volFadeStep > 0) { // Fade in + if (track->vol < track->volFadeDest) { + int tempVolume = transformVolumeEqualPowToLinear(track->vol, 1); // Equal power to linear... + tempVolume += track->volFadeStep; // Add step... + track->vol = transformVolumeLinearToEqualPow(tempVolume, 1); // Linear to equal power... + if (track->vol >= track->volFadeDest) { + track->vol = track->volFadeDest; + track->volFadeUsed = false; + } } } - } else if (track->volFadeStep > 0) { - if (track->vol < track->volFadeDest) { - track->vol += track->volFadeStep; + } else { + if (track->volFadeStep < 0) { if (track->vol > track->volFadeDest) { - track->vol = track->volFadeDest; - track->volFadeUsed = false; + track->vol += track->volFadeStep; + if (track->vol < track->volFadeDest) { + track->vol = track->volFadeDest; + track->volFadeUsed = false; + } + if (track->vol == 0) { + // Fade out complete -> remove this track + flushTrack(track); + continue; + } + } + } else if (track->volFadeStep > 0) { + if (track->vol < track->volFadeDest) { + track->vol += track->volFadeStep; + if (track->vol > track->volFadeDest) { + track->vol = track->volFadeDest; + track->volFadeUsed = false; + } } } } - debug(5, "Fade: sound(%d), Vol(%d)", track->soundId, track->vol / 1000); + debug(5, "Fade: sound(%d), Vol(%d) in track(%d)", track->soundId, track->vol / 1000, track->trackId); } if (!track->souStreamUsed) { @@ -407,25 +442,30 @@ void IMuseDigital::switchToNextRegion(Track *track) { int fadeDelay = (60 * _sound->getJumpFade(soundDesc, jumpId)) / 1000; debug(5, "SwToNeReg(trackId:%d) - sound(%d) match hookId", track->trackId, track->soundId); if (fadeDelay) { - debug(5, "SwToNeReg(trackId:%d) - call cloneToFadeOutTrack(delay:%d)", track->trackId, fadeDelay); - Track *fadeTrack = cloneToFadeOutTrack(track, fadeDelay); - if (fadeTrack) { - fadeTrack->dataOffset = _sound->getRegionOffset(fadeTrack->soundDesc, fadeTrack->curRegion); - fadeTrack->regionOffset = 0; - debug(5, "SwToNeReg(trackId:%d) - sound(%d) faded track, select region %d, curHookId: %d", fadeTrack->trackId, fadeTrack->soundId, fadeTrack->curRegion, fadeTrack->curHookId); - fadeTrack->curHookId = 0; - } + // COMI specific: jump to new region without true crossfades; this is not true to the original interpreter, + // but crossfading between regions in the correct way requires an implementation which is more complex. + // Leaving as is, for now. + if (_vm->_game.id != GID_CMI) { + debug(5, "SwToNeReg(trackId:%d) - call cloneToFadeOutTrack(delay:%d)", track->trackId, fadeDelay); + Track *fadeTrack = cloneToFadeOutTrack(track, fadeDelay); + if (fadeTrack) { + fadeTrack->dataOffset = _sound->getRegionOffset(fadeTrack->soundDesc, fadeTrack->curRegion); + fadeTrack->regionOffset = 0; + debug(5, "SwToNeReg(trackId:%d) - sound(%d) faded track, select region %d, curHookId: %d", fadeTrack->trackId, fadeTrack->soundId, fadeTrack->curRegion, fadeTrack->curHookId); + fadeTrack->curHookId = 0; + } + } } track->curRegion = region; debug(5, "SwToNeReg(trackId:%d) - sound(%d) jump to region %d, curHookId: %d", track->trackId, track->soundId, track->curRegion, track->curHookId); track->curHookId = 0; + } else { debug(5, "SwToNeReg(trackId:%d) - Normal switch region, sound(%d), hookId(%d)", track->trackId, track->soundId, track->curHookId); } } else { debug(5, "SwToNeReg(trackId:%d) - Normal switch region, sound(%d), hookId(%d)", track->trackId, track->soundId, track->curHookId); } - debug(5, "SwToNeReg(trackId:%d) - sound(%d), select region %d", track->trackId, track->soundId, track->curRegion); track->dataOffset = _sound->getRegionOffset(soundDesc, track->curRegion); track->regionOffset = 0; diff --git a/engines/scumm/imuse_digi/dimuse.h b/engines/scumm/imuse_digi/dimuse.h index e289ec7138b..485922d3fc9 100644 --- a/engines/scumm/imuse_digi/dimuse.h +++ b/engines/scumm/imuse_digi/dimuse.h @@ -105,7 +105,10 @@ private: void setTrigger(TriggerParams *trigger); void setHookIdForMusic(int hookId); Track *cloneToFadeOutTrack(Track *track, int fadeDelay); - + Track *handleComiFadeOut(Track *track, int fadeDelay); + int transformVolumeLinearToEqualPow(int volume, int mode); + int transformVolumeEqualPowToLinear(int volume, int mode); + void setFtMusicState(int stateId); void setFtMusicSequence(int seqId); void setFtMusicCuePoint(int cueId); diff --git a/engines/scumm/imuse_digi/dimuse_music.cpp b/engines/scumm/imuse_digi/dimuse_music.cpp index 281d06b1500..5c3cf30dbda 100644 --- a/engines/scumm/imuse_digi/dimuse_music.cpp +++ b/engines/scumm/imuse_digi/dimuse_music.cpp @@ -321,8 +321,6 @@ void IMuseDigital::playComiMusic(const char *songName, const imuseComiTable *tab } if (getCurMusicSoundId() == table->soundId) return; - if (table->transitionType == 4) - _stopingSequence = 1; if (table->transitionType == 2) { fadeOutMusic(table->fadeOutDelay); startMusic(table->filename, table->soundId, table->hookId, 127); diff --git a/engines/scumm/imuse_digi/dimuse_script.cpp b/engines/scumm/imuse_digi/dimuse_script.cpp index 8d1e7a4bfdd..f5b5f195602 100644 --- a/engines/scumm/imuse_digi/dimuse_script.cpp +++ b/engines/scumm/imuse_digi/dimuse_script.cpp @@ -166,7 +166,7 @@ void IMuseDigital::flushTrack(Track *track) { if (track->souStreamUsed) { _mixer->stopHandle(track->mixChanHandle); } else if (track->stream) { - debug(5, "flushTrack() - soundId:%d", track->soundId); + debug(5, "flushTrack(trackId: %d) - soundId:%d", track->trackId, track->soundId); // Finalize the appendable stream, then remove our reference to it. // Note that there might still be some data left in the buffers of the // appendable stream. We play it nice and wait till all of it @@ -190,7 +190,7 @@ void IMuseDigital::flushTracks() { for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) { Track *track = _track[l]; if (track->used && track->toBeRemoved && !_mixer->isSoundHandleActive(track->mixChanHandle)) { - debug(5, "flushTracks() - soundId:%d", track->soundId); + debug(5, "flushTracks() - trackId:%d, soundId:%d", track->trackId, track->soundId); track->reset(); } } diff --git a/engines/scumm/imuse_digi/dimuse_track.cpp b/engines/scumm/imuse_digi/dimuse_track.cpp index 92ef823db1b..6107b02ef03 100644 --- a/engines/scumm/imuse_digi/dimuse_track.cpp +++ b/engines/scumm/imuse_digi/dimuse_track.cpp @@ -158,11 +158,26 @@ void IMuseDigital::startSound(int soundId, const char *soundName, int soundType, } else error("IMuseDigital::startSound(): Can't handle %d bit samples", bits); + int fadeDelay = 30; // Default fade value if not found anywhere else + if (otherTrack && otherTrack->used && !otherTrack->toBeRemoved) { track->curRegion = otherTrack->curRegion; track->dataOffset = otherTrack->dataOffset; track->regionOffset = otherTrack->regionOffset; track->dataMod12Bit = otherTrack->dataMod12Bit; + + if (_vm->_game.id == GID_CMI) { + fadeDelay = otherTrack->volFadeDelay != 0 ? otherTrack->volFadeDelay : fadeDelay; + track->regionOffset -= track->regionOffset >= (track->feedSize / _callbackFps) ? (track->feedSize / _callbackFps) : 0; + } + } + if (_vm->_game.id == GID_CMI) { + // Fade in the new track + track->vol = 0; + track->volFadeDelay = fadeDelay; + track->volFadeDest = volume * 1000; + track->volFadeStep = (track->volFadeDest - track->vol) * 60 * (1000 / _callbackFps) / (1000 * fadeDelay); + track->volFadeUsed = true; } track->stream = Audio::makeQueuingAudioStream(freq, track->mixerFlags & kFlagStereo); @@ -279,9 +294,18 @@ void IMuseDigital::fadeOutMusicAndStartNew(int fadeDelay, const char *filename, Track *track = _track[l]; if (track->used && !track->toBeRemoved && (track->volGroupId == IMUSE_VOLGRP_MUSIC)) { debug(5, "IMuseDigital::fadeOutMusicAndStartNew(sound:%d) - starting", soundId); - startMusicWithOtherPos(filename, soundId, 0, 127, track); - cloneToFadeOutTrack(track, fadeDelay); - flushTrack(track); + + // Store the fadeDelay in the track: startMusicWithOtherPos will use it to + // fade in the new track; this will match fade in and fade out speeds. + if (_vm->_game.id == GID_CMI) { + track->volFadeDelay = fadeDelay; + startMusicWithOtherPos(filename, soundId, 0, 127, track); + handleComiFadeOut(track, fadeDelay); + } else { + startMusicWithOtherPos(filename, soundId, 0, 127, track); + cloneToFadeOutTrack(track, fadeDelay); + flushTrack(track); + } break; } } @@ -295,8 +319,12 @@ void IMuseDigital::fadeOutMusic(int fadeDelay) { Track *track = _track[l]; if (track->used && !track->toBeRemoved && (track->volGroupId == IMUSE_VOLGRP_MUSIC)) { debug(5, "IMuseDigital::fadeOutMusic(fade:%d, sound:%d)", fadeDelay, track->soundId); - cloneToFadeOutTrack(track, fadeDelay); - flushTrack(track); + if (_vm->_game.id == GID_CMI) { + handleComiFadeOut(track, fadeDelay); + } else { + cloneToFadeOutTrack(track, fadeDelay); + flushTrack(track); + } break; } } @@ -324,6 +352,16 @@ void IMuseDigital::setTrigger(TriggerParams *trigger) { _triggerUsed = true; } +Track *IMuseDigital::handleComiFadeOut(Track *track, int fadeDelay) { + track->volFadeDelay = fadeDelay; + track->volFadeDest = 0; + track->volFadeStep = (track->volFadeDest - track->vol) * 60 * (1000 / _callbackFps) / (1000 * fadeDelay); + track->volFadeUsed = true; + track->toBeRemoved = true; + return track; +} + + Track *IMuseDigital::cloneToFadeOutTrack(Track *track, int fadeDelay) { assert(track); Track *fadeTrack; @@ -337,7 +375,6 @@ Track *IMuseDigital::cloneToFadeOutTrack(Track *track, int fadeDelay) { assert(track->trackId < MAX_DIGITAL_TRACKS); fadeTrack = _track[track->trackId + MAX_DIGITAL_TRACKS]; - if (fadeTrack->used) { debug(5, "cloneToFadeOutTrack: No free fade track, force flush fade soundId:%d", fadeTrack->soundId); flushTrack(fadeTrack); @@ -368,10 +405,98 @@ Track *IMuseDigital::cloneToFadeOutTrack(Track *track, int fadeDelay) { fadeTrack->stream = Audio::makeQueuingAudioStream(_sound->getFreq(fadeTrack->soundDesc), track->mixerFlags & kFlagStereo); _mixer->playStream(track->getType(), &fadeTrack->mixChanHandle, fadeTrack->stream, -1, fadeTrack->getVol(), fadeTrack->getPan()); fadeTrack->used = true; - debug(5, "cloneToFadeOutTrack() - end of func, soundId %d, fade soundId %d", track->soundId, fadeTrack->soundId); return fadeTrack; } +int IMuseDigital::transformVolumeLinearToEqualPow(int volume, int mode) { + if (volume == 0 || volume == 127000) + return volume; + + int result = volume; + if (!(volume < 0 || volume > 127000)) { + // Change range of values from 0-127*1000 to 0.0-1.0 + double mappedValue = (((volume - 0) * (1.0 - 0.0)) / (127000 - 0)) + 0; + double eqPowValue; + + switch (mode) { + case 0: // Sqrt curve + eqPowValue = sqrt(mappedValue); + break; + case 1: // Quarter sine curve + eqPowValue = sin(mappedValue * M_PI / 2.0); + break; + case 2: // Parabola + eqPowValue = (1 - (1 - mappedValue) * (1 - mappedValue)); + break; + case 3: // Logarithmic 1 + eqPowValue = 1 + 0.2 * log10(mappedValue); + break; + case 4: // Logarithmic 2 + eqPowValue = 1 + 0.5 * log10(mappedValue); + break; + case 5: // Logarithmic 3 + eqPowValue = 1 + 0.7 * log10(mappedValue); + break; + case 6: // Half sine curve + eqPowValue = (1.0 - cos(mappedValue * M_PI)) / 2.0; + default: // Fallback to linear + eqPowValue = mappedValue; + break; + } + + // Change range again, this time round the result + result = (((eqPowValue - 0.0) * (127000 - 0)) / (1.0 - 0.0)) + 0.0; + result = (int)round(result); + } + + return result; +} + +int IMuseDigital::transformVolumeEqualPowToLinear(int volume, int mode) { + if (volume == 0 || volume == 127000) + return volume; + + int result = volume; + if (!(volume < 0 || volume > 127000)) { + // Change range of values from 0-127*1000 to 0.0-1.0 + double mappedValue = (((volume - 0) * (1.0 - 0.0)) / (127000 - 0)) + 0; + double linearValue; + + switch (mode) { + case 0: // Sqrt curve + linearValue = mappedValue * mappedValue; + break; + case 1: // Quarter sine curve + linearValue = 0.63662 * asin(mappedValue); + break; + case 2: // Parabola + linearValue = 1 - sqrt(1 - mappedValue); + break; + case 3: // Logarithmic 1 + linearValue = 0.00001 * pow(M_E, 11.5129 * mappedValue); + break; + case 4: // Logarithmic 2 + linearValue = 0.01 * pow(M_E, 4.60517 * mappedValue); + break; + case 5: // Logarithmic 3 + linearValue = 0.0372759 * pow(M_E, 3.28941 * mappedValue); + break; + case 6: // Half sine curve + linearValue = (2 * asin(sqrt(mappedValue))) / M_PI; // Ricontrolla + break; + default: // Fallback to linear + linearValue = mappedValue; + break; + } + + // Change range again, this time round the result + result = (((linearValue - 0.0) * (127000 - 0)) / (1.0 - 0.0)) + 0.0; + result = (int)round(result); + } + + return result; +} + } // End of namespace Scumm