mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-06 01:31:39 +00:00
1762 lines
59 KiB
C++
1762 lines
59 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "scumm/imuse_digi/dimuse_engine.h"
|
|
#include "scumm/imuse_digi/dimuse_defs.h"
|
|
|
|
namespace Scumm {
|
|
|
|
int IMuseDigital::dispatchInit() {
|
|
_dispatchBuffer = (uint8 *)malloc(DIMUSE_SMALL_FADES * DIMUSE_SMALL_FADE_DIM + DIMUSE_LARGE_FADE_DIM * DIMUSE_LARGE_FADES);
|
|
|
|
if (_dispatchBuffer) {
|
|
_dispatchLargeFadeBufs = _dispatchBuffer;
|
|
_dispatchSmallFadeBufs = _dispatchBuffer + (DIMUSE_LARGE_FADE_DIM * DIMUSE_LARGE_FADES);
|
|
|
|
for (int i = 0; i < DIMUSE_LARGE_FADES; i++) {
|
|
_dispatchLargeFadeFlags[i] = 0;
|
|
}
|
|
|
|
for (int i = 0; i < DIMUSE_SMALL_FADES; i++) {
|
|
_dispatchSmallFadeFlags[i] = 0;
|
|
}
|
|
|
|
for (int i = 0; i < DIMUSE_MAX_STREAMZONES; i++) {
|
|
_streamZones[i].useFlag = 0;
|
|
_streamZones[i].fadeFlag = 0;
|
|
_streamZones[i].prev = nullptr;
|
|
_streamZones[i].next = nullptr;
|
|
_streamZones[i].size = 0;
|
|
_streamZones[i].offset = 0;
|
|
}
|
|
|
|
for (int i = 0; i < DIMUSE_MAX_DISPATCHES; i++) {
|
|
_dispatches[i].trackPtr = nullptr;
|
|
_dispatches[i].wordSize = 0;
|
|
_dispatches[i].sampleRate = 0;
|
|
_dispatches[i].channelCount = 0;
|
|
_dispatches[i].currentOffset = 0;
|
|
_dispatches[i].audioRemaining = 0;
|
|
memset(_dispatches[i].map, 0, sizeof(_dispatches[i].map));
|
|
_dispatches[i].streamPtr = nullptr;
|
|
_dispatches[i].streamBufID = 0;
|
|
_dispatches[i].streamZoneList = nullptr;
|
|
_dispatches[i].streamErrFlag = 0;
|
|
_dispatches[i].fadeBuf = nullptr;
|
|
_dispatches[i].fadeOffset = 0;
|
|
_dispatches[i].fadeRemaining = 0;
|
|
_dispatches[i].fadeWordSize = 0;
|
|
_dispatches[i].fadeSampleRate = 0;
|
|
_dispatches[i].fadeChannelCount = 0;
|
|
_dispatches[i].fadeSyncFlag = 0;
|
|
_dispatches[i].fadeSyncDelta = 0;
|
|
_dispatches[i].fadeVol = 0;
|
|
_dispatches[i].fadeSlope = 0;
|
|
_dispatches[i].vocLoopStartingPoint = 0;
|
|
}
|
|
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchInit(): ERROR: couldn't allocate buffers\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
IMuseDigiDispatch *IMuseDigital::dispatchGetDispatchByTrackId(int trackId) {
|
|
return &_dispatches[trackId];
|
|
}
|
|
|
|
void IMuseDigital::dispatchSaveLoad(Common::Serializer &ser) {
|
|
|
|
for (int l = 0; l < DIMUSE_MAX_DISPATCHES; l++) {
|
|
ser.syncAsSint32LE(_dispatches[l].wordSize, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].sampleRate, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].channelCount, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].currentOffset, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].audioRemaining, VER(103));
|
|
ser.syncArray(_dispatches[l].map, 2048, Common::Serializer::Sint32LE, VER(103));
|
|
|
|
// This is only needed to signal if the sound originally had a stream associated with it
|
|
int hasStream = 0;
|
|
if (ser.isSaving()) {
|
|
if (_dispatches[l].streamPtr)
|
|
hasStream = 1;
|
|
ser.syncAsSint32LE(hasStream, VER(103));
|
|
} else {
|
|
ser.syncAsSint32LE(hasStream, VER(103));
|
|
_dispatches[l].streamPtr = hasStream ? (IMuseDigiStream *)1 : nullptr;
|
|
}
|
|
|
|
ser.syncAsSint32LE(_dispatches[l].streamBufID, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].streamErrFlag, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].fadeOffset, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].fadeRemaining, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].fadeWordSize, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].fadeSampleRate, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].fadeChannelCount, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].fadeSyncFlag, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].fadeSyncDelta, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].fadeVol, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].fadeSlope, VER(103));
|
|
ser.syncAsSint32LE(_dispatches[l].vocLoopStartingPoint, VER(103));
|
|
}
|
|
|
|
if (ser.isLoading()) {
|
|
for (int i = 0; i < DIMUSE_LARGE_FADES; i++) {
|
|
_dispatchLargeFadeFlags[i] = 0;
|
|
}
|
|
|
|
for (int i = 0; i < DIMUSE_SMALL_FADES; i++) {
|
|
_dispatchSmallFadeFlags[i] = 0;
|
|
}
|
|
|
|
for (int i = 0; i < DIMUSE_MAX_STREAMZONES; i++) {
|
|
_streamZones[i].useFlag = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int IMuseDigital::dispatchRestoreStreamZones() {
|
|
IMuseDigiDispatch *curDispatchPtr;
|
|
IMuseDigiStreamZone *curStreamZone;
|
|
int32 sizeToFeed = _isEarlyDiMUSE ? 0x800 : 0x4000;
|
|
|
|
curDispatchPtr = _dispatches;
|
|
for (int i = 0; i < _trackCount; i++) {
|
|
curDispatchPtr = &_dispatches[i];
|
|
curDispatchPtr->fadeBuf = nullptr;
|
|
|
|
if (curDispatchPtr->trackPtr->soundId && curDispatchPtr->streamPtr) {
|
|
// Try allocating the stream
|
|
curDispatchPtr->streamPtr = streamerAllocateSound(curDispatchPtr->trackPtr->soundId, curDispatchPtr->streamBufID, sizeToFeed);
|
|
|
|
if (curDispatchPtr->streamPtr) {
|
|
streamerSetSoundToStreamFromOffset(curDispatchPtr->streamPtr, curDispatchPtr->trackPtr->soundId, curDispatchPtr->currentOffset);
|
|
|
|
if (_isEarlyDiMUSE) {
|
|
if (curDispatchPtr->vocLoopStartingPoint)
|
|
streamerSetLoopFlag(curDispatchPtr->streamPtr, curDispatchPtr->currentOffset + curDispatchPtr->audioRemaining);
|
|
} else if (curDispatchPtr->audioRemaining) {
|
|
// Try allocating the first streamZone of the dispatch
|
|
curStreamZone = dispatchAllocateStreamZone();
|
|
curDispatchPtr->streamZoneList = curStreamZone;
|
|
|
|
if (curStreamZone) {
|
|
curStreamZone->offset = curDispatchPtr->currentOffset;
|
|
curStreamZone->size = 0;
|
|
curStreamZone->fadeFlag = 0;
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchRestoreStreamZones(): unable to allocate streamZone during restore");
|
|
}
|
|
}
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchRestoreStreamZones(): unable to start stream during restore");
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int IMuseDigital::dispatchAllocateSound(IMuseDigiTrack *trackPtr, int groupId) {
|
|
IMuseDigiDispatch *trackDispatch;
|
|
int navigateMapResult;
|
|
int32 sizeToFeed = _isEarlyDiMUSE ? 0x800 : 0x4000;
|
|
|
|
trackDispatch = trackPtr->dispatchPtr;
|
|
trackDispatch->currentOffset = 0;
|
|
trackDispatch->audioRemaining = 0;
|
|
trackDispatch->fadeBuf = nullptr;
|
|
|
|
if (_isEarlyDiMUSE) {
|
|
trackDispatch->vocLoopStartingPoint = 0;
|
|
} else {
|
|
memset(trackDispatch->map, 0, sizeof(trackDispatch->map));
|
|
}
|
|
|
|
if (groupId) {
|
|
trackDispatch->streamPtr = streamerAllocateSound(trackPtr->soundId, groupId, sizeToFeed);
|
|
trackDispatch->streamBufID = groupId;
|
|
|
|
if (!trackDispatch->streamPtr) {
|
|
debug(5, "IMuseDigital::dispatchAllocateSound(): unable to allocate stream for sound %d", trackPtr->soundId);
|
|
return -1;
|
|
}
|
|
|
|
if (_isEarlyDiMUSE)
|
|
return 0;
|
|
|
|
trackDispatch->streamZoneList = 0;
|
|
trackDispatch->streamErrFlag = 0;
|
|
} else {
|
|
trackDispatch->streamPtr = nullptr;
|
|
if (_isEarlyDiMUSE)
|
|
return dispatchSeekToNextChunk(trackDispatch);
|
|
}
|
|
|
|
navigateMapResult = dispatchNavigateMap(trackDispatch);
|
|
if (navigateMapResult && navigateMapResult != -3) {
|
|
// At this point, something went wrong, so let's release the dispatch
|
|
debug(5, "IMuseDigital::dispatchAllocateSound(): problem starting sound (%d) in dispatch", trackPtr->soundId);
|
|
dispatchRelease(trackPtr);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int IMuseDigital::dispatchRelease(IMuseDigiTrack *trackPtr) {
|
|
IMuseDigiDispatch *dispatchToDeallocate;
|
|
IMuseDigiStreamZone *streamZoneList;
|
|
|
|
dispatchToDeallocate = trackPtr->dispatchPtr;
|
|
|
|
// Remove streamZones from list
|
|
if (dispatchToDeallocate->streamPtr) {
|
|
streamerClearSoundInStream(dispatchToDeallocate->streamPtr);
|
|
|
|
if (_isEarlyDiMUSE)
|
|
return 0;
|
|
|
|
streamZoneList = dispatchToDeallocate->streamZoneList;
|
|
if (dispatchToDeallocate->streamZoneList) {
|
|
do {
|
|
streamZoneList->useFlag = 0;
|
|
removeStreamZoneFromList(&dispatchToDeallocate->streamZoneList, streamZoneList);
|
|
streamZoneList = dispatchToDeallocate->streamZoneList;
|
|
} while (dispatchToDeallocate->streamZoneList);
|
|
}
|
|
}
|
|
|
|
// Mark the fade corresponding to our fadeBuf as unused
|
|
if (dispatchToDeallocate->fadeBuf)
|
|
dispatchDeallocateFade(dispatchToDeallocate, "dispatchRelease");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int IMuseDigital::dispatchSwitchStream(int oldSoundId, int newSoundId, int fadeLength, int exitTriggerSyncFlag, int offsetFadeSyncFlag) {
|
|
int32 effFadeLen, effFadeSize, strZnSize;
|
|
int getMapResult, i;
|
|
IMuseDigiDispatch *curDispatch = _dispatches;
|
|
|
|
effFadeLen = fadeLength;
|
|
|
|
if (fadeLength > 2000)
|
|
effFadeLen = 2000;
|
|
|
|
for (i = 0; i < _trackCount; i++) {
|
|
curDispatch = &_dispatches[i];
|
|
if (oldSoundId && curDispatch->trackPtr->soundId == oldSoundId && curDispatch->streamPtr) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= _trackCount) {
|
|
debug(5, "IMuseDigital::dispatchSwitchStream(): couldn't find sound, index went past _trackCount (%d)", _trackCount);
|
|
return -1;
|
|
}
|
|
|
|
if (curDispatch->streamZoneList) {
|
|
if (!curDispatch->wordSize) {
|
|
debug(5, "IMuseDigital::dispatchSwitchStream(): found streamZoneList but NULL wordSize");
|
|
return -1;
|
|
}
|
|
|
|
if (curDispatch->fadeBuf) {
|
|
// Mark the fade corresponding to our fadeBuf as unused
|
|
dispatchDeallocateFade(curDispatch, "dispatchSwitchStream");
|
|
}
|
|
|
|
_dispatchFadeSize = dispatchGetFadeSize(curDispatch, effFadeLen);
|
|
|
|
strZnSize = curDispatch->streamZoneList->size;
|
|
if (strZnSize >= _dispatchFadeSize)
|
|
strZnSize = _dispatchFadeSize;
|
|
_dispatchFadeSize = strZnSize;
|
|
|
|
// Validate and adjust the fade dispatch size further;
|
|
// this should correctly align the dispatch size to avoid starting a fade without
|
|
// inverting the stereo image by mistake
|
|
dispatchValidateFadeSize(curDispatch, _dispatchFadeSize, "dispatchSwitchStream");
|
|
|
|
curDispatch->fadeBuf = dispatchAllocateFade(_dispatchFadeSize, "dispatchSwitchStream");
|
|
|
|
// If we were able to allocate a fade, set up a fade out for the old sound.
|
|
// We'll copy data from the stream buffer to the fade buffer
|
|
if (curDispatch->fadeBuf) {
|
|
curDispatch->fadeOffset = 0;
|
|
curDispatch->fadeRemaining = 0;
|
|
curDispatch->fadeWordSize = curDispatch->wordSize;
|
|
curDispatch->fadeSampleRate = curDispatch->sampleRate;
|
|
curDispatch->fadeChannelCount = curDispatch->channelCount;
|
|
curDispatch->fadeSyncFlag = exitTriggerSyncFlag | offsetFadeSyncFlag;
|
|
curDispatch->fadeSyncDelta = 0;
|
|
curDispatch->fadeVol = DIMUSE_MAX_FADE_VOLUME;
|
|
curDispatch->fadeSlope = 0;
|
|
|
|
while (curDispatch->fadeRemaining < _dispatchFadeSize) {
|
|
effFadeSize = _dispatchFadeSize - curDispatch->fadeRemaining;
|
|
if ((_dispatchFadeSize - curDispatch->fadeRemaining) >= 0x4000)
|
|
effFadeSize = 0x4000;
|
|
|
|
memcpy(&curDispatch->fadeBuf[curDispatch->fadeRemaining], streamerGetStreamBuffer(curDispatch->streamPtr, effFadeSize), effFadeSize);
|
|
|
|
curDispatch->fadeRemaining += effFadeSize;
|
|
}
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchSwitchStream(): WARNING: couldn't allocate fade buffer (from sound %d to sound %d)", oldSoundId, newSoundId);
|
|
}
|
|
}
|
|
|
|
// Clear fades and triggers for the old soundId
|
|
char emptyMarker[1] = "";
|
|
_fadesHandler->clearFadeStatus(curDispatch->trackPtr->soundId, -1);
|
|
_triggersHandler->clearTrigger(curDispatch->trackPtr->soundId, (char *)emptyMarker, -1);
|
|
|
|
// Setup the new soundId
|
|
curDispatch->trackPtr->soundId = newSoundId;
|
|
|
|
streamerSetReadIndex(curDispatch->streamPtr, streamerGetFreeBufferAmount(curDispatch->streamPtr));
|
|
|
|
if (offsetFadeSyncFlag && curDispatch->streamZoneList) {
|
|
// Start the soundId from an offset
|
|
streamerSetSoundToStreamFromOffset(curDispatch->streamPtr, newSoundId, curDispatch->currentOffset);
|
|
while (curDispatch->streamZoneList->next) {
|
|
curDispatch->streamZoneList->next->useFlag = 0;
|
|
removeStreamZoneFromList(&curDispatch->streamZoneList->next, curDispatch->streamZoneList->next);
|
|
}
|
|
curDispatch->streamZoneList->size = 0;
|
|
|
|
return 0;
|
|
} else {
|
|
// Start the soundId from the beginning
|
|
streamerSetSoundToStreamFromOffset(curDispatch->streamPtr, newSoundId, 0);
|
|
|
|
while (curDispatch->streamZoneList) {
|
|
curDispatch->streamZoneList->useFlag = 0;
|
|
removeStreamZoneFromList(&curDispatch->streamZoneList, curDispatch->streamZoneList);
|
|
}
|
|
|
|
curDispatch->currentOffset = 0;
|
|
curDispatch->audioRemaining = 0;
|
|
memset(curDispatch->map, 0, sizeof(curDispatch->map));
|
|
|
|
getMapResult = dispatchNavigateMap(curDispatch);
|
|
if (!getMapResult || getMapResult == -3) {
|
|
return 0;
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchSwitchStream(): problem switching stream in dispatch (from sound %d to sound %d)", oldSoundId, newSoundId);
|
|
tracksClear(curDispatch->trackPtr);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
int IMuseDigital::dispatchSwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag) {
|
|
IMuseDigiDispatch *dispatchPtr = nullptr;
|
|
uint8 *streamBuf;
|
|
int i;
|
|
int32 effAudioRemaining, audioRemaining, offset;
|
|
|
|
for (i = 0; i < _trackCount; i++) {
|
|
dispatchPtr = &_dispatches[i];
|
|
if (oldSoundId && dispatchPtr->trackPtr->soundId == oldSoundId && dispatchPtr->streamPtr) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i >= _trackCount) {
|
|
debug(5, "IMuseDigital::dispatchSwitchStream(): couldn't find sound, index went past _trackCount (%d)", _trackCount);
|
|
return -1;
|
|
}
|
|
|
|
offset = dispatchPtr->currentOffset;
|
|
audioRemaining = dispatchPtr->audioRemaining;
|
|
dispatchPtr->trackPtr->soundId = newSoundId;
|
|
dispatchPtr->fadeBuf = crossfadeBuffer;
|
|
dispatchPtr->fadeRemaining = 0;
|
|
dispatchPtr->fadeSyncDelta = 0;
|
|
dispatchPtr->fadeVol = DIMUSE_MAX_FADE_VOLUME;
|
|
dispatchPtr->fadeSlope = 0;
|
|
|
|
if (crossfadeBufferSize) {
|
|
do {
|
|
if (!streamerGetFreeBufferAmount(dispatchPtr->streamPtr)
|
|
|| (!dispatchPtr->audioRemaining && dispatchSeekToNextChunk(dispatchPtr))) {
|
|
break;
|
|
}
|
|
|
|
effAudioRemaining = dispatchPtr->audioRemaining;
|
|
if (crossfadeBufferSize - dispatchPtr->fadeRemaining < effAudioRemaining)
|
|
effAudioRemaining = crossfadeBufferSize - dispatchPtr->fadeRemaining;
|
|
|
|
if (effAudioRemaining >= streamerGetFreeBufferAmount(dispatchPtr->streamPtr))
|
|
effAudioRemaining = streamerGetFreeBufferAmount(dispatchPtr->streamPtr);
|
|
|
|
if (effAudioRemaining >= 0x800)
|
|
effAudioRemaining = 0x800;
|
|
|
|
streamBuf = streamerGetStreamBuffer(dispatchPtr->streamPtr, effAudioRemaining);
|
|
memcpy(&crossfadeBuffer[dispatchPtr->fadeRemaining], streamBuf, effAudioRemaining);
|
|
|
|
dispatchPtr->fadeRemaining += effAudioRemaining;
|
|
dispatchPtr->currentOffset += effAudioRemaining;
|
|
dispatchPtr->audioRemaining -= effAudioRemaining;
|
|
} while (dispatchPtr->fadeRemaining < crossfadeBufferSize);
|
|
}
|
|
|
|
streamerSetReadIndex(dispatchPtr->streamPtr, streamerGetFreeBufferAmount(dispatchPtr->streamPtr));
|
|
streamerSetSoundToStreamFromOffset(dispatchPtr->streamPtr, newSoundId, vocLoopFlag ? offset : 0);
|
|
|
|
if (vocLoopFlag) {
|
|
if (dispatchPtr->vocLoopStartingPoint)
|
|
streamerSetLoopFlag(dispatchPtr->streamPtr, dispatchPtr->audioRemaining + dispatchPtr->currentOffset);
|
|
} else {
|
|
streamerRemoveLoopFlag(dispatchPtr->streamPtr);
|
|
}
|
|
|
|
dispatchPtr->currentOffset = vocLoopFlag ? offset : 0;
|
|
dispatchPtr->audioRemaining = vocLoopFlag ? audioRemaining : 0;
|
|
|
|
if (!vocLoopFlag) {
|
|
dispatchPtr->vocLoopStartingPoint = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void IMuseDigital::dispatchProcessDispatches(IMuseDigiTrack *trackPtr, int feedSize, int sampleRate) {
|
|
IMuseDigiDispatch *dispatchPtr;
|
|
int32 effFeedSize, effWordSize, effRemainingAudio, effRemainingFade, effSampleRate;
|
|
int32 inFrameCount, mixVolume, mixStartingPoint, elapsedFadeDelta;
|
|
int navigateMapResult;
|
|
uint8 *srcBuf, *soundAddrData;
|
|
|
|
dispatchPtr = trackPtr->dispatchPtr;
|
|
if (dispatchPtr->streamPtr && dispatchPtr->streamZoneList)
|
|
dispatchPredictStream(dispatchPtr);
|
|
|
|
// If a fade has previously been allocated
|
|
if (dispatchPtr->fadeBuf) {
|
|
inFrameCount = 8 * dispatchPtr->fadeRemaining / (dispatchPtr->fadeWordSize * dispatchPtr->fadeChannelCount);
|
|
|
|
if (_vm->_game.id == GID_DIG) {
|
|
effSampleRate = dispatchPtr->fadeSampleRate;
|
|
} else {
|
|
effSampleRate = (trackPtr->pitchShift * dispatchPtr->fadeSampleRate) >> 8;
|
|
}
|
|
|
|
if (inFrameCount >= effSampleRate * feedSize / sampleRate) {
|
|
inFrameCount = effSampleRate * feedSize / sampleRate;
|
|
effFeedSize = feedSize;
|
|
} else {
|
|
effFeedSize = sampleRate * inFrameCount / effSampleRate;
|
|
}
|
|
|
|
if (dispatchPtr->fadeWordSize == 12 && dispatchPtr->fadeChannelCount == 1)
|
|
inFrameCount &= 0xFFFFFFFE;
|
|
|
|
// If the fade is still going on
|
|
if (inFrameCount) {
|
|
// Update the fade volume
|
|
effRemainingFade = ((dispatchPtr->fadeWordSize * dispatchPtr->fadeChannelCount) * inFrameCount) / 8;
|
|
mixVolume = dispatchUpdateFadeMixVolume(dispatchPtr, effRemainingFade);
|
|
|
|
// Send it all to the mixer
|
|
srcBuf = &dispatchPtr->fadeBuf[dispatchPtr->fadeOffset];
|
|
|
|
_internalMixer->mix(
|
|
srcBuf,
|
|
inFrameCount,
|
|
dispatchPtr->fadeWordSize,
|
|
dispatchPtr->fadeChannelCount,
|
|
effFeedSize,
|
|
0,
|
|
mixVolume,
|
|
trackPtr->pan,
|
|
false);
|
|
|
|
dispatchPtr->fadeOffset += effRemainingFade;
|
|
dispatchPtr->fadeRemaining -= effRemainingFade;
|
|
|
|
// Deallocate fade if it ended
|
|
if (!dispatchPtr->fadeRemaining) {
|
|
dispatchDeallocateFade(dispatchPtr, "dispatchProcessDispatches");
|
|
}
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchProcessDispatches(): WARNING: fade for sound %d ends with incomplete frame (or odd 12-bit mono frame)", trackPtr->soundId);
|
|
|
|
// Fade ended, deallocate it
|
|
dispatchDeallocateFade(dispatchPtr, "dispatchProcessDispatches");
|
|
}
|
|
|
|
if (!dispatchPtr->fadeRemaining)
|
|
dispatchPtr->fadeBuf = nullptr;
|
|
}
|
|
|
|
// This index keeps track of the offset position until which we have
|
|
// filled the buffer; with each update it is incremented by the effective
|
|
// feed size.
|
|
mixStartingPoint = 0;
|
|
|
|
while (1) {
|
|
// If the current region is finished playing
|
|
// go check for any event on the map for the current offset
|
|
if (!dispatchPtr->audioRemaining) {
|
|
_dispatchFadeStartedFlag = 0;
|
|
navigateMapResult = dispatchNavigateMap(dispatchPtr);
|
|
|
|
if (navigateMapResult)
|
|
break;
|
|
|
|
if (_dispatchFadeStartedFlag) {
|
|
// We reached a JUMP, therefore we have to crossfade to
|
|
// the destination region: start a fade-out
|
|
if (_vm->_game.id == GID_DIG) {
|
|
effSampleRate = dispatchPtr->fadeSampleRate;
|
|
} else {
|
|
effSampleRate = (trackPtr->pitchShift * dispatchPtr->fadeSampleRate) >> 8;
|
|
}
|
|
|
|
inFrameCount = 8 * dispatchPtr->fadeRemaining / (dispatchPtr->fadeWordSize * dispatchPtr->fadeChannelCount);
|
|
if (inFrameCount >= effSampleRate * feedSize / sampleRate) {
|
|
inFrameCount = effSampleRate * feedSize / sampleRate;
|
|
effFeedSize = feedSize;
|
|
} else {
|
|
effFeedSize = sampleRate * inFrameCount / effSampleRate;
|
|
}
|
|
|
|
if (dispatchPtr->fadeWordSize == 12 && dispatchPtr->fadeChannelCount == 1)
|
|
inFrameCount &= 0xFFFFFFFE;
|
|
|
|
if (!inFrameCount)
|
|
debug(5, "IMuseDigital::dispatchProcessDispatches(): WARNING: fade for sound %d ends with incomplete frame (or odd 12-bit mono frame)", trackPtr->soundId);
|
|
|
|
// Update the fade volume
|
|
effRemainingFade = (inFrameCount * dispatchPtr->fadeWordSize * dispatchPtr->fadeChannelCount) / 8;
|
|
mixVolume = dispatchUpdateFadeMixVolume(dispatchPtr, effRemainingFade);
|
|
|
|
// Send it all to the mixer
|
|
srcBuf = &dispatchPtr->fadeBuf[dispatchPtr->fadeOffset];
|
|
|
|
_internalMixer->mix(
|
|
srcBuf,
|
|
inFrameCount,
|
|
dispatchPtr->fadeWordSize,
|
|
dispatchPtr->fadeChannelCount,
|
|
effFeedSize,
|
|
mixStartingPoint,
|
|
mixVolume,
|
|
trackPtr->pan,
|
|
false);
|
|
|
|
dispatchPtr->fadeOffset += effRemainingFade;
|
|
dispatchPtr->fadeRemaining -= effRemainingFade;
|
|
|
|
if (!dispatchPtr->fadeRemaining) {
|
|
// Fade ended, deallocate it
|
|
dispatchDeallocateFade(dispatchPtr, "dispatchProcessDispatches");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!feedSize)
|
|
return;
|
|
|
|
if (_vm->_game.id == GID_DIG) {
|
|
effSampleRate = dispatchPtr->sampleRate;
|
|
} else {
|
|
effSampleRate = (trackPtr->pitchShift * dispatchPtr->sampleRate) >> 8;
|
|
}
|
|
|
|
effWordSize = dispatchPtr->channelCount * dispatchPtr->wordSize;
|
|
inFrameCount = effSampleRate * feedSize / sampleRate;
|
|
|
|
if (inFrameCount <= (8 * dispatchPtr->audioRemaining / effWordSize)) {
|
|
effFeedSize = feedSize;
|
|
} else {
|
|
inFrameCount = 8 * dispatchPtr->audioRemaining / effWordSize;
|
|
effFeedSize = sampleRate * (8 * dispatchPtr->audioRemaining / effWordSize) / effSampleRate;
|
|
}
|
|
|
|
if (dispatchPtr->wordSize == 12 && dispatchPtr->channelCount == 1)
|
|
inFrameCount &= 0xFFFFFFFE;
|
|
|
|
if (!inFrameCount) {
|
|
if (_vm->_game.id == GID_DIG || dispatchPtr->wordSize == 12)
|
|
debug(5, "IMuseDigital::dispatchProcessDispatches(): WARNING: region in sound %d ends with incomplete frame (or odd 12-bit mono frame)", trackPtr->soundId);
|
|
tracksClear(trackPtr);
|
|
return;
|
|
}
|
|
|
|
// Play the audio of the current region
|
|
effRemainingAudio = (effWordSize * inFrameCount) / 8;
|
|
|
|
if (dispatchPtr->streamPtr) {
|
|
srcBuf = streamerGetStreamBuffer(dispatchPtr->streamPtr, effRemainingAudio);
|
|
if (!srcBuf) {
|
|
dispatchPtr->streamErrFlag = 1;
|
|
if (dispatchPtr->fadeBuf && dispatchPtr->fadeSyncFlag)
|
|
dispatchPtr->fadeSyncDelta += feedSize;
|
|
|
|
streamerQueryStream(
|
|
dispatchPtr->streamPtr,
|
|
_dispatchCurStreamBufSize,
|
|
_dispatchCurStreamCriticalSize,
|
|
_dispatchCurStreamFreeSpace,
|
|
_dispatchCurStreamPaused);
|
|
|
|
if (_dispatchCurStreamPaused) {
|
|
debug(5, "IMuseDigital::dispatchProcessDispatches(): WARNING: stopping starving paused stream for sound %d", dispatchPtr->trackPtr->soundId);
|
|
tracksClear(trackPtr);
|
|
}
|
|
|
|
return;
|
|
}
|
|
dispatchPtr->streamZoneList->offset += effRemainingAudio;
|
|
dispatchPtr->streamZoneList->size -= effRemainingAudio;
|
|
dispatchPtr->streamErrFlag = 0;
|
|
} else {
|
|
soundAddrData = _filesHandler->getSoundAddrData(trackPtr->soundId);
|
|
if (!soundAddrData) {
|
|
debug(5, "IMuseDigital::dispatchProcessDispatches(): ERROR: soundAddrData for sound %d is NULL", trackPtr->soundId);
|
|
// Try to gracefully play nothing instead of getting stuck on an infinite loop
|
|
dispatchPtr->currentOffset += effRemainingAudio;
|
|
dispatchPtr->audioRemaining -= effRemainingAudio;
|
|
return;
|
|
}
|
|
|
|
srcBuf = &soundAddrData[dispatchPtr->currentOffset];
|
|
}
|
|
|
|
if (dispatchPtr->fadeBuf) {
|
|
// If the fadeSyncFlag is active (e.g. we are crossfading
|
|
// to another version of the same music piece), do this... thing,
|
|
// and update the fadeSyncDelta
|
|
if (dispatchPtr->fadeSyncFlag) {
|
|
if (dispatchPtr->fadeSyncDelta) {
|
|
elapsedFadeDelta = effFeedSize;
|
|
if (effFeedSize >= dispatchPtr->fadeSyncDelta)
|
|
elapsedFadeDelta = dispatchPtr->fadeSyncDelta;
|
|
|
|
dispatchPtr->fadeSyncDelta -= elapsedFadeDelta;
|
|
effFeedSize -= elapsedFadeDelta;
|
|
|
|
if (_vm->_game.id == GID_DIG) {
|
|
effSampleRate = dispatchPtr->sampleRate;
|
|
} else {
|
|
effSampleRate = (trackPtr->pitchShift * dispatchPtr->sampleRate) >> 8;
|
|
}
|
|
|
|
inFrameCount = effFeedSize * effSampleRate / sampleRate;
|
|
|
|
if (dispatchPtr->wordSize == 12 && dispatchPtr->channelCount == 1)
|
|
inFrameCount &= 0xFFFFFFFE;
|
|
|
|
srcBuf = &srcBuf[effRemainingAudio - ((dispatchPtr->wordSize * inFrameCount * dispatchPtr->channelCount) / 8)];
|
|
}
|
|
}
|
|
|
|
// If there's still a fadeBuffer active in our dispatch
|
|
// we balance the volume of the considered track with
|
|
// the fade volume, effectively creating a crossfade
|
|
if (dispatchPtr->fadeBuf) {
|
|
// Fade-in
|
|
mixVolume = dispatchUpdateFadeSlope(dispatchPtr);
|
|
} else {
|
|
mixVolume = trackPtr->effVol;
|
|
}
|
|
} else {
|
|
mixVolume = trackPtr->effVol;
|
|
}
|
|
|
|
// Real-time lo-fi Radio voice effect
|
|
if (trackPtr->mailbox)
|
|
_internalMixer->setRadioChatter();
|
|
|
|
_internalMixer->mix(
|
|
srcBuf,
|
|
inFrameCount,
|
|
dispatchPtr->wordSize,
|
|
dispatchPtr->channelCount,
|
|
effFeedSize,
|
|
mixStartingPoint,
|
|
mixVolume,
|
|
trackPtr->pan,
|
|
false);
|
|
|
|
_internalMixer->clearRadioChatter();
|
|
mixStartingPoint += effFeedSize;
|
|
feedSize -= effFeedSize;
|
|
|
|
dispatchPtr->currentOffset += effRemainingAudio;
|
|
dispatchPtr->audioRemaining -= effRemainingAudio;
|
|
}
|
|
|
|
// Behavior of errors and STOP marker
|
|
if (navigateMapResult == -1)
|
|
tracksClear(trackPtr);
|
|
|
|
if (dispatchPtr->fadeBuf && dispatchPtr->fadeSyncFlag)
|
|
dispatchPtr->fadeSyncDelta += feedSize;
|
|
}
|
|
|
|
void IMuseDigital::dispatchProcessDispatches(IMuseDigiTrack *trackPtr, int feedSize) {
|
|
IMuseDigiDispatch *dispatchPtr = nullptr;
|
|
IMuseDigiStream *streamPtr;
|
|
uint8 *buffer, *srcBuf;
|
|
int32 fadeChunkSize = 0;
|
|
int32 tentativeFeedSize, inFrameCount, fadeSyncDelta, mixStartingPoint, seekResult;
|
|
int mixVolume;
|
|
|
|
dispatchPtr = trackPtr->dispatchPtr;
|
|
tentativeFeedSize = (dispatchPtr->sampleRate == DIMUSE_BASE_SAMPLERATE) ? feedSize : feedSize / 2;
|
|
|
|
if (dispatchPtr->fadeBuf) {
|
|
if (tentativeFeedSize >= dispatchPtr->fadeRemaining) {
|
|
fadeChunkSize = dispatchPtr->fadeRemaining;
|
|
} else {
|
|
fadeChunkSize = tentativeFeedSize;
|
|
}
|
|
|
|
mixVolume = dispatchUpdateFadeMixVolume(dispatchPtr, fadeChunkSize);
|
|
|
|
_internalMixer->mix(
|
|
dispatchPtr->fadeBuf,
|
|
fadeChunkSize,
|
|
8,
|
|
1,
|
|
feedSize,
|
|
0,
|
|
mixVolume,
|
|
trackPtr->pan,
|
|
(dispatchPtr->sampleRate == (DIMUSE_BASE_SAMPLERATE / 2))
|
|
);
|
|
|
|
dispatchPtr->fadeRemaining -= fadeChunkSize;
|
|
dispatchPtr->fadeBuf += fadeChunkSize;
|
|
if (dispatchPtr->fadeRemaining == fadeChunkSize)
|
|
dispatchPtr->fadeBuf = nullptr;
|
|
}
|
|
|
|
mixStartingPoint = 0;
|
|
while (1) {
|
|
if (!dispatchPtr->audioRemaining) {
|
|
seekResult = dispatchSeekToNextChunk(dispatchPtr);
|
|
if (seekResult)
|
|
break;
|
|
}
|
|
|
|
if (!tentativeFeedSize)
|
|
return;
|
|
|
|
inFrameCount = dispatchPtr->audioRemaining;
|
|
if (tentativeFeedSize < inFrameCount)
|
|
inFrameCount = tentativeFeedSize;
|
|
|
|
streamPtr = dispatchPtr->streamPtr;
|
|
if (streamPtr) {
|
|
buffer = streamerGetStreamBuffer(streamPtr, inFrameCount);
|
|
if (!buffer) {
|
|
if (dispatchPtr->fadeBuf)
|
|
dispatchPtr->fadeSyncDelta += fadeChunkSize;
|
|
return;
|
|
}
|
|
} else {
|
|
srcBuf = _filesHandler->getSoundAddrData(trackPtr->soundId);
|
|
if (!srcBuf)
|
|
return;
|
|
buffer = &srcBuf[dispatchPtr->currentOffset];
|
|
}
|
|
|
|
if (dispatchPtr->fadeBuf) {
|
|
if (dispatchPtr->fadeSyncDelta) {
|
|
fadeSyncDelta = dispatchPtr->fadeSyncDelta;
|
|
if (dispatchPtr->fadeSyncDelta >= inFrameCount)
|
|
fadeSyncDelta = inFrameCount;
|
|
inFrameCount -= fadeSyncDelta;
|
|
dispatchPtr->fadeSyncDelta -= fadeSyncDelta;
|
|
buffer += fadeSyncDelta;
|
|
dispatchPtr->currentOffset += fadeSyncDelta;
|
|
dispatchPtr->audioRemaining -= fadeSyncDelta;
|
|
}
|
|
}
|
|
|
|
if (inFrameCount) {
|
|
if (dispatchPtr->fadeBuf) {
|
|
mixVolume = dispatchUpdateFadeSlope(dispatchPtr);
|
|
} else {
|
|
mixVolume = trackPtr->effVol;
|
|
}
|
|
|
|
_internalMixer->mix(
|
|
buffer,
|
|
inFrameCount,
|
|
8,
|
|
1,
|
|
feedSize,
|
|
mixStartingPoint,
|
|
mixVolume,
|
|
trackPtr->pan,
|
|
(dispatchPtr->sampleRate == (DIMUSE_BASE_SAMPLERATE / 2)));
|
|
mixStartingPoint += inFrameCount;
|
|
tentativeFeedSize -= inFrameCount;
|
|
dispatchPtr->currentOffset += inFrameCount;
|
|
dispatchPtr->audioRemaining -= inFrameCount;
|
|
}
|
|
}
|
|
|
|
if (seekResult == -1)
|
|
tracksClear(trackPtr);
|
|
|
|
if (dispatchPtr->fadeBuf)
|
|
dispatchPtr->fadeSyncDelta += fadeChunkSize;
|
|
}
|
|
|
|
void IMuseDigital::dispatchPredictFirstStream() {
|
|
Common::StackLock lock(*_mutex);
|
|
|
|
for (int i = 0; i < _trackCount; i++) {
|
|
if (_dispatches[i].trackPtr->soundId && _dispatches[i].streamPtr && _dispatches[i].streamZoneList)
|
|
dispatchPredictStream(&_dispatches[i]);
|
|
}
|
|
}
|
|
|
|
int IMuseDigital::dispatchNavigateMap(IMuseDigiDispatch *dispatchPtr) {
|
|
uint8 *mapCurEvent;
|
|
int32 blockTag, effFadeSize, elapsedFadeSize, regionOffset;
|
|
char *marker = NULL;
|
|
|
|
int getMapResult = dispatchGetMap(dispatchPtr);
|
|
if (getMapResult)
|
|
return getMapResult;
|
|
|
|
if (dispatchPtr->audioRemaining
|
|
|| (dispatchPtr->streamPtr && dispatchPtr->streamZoneList->offset != dispatchPtr->currentOffset)) {
|
|
debug(5, "IMuseDigital::dispatchNavigateMap(): ERROR: navigation error in dispatch");
|
|
return -1;
|
|
}
|
|
|
|
mapCurEvent = NULL;
|
|
while (1) {
|
|
mapCurEvent = dispatchGetNextMapEvent(dispatchPtr->map, dispatchPtr->currentOffset, mapCurEvent);
|
|
|
|
if (!mapCurEvent) {
|
|
debug(5, "IMuseDigital::dispatchNavigateMap(): ERROR: no more map events at offset %dx", dispatchPtr->currentOffset);
|
|
return -1;
|
|
}
|
|
|
|
blockTag = READ_UINT32(mapCurEvent);
|
|
switch (blockTag) {
|
|
case MKTAG('J', 'U', 'M', 'P'):
|
|
// Handle any event found at this offset
|
|
// Jump block (fixed size: 28 bytes)
|
|
// - The tag 'JUMP' (4 bytes)
|
|
// - Block size in bytes minus 8 (4 bytes)
|
|
// - Block offset (hook position) (4 bytes)
|
|
// - Jump destination offset (4 bytes)
|
|
// - Hook ID (4 bytes)
|
|
// - Fade time in ms (4 bytes)
|
|
if (!checkHookId(dispatchPtr->trackPtr->jumpHook, READ_UINT32(mapCurEvent + 16))) {
|
|
// This is the right hookId, let's jump
|
|
dispatchPtr->currentOffset = READ_UINT32(mapCurEvent + 12);
|
|
if (dispatchPtr->streamPtr) {
|
|
if (dispatchPtr->streamZoneList->size || !dispatchPtr->streamZoneList->next) {
|
|
debug(5, "IMuseDigital::dispatchNavigateMap(): next streamZone is unallocated, calling dispatchPrepareToJump()");
|
|
dispatchPrepareToJump(dispatchPtr, dispatchPtr->streamZoneList, mapCurEvent, 1);
|
|
}
|
|
|
|
debug(5, "IMuseDigital::dispatchNavigateMap(): \n"
|
|
"\tJUMP found for sound %d with valid candidateHookId (%d), \n"
|
|
"\tgoing to offset %d with a crossfade of %d ms",
|
|
dispatchPtr->trackPtr->soundId, (int)READ_UINT32(mapCurEvent + 16),
|
|
(int)READ_UINT32(mapCurEvent + 12), (int)READ_UINT32(mapCurEvent + 20));
|
|
|
|
dispatchPtr->streamZoneList->useFlag = 0;
|
|
removeStreamZoneFromList(&dispatchPtr->streamZoneList, dispatchPtr->streamZoneList);
|
|
|
|
if (dispatchPtr->streamZoneList->fadeFlag) {
|
|
if (dispatchPtr->fadeBuf) {
|
|
// Mark the fade corresponding to our fadeBuf as unused
|
|
dispatchDeallocateFade(dispatchPtr, "dispatchNavigateMap");
|
|
}
|
|
|
|
_dispatchJumpFadeSize = dispatchPtr->streamZoneList->size;
|
|
dispatchPtr->fadeBuf = dispatchAllocateFade(_dispatchJumpFadeSize, "dispatchNavigateMap");
|
|
|
|
// If the fade buffer is allocated
|
|
// set up the fade
|
|
if (dispatchPtr->fadeBuf) {
|
|
dispatchPtr->fadeWordSize = dispatchPtr->wordSize;
|
|
dispatchPtr->fadeSampleRate = dispatchPtr->sampleRate;
|
|
dispatchPtr->fadeChannelCount = dispatchPtr->channelCount;
|
|
dispatchPtr->fadeOffset = 0;
|
|
dispatchPtr->fadeRemaining = 0;
|
|
dispatchPtr->fadeSyncFlag = 0;
|
|
dispatchPtr->fadeSyncDelta = 0;
|
|
dispatchPtr->fadeVol = DIMUSE_MAX_FADE_VOLUME;
|
|
dispatchPtr->fadeSlope = 0;
|
|
|
|
// Clone the old sound in the fade buffer for just the duration of the fade
|
|
if (_dispatchJumpFadeSize) {
|
|
do {
|
|
effFadeSize = _dispatchJumpFadeSize - dispatchPtr->fadeRemaining;
|
|
if ((_dispatchJumpFadeSize - dispatchPtr->fadeRemaining) >= 0x4000)
|
|
effFadeSize = 0x4000;
|
|
|
|
memcpy(&dispatchPtr->fadeBuf[dispatchPtr->fadeRemaining],
|
|
streamerGetStreamBuffer(dispatchPtr->streamPtr, effFadeSize),
|
|
effFadeSize);
|
|
|
|
elapsedFadeSize = effFadeSize + dispatchPtr->fadeRemaining;
|
|
dispatchPtr->fadeRemaining = elapsedFadeSize;
|
|
} while (_dispatchJumpFadeSize > elapsedFadeSize);
|
|
}
|
|
_dispatchFadeStartedFlag = 1;
|
|
}
|
|
dispatchPtr->streamZoneList->useFlag = 0;
|
|
removeStreamZoneFromList(&dispatchPtr->streamZoneList, dispatchPtr->streamZoneList);
|
|
}
|
|
}
|
|
mapCurEvent = nullptr;
|
|
}
|
|
|
|
continue;
|
|
case MKTAG('S', 'Y', 'N', 'C'):
|
|
{
|
|
// SYNC block (fixed size: x bytes)
|
|
// - The tag 'SYNC' (4 bytes)
|
|
// - SYNC size in bytes (4 bytes)
|
|
// - SYNC data (variable length)
|
|
|
|
// It is possible to gather a total maximum of 4 SYNCs for a single track;
|
|
// this is not a problem however, as speech files only have one SYNC block,
|
|
// and the most we get is four (one for each character) in
|
|
// A Pirate I Was Meant To Be, in Part 3 of COMI
|
|
|
|
// Curiously we skip the first four bytes of data, ending up having the first
|
|
// four bytes of the next block in our syncPtr; but this is exactly what happens
|
|
// within the interpreter, so I'm not going to argue with it
|
|
|
|
int32 targetSyncSize = READ_UINT32(mapCurEvent + 4); // As per specifications above, we fetch the size
|
|
uint8 *mapEventStart = mapCurEvent + 4 + 4 + 4; // We skip the 'SYNC' tag, the size, and an additional 4-bytes dword
|
|
|
|
// Allocate space for the data block and fill it, 4 bytes at a time...
|
|
byte *syncData = (byte *)malloc(targetSyncSize);
|
|
uint32 *syncDwordPtr = (uint32 *)syncData;
|
|
uint32 *mapEventsDwordPtr = (uint32 *)mapEventStart;
|
|
if (syncData) {
|
|
for (int i = 0; i < (targetSyncSize >> 2); i++) {
|
|
WRITE_LE_UINT32(&syncDwordPtr[i], mapEventsDwordPtr[i]);
|
|
}
|
|
}
|
|
|
|
if (!dispatchPtr->trackPtr->syncPtr_0) {
|
|
dispatchPtr->trackPtr->syncPtr_0 = syncData;
|
|
dispatchPtr->trackPtr->syncSize_0 = targetSyncSize;
|
|
|
|
} else if (!dispatchPtr->trackPtr->syncPtr_1) {
|
|
dispatchPtr->trackPtr->syncPtr_1 = syncData;
|
|
dispatchPtr->trackPtr->syncSize_1 = targetSyncSize;
|
|
|
|
} else if (!dispatchPtr->trackPtr->syncPtr_2) {
|
|
dispatchPtr->trackPtr->syncPtr_2 = syncData;
|
|
dispatchPtr->trackPtr->syncSize_2 = targetSyncSize;
|
|
|
|
} else if (!dispatchPtr->trackPtr->syncPtr_3) {
|
|
dispatchPtr->trackPtr->syncPtr_3 = syncData;
|
|
dispatchPtr->trackPtr->syncSize_3 = targetSyncSize;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
case MKTAG('F', 'R', 'M', 'T'):
|
|
// Format block (fixed size: 28 bytes)
|
|
// - The tag 'FRMT' (4 bytes)
|
|
// - Block size in bytes minus 8 (4 bytes)
|
|
// - Block offset (4 bytes)
|
|
// - Empty field (4 bytes) (which is set to 1 in Grim Fandango, I suspect this is the endianness)
|
|
// - Word size between 8, 12 and 16 (4 bytes)
|
|
// - Sample rate (4 bytes)
|
|
// - Number of channels (4 bytes)
|
|
dispatchPtr->wordSize = READ_UINT32(mapCurEvent + 16);
|
|
dispatchPtr->sampleRate = READ_UINT32(mapCurEvent + 20);
|
|
dispatchPtr->channelCount = READ_UINT32(mapCurEvent + 24);
|
|
|
|
continue;
|
|
case MKTAG('R', 'E', 'G', 'N'):
|
|
// Region block (fixed size: 16 bytes)
|
|
// - The tag 'REGN' (4 bytes)
|
|
// - Block size in bytes minus 8 (4 bytes)
|
|
// - Block offset (4 bytes)
|
|
// - Region length (4 bytes)
|
|
regionOffset = READ_UINT32(mapCurEvent + 8);
|
|
if (regionOffset == dispatchPtr->currentOffset) {
|
|
dispatchPtr->audioRemaining = READ_UINT32(mapCurEvent + 12);
|
|
return 0;
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchNavigateMap(): ERROR: region offset %d != currentOffset %d", regionOffset, dispatchPtr->currentOffset);
|
|
return -1;
|
|
}
|
|
case MKTAG('S', 'T', 'O', 'P'):
|
|
// Stop block (fixed size: 12 bytes)
|
|
// Contains:
|
|
// - The tag 'STOP' (4 bytes)
|
|
// - Block size in bytes minus 8 (4 bytes)
|
|
// - Block offset (4 bytes)
|
|
return -1;
|
|
case MKTAG('T', 'E', 'X', 'T'):
|
|
// Marker block (variable size)
|
|
// Contains:
|
|
// - The tag 'TEXT' (4 bytes)
|
|
// - Block size in bytes minus 8 (4 bytes)
|
|
// - Block offset (4 bytes)
|
|
// - A string of characters ending with '\0' (variable length)
|
|
marker = (char *)mapCurEvent + 12;
|
|
_triggersHandler->processTriggers(dispatchPtr->trackPtr->soundId, marker);
|
|
if (dispatchPtr->audioRemaining)
|
|
return 0;
|
|
|
|
continue;
|
|
default:
|
|
debug(5, "IMuseDigital::dispatchNavigateMap(): ERROR: Unrecognized map event at offset %dx", dispatchPtr->currentOffset);
|
|
break;
|
|
};
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int IMuseDigital::dispatchGetMap(IMuseDigiDispatch *dispatchPtr) {
|
|
int32 *dstMap;
|
|
uint8 *rawMap, *copiedBuf, *soundAddrData;
|
|
int32 size;
|
|
|
|
dstMap = dispatchPtr->map;
|
|
|
|
// If there's no map, try to fetch it
|
|
if (dispatchPtr->map[0] != MKTAG('M', 'A', 'P', ' ')) {
|
|
if (dispatchPtr->currentOffset) {
|
|
debug(5, "IMuseDigital::dispatchNavigateMap(): found offset but no map");
|
|
return -1;
|
|
}
|
|
|
|
// If there's a streamPtr it means that this is a sound loaded
|
|
// from a bundle (either music or speech)
|
|
if (dispatchPtr->streamPtr) {
|
|
copiedBuf = (uint8 *)streamerGetStreamBufferAtOffset(dispatchPtr->streamPtr, 0, 0x10);
|
|
|
|
if (!copiedBuf) {
|
|
return -3;
|
|
}
|
|
|
|
if (READ_BE_UINT32(copiedBuf) == MKTAG('i', 'M', 'U', 'S') && READ_BE_UINT32(copiedBuf + 8) == MKTAG('M', 'A', 'P', ' ')) {
|
|
size = READ_BE_UINT32(copiedBuf + 12) + 24;
|
|
if (!streamerGetStreamBufferAtOffset(dispatchPtr->streamPtr, 0, size)) {
|
|
return -3;
|
|
}
|
|
rawMap = (uint8 *)streamerGetStreamBuffer(dispatchPtr->streamPtr, size);
|
|
if (!rawMap) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: stream read failed after view succeeded");
|
|
return -1;
|
|
}
|
|
|
|
dispatchPtr->currentOffset = size;
|
|
if (dispatchConvertMap(rawMap + 8, dstMap)) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: dispatchConvertMap() failed");
|
|
return -1;
|
|
}
|
|
|
|
if (dispatchPtr->map[2] != MKTAG('F', 'R', 'M', 'T')) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected 'FRMT' at start of map");
|
|
return -1;
|
|
}
|
|
|
|
if (dispatchPtr->map[4] != dispatchPtr->currentOffset) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected data to follow map");
|
|
return -1;
|
|
} else {
|
|
if (dispatchPtr->streamZoneList) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected NULL streamZoneList");
|
|
return -1;
|
|
}
|
|
|
|
dispatchPtr->streamZoneList = dispatchAllocateStreamZone();
|
|
if (!dispatchPtr->streamZoneList) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: couldn't allocate zone");
|
|
return -1;
|
|
}
|
|
|
|
dispatchPtr->streamZoneList->offset = dispatchPtr->currentOffset;
|
|
dispatchPtr->streamZoneList->size = streamerGetFreeBufferAmount(dispatchPtr->streamPtr);
|
|
dispatchPtr->streamZoneList->fadeFlag = 0;
|
|
}
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: unrecognized file format in stream buffer");
|
|
return -1;
|
|
}
|
|
|
|
} else {
|
|
// Otherwise, this is a SFX and we must load it using its resource pointer
|
|
soundAddrData = _filesHandler->getSoundAddrData(dispatchPtr->trackPtr->soundId);
|
|
|
|
if (!soundAddrData) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: couldn't get sound address");
|
|
return -1;
|
|
}
|
|
|
|
if (READ_BE_UINT32(soundAddrData) == MKTAG('i', 'M', 'U', 'S') && READ_BE_UINT32(soundAddrData + 8) == MKTAG('M', 'A', 'P', ' ')) {
|
|
dispatchPtr->currentOffset = READ_BE_UINT32(soundAddrData + 12) + 24;
|
|
if (dispatchConvertMap((soundAddrData + 8), dstMap)) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: dispatchConvertMap() failure");
|
|
return -1;
|
|
}
|
|
|
|
if (dispatchPtr->map[2] != MKTAG('F', 'R', 'M', 'T')) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected 'FRMT' at start of map");
|
|
return -1;
|
|
}
|
|
|
|
if (dispatchPtr->map[4] != dispatchPtr->currentOffset) {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: expected data to follow map");
|
|
return -1;
|
|
}
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchGetMap(): ERROR: unrecognized file format in stream buffer");
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int IMuseDigital::dispatchConvertMap(uint8 *rawMap, int32 *destMap) {
|
|
int32 effMapSize;
|
|
uint8 *mapCurPos;
|
|
int32 blockName;
|
|
uint8 *blockSizePtr;
|
|
uint32 blockSizeMin8;
|
|
uint8 *firstChar;
|
|
uint8 *otherChars;
|
|
uint32 remainingFieldsNum;
|
|
int32 bytesUntilEndOfMap;
|
|
uint8 *endOfMapPtr;
|
|
|
|
if (READ_BE_UINT32(rawMap) == MKTAG('M', 'A', 'P', ' ')) {
|
|
bytesUntilEndOfMap = READ_BE_UINT32(rawMap + 4);
|
|
effMapSize = bytesUntilEndOfMap + 8;
|
|
if (((_vm->_game.id == GID_DIG
|
|
|| (_vm->_game.id == GID_CMI && _vm->_game.features & GF_DEMO)) && effMapSize <= 0x400)
|
|
|| (_vm->_game.id == GID_CMI && effMapSize <= 0x2000)) {
|
|
memcpy(destMap, rawMap, effMapSize);
|
|
|
|
// Fill (or rather, swap32) the fields:
|
|
// - The 4 bytes string 'MAP '
|
|
// - Size of the map
|
|
destMap[0] = READ_BE_UINT32(destMap);
|
|
destMap[1] = READ_BE_UINT32(destMap + 4);
|
|
|
|
mapCurPos = (uint8 *)destMap + 8;
|
|
endOfMapPtr = (uint8 *)destMap + effMapSize;
|
|
|
|
// Swap32 the rest of the map
|
|
while (mapCurPos < endOfMapPtr) {
|
|
// Swap32 the 4 characters block name
|
|
int32 swapped = READ_BE_UINT32(mapCurPos);
|
|
memcpy(mapCurPos, &swapped, 4);
|
|
blockName = swapped;
|
|
|
|
// Advance and Swap32 the block size (minus 8) field
|
|
blockSizePtr = mapCurPos + 4;
|
|
blockSizeMin8 = READ_BE_UINT32(blockSizePtr);
|
|
memcpy(blockSizePtr, &blockSizeMin8, 4);
|
|
mapCurPos = blockSizePtr + 4;
|
|
|
|
// Swapping32 a TEXT block is different:
|
|
// it also contains single characters, so we skip them
|
|
// since they're already good like this
|
|
if (blockName == MKTAG('T', 'E', 'X', 'T')) {
|
|
// Swap32 the block offset position
|
|
swapped = READ_BE_UINT32(mapCurPos);
|
|
memcpy(mapCurPos, &swapped, 4);
|
|
|
|
// Skip the single characters
|
|
firstChar = mapCurPos + 4;
|
|
mapCurPos += 5;
|
|
if (*firstChar) {
|
|
do {
|
|
otherChars = mapCurPos++;
|
|
} while (*otherChars);
|
|
}
|
|
} else if ((blockSizeMin8 & 0xFFFFFFFC) != 0) {
|
|
// Basically divide by 4 to retrieve the number
|
|
// of fields to swap
|
|
remainingFieldsNum = blockSizeMin8 >> 2;
|
|
|
|
// ...and swap them of course
|
|
do {
|
|
swapped = READ_BE_UINT32(mapCurPos);
|
|
memcpy(mapCurPos, &swapped, 4);
|
|
mapCurPos += 4;
|
|
--remainingFieldsNum;
|
|
} while (remainingFieldsNum);
|
|
}
|
|
}
|
|
|
|
// Just a sanity check to see if we've parsed the whole map
|
|
if ((uint8 *)destMap + bytesUntilEndOfMap - mapCurPos == -8) {
|
|
return 0;
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchConvertMap(): ERROR: converted wrong number of bytes");
|
|
return -1;
|
|
}
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchConvertMap(): ERROR: map is too big (%d)", effMapSize);
|
|
return -1;
|
|
}
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchConvertMap(): ERROR: got bogus map");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
uint8 *IMuseDigital::dispatchGetNextMapEvent(int32 *mapPtr, int32 soundOffset, uint8 *mapEvent) {
|
|
if (mapEvent) {
|
|
// Advance the map to the next block (READ_UINT32(mapEvent + 4) + 8 is the size of the block)
|
|
mapEvent = mapEvent + READ_UINT32(mapEvent + 4) + 8;
|
|
|
|
if ((uint8 *)&mapPtr[2] + mapPtr[1] > mapEvent) {
|
|
if ((int32)READ_UINT32(mapEvent + 8) != soundOffset) {
|
|
debug(5, "IMuseDigital::dispatchGetNextMapEvent(): ERROR: no more events at offset %d", soundOffset);
|
|
return nullptr;
|
|
}
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchGetNextMapEvent(): ERROR: map overrun");
|
|
return nullptr;
|
|
}
|
|
|
|
} else {
|
|
// Init the current map position starting from the first block
|
|
// (cells 0 and 1 are the tag 'MAP ' and the map size respectively)
|
|
mapEvent = (uint8 *)&mapPtr[2];
|
|
|
|
// Search for the block with the same offset as ours
|
|
while ((int32)READ_UINT32(mapEvent + 8) != soundOffset) {
|
|
// Check if we've overrun the offset, to make sure
|
|
// that there actually is an event at our offset
|
|
mapEvent = mapEvent + READ_UINT32(mapEvent + 4) + 8;
|
|
|
|
if ((uint8 *)&mapPtr[2] + mapPtr[1] <= mapEvent) {
|
|
debug(5, "IMuseDigital::dispatchGetNextMapEvent(): ERROR: couldn't find event at offset %d", soundOffset);
|
|
return nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return mapEvent;
|
|
}
|
|
|
|
void IMuseDigital::dispatchPredictStream(IMuseDigiDispatch *dispatchPtr) {
|
|
IMuseDigiStreamZone *szTmp, *lastStreamInList, *curStrZn;
|
|
int32 cumulativeStreamOffset;
|
|
uint8 *jumpParameters;
|
|
|
|
if (!dispatchPtr->streamPtr || !dispatchPtr->streamZoneList) {
|
|
debug(5, "IMuseDigital::dispatchPredictStream(): ERROR: NULL streamId or streamZoneList");
|
|
return;
|
|
}
|
|
|
|
szTmp = dispatchPtr->streamZoneList;
|
|
|
|
// Get the offset which our stream is currently at
|
|
cumulativeStreamOffset = 0;
|
|
do {
|
|
cumulativeStreamOffset += szTmp->size;
|
|
lastStreamInList = szTmp;
|
|
szTmp = szTmp->next;
|
|
} while (szTmp);
|
|
|
|
lastStreamInList->size += streamerGetFreeBufferAmount(dispatchPtr->streamPtr) - cumulativeStreamOffset;
|
|
curStrZn = dispatchPtr->streamZoneList;
|
|
|
|
for (_dispatchBufferedHookId = dispatchPtr->trackPtr->jumpHook; curStrZn; curStrZn = curStrZn->next) {
|
|
if (!curStrZn->fadeFlag) {
|
|
jumpParameters = dispatchCheckForJump(dispatchPtr->map, curStrZn, _dispatchBufferedHookId);
|
|
if (jumpParameters) {
|
|
// If we've reached a JUMP and it's successful, allocate the streamZone of the destination
|
|
dispatchPrepareToJump(dispatchPtr, curStrZn, jumpParameters, 0);
|
|
} else {
|
|
// If we don't have to jump, just play the next streamZone available
|
|
dispatchStreamNextZone(dispatchPtr, curStrZn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8 *IMuseDigital::dispatchCheckForJump(int32 *mapPtr, IMuseDigiStreamZone *strZnPtr, int &candidateHookId) {
|
|
uint8 *curMapPlace = (uint8 *)&mapPtr[2];
|
|
uint8 *endOfMap = (uint8 *)&mapPtr[2] + mapPtr[1];
|
|
int32 mapPlaceTag, jumpHookPos, jumpHookId, bytesUntilNextPlace;
|
|
|
|
while (curMapPlace < endOfMap) {
|
|
mapPlaceTag = READ_UINT32(curMapPlace);
|
|
bytesUntilNextPlace = READ_UINT32(curMapPlace + 4) + 8;
|
|
|
|
if (mapPlaceTag == MKTAG('J', 'U', 'M', 'P')) {
|
|
jumpHookPos = READ_UINT32(curMapPlace + 8);
|
|
jumpHookId = READ_UINT32(curMapPlace + 16);
|
|
|
|
if (jumpHookPos > strZnPtr->offset && jumpHookPos <= strZnPtr->size + strZnPtr->offset) {
|
|
if (!checkHookId(candidateHookId, jumpHookId))
|
|
return curMapPlace;
|
|
}
|
|
}
|
|
// Advance the map to the next place
|
|
curMapPlace = curMapPlace + bytesUntilNextPlace;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void IMuseDigital::dispatchPrepareToJump(IMuseDigiDispatch *dispatchPtr, IMuseDigiStreamZone *strZnPtr, uint8 *jumpParams, int calledFromNavigateMap) {
|
|
int32 hookPosition, jumpDestination, fadeTime;
|
|
IMuseDigiStreamZone *nextStreamZone;
|
|
IMuseDigiStreamZone *zoneForJump = nullptr;
|
|
IMuseDigiStreamZone *zoneAfterJump;
|
|
uint32 streamOffset;
|
|
IMuseDigiStreamZone *zoneCycle;
|
|
|
|
// jumpParams format (assuming jumpParams is int32*):
|
|
// jumpParams[0]: four bytes which form the string 'JUMP'
|
|
// jumpParams[1]: block size in bytes minus 8 (16 for a JUMP block like this one; total == 24 bytes)
|
|
// jumpParams[2]: hook position
|
|
// jumpParams[3]: jump destination
|
|
// jumpParams[4]: hook ID
|
|
// jumpParams[5]: fade time in milliseconds
|
|
|
|
hookPosition = READ_UINT32(jumpParams + 8);
|
|
jumpDestination = READ_UINT32(jumpParams + 12);
|
|
fadeTime = READ_UINT32(jumpParams + 20);
|
|
|
|
// Edge cases handling
|
|
if (strZnPtr->size + strZnPtr->offset == hookPosition) {
|
|
nextStreamZone = strZnPtr->next;
|
|
if (nextStreamZone) {
|
|
if (nextStreamZone->fadeFlag) {
|
|
// Avoid jumping if the next stream zone is already fading
|
|
// and its ending position is our jump destination.
|
|
// Basically: cancel the jump if there's already a fade in progress
|
|
if (nextStreamZone->offset == hookPosition) {
|
|
if (nextStreamZone->next) {
|
|
if (nextStreamZone->next->offset == jumpDestination)
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
// Avoid jumping if we're trying to jump to the next stream zone
|
|
if (nextStreamZone->offset == jumpDestination)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Maximum size of the dispatch for the fade (in bytes)
|
|
_dispatchSize = dispatchGetFadeSize(dispatchPtr, fadeTime);
|
|
|
|
// If this function is being called from dispatchPredictStream,
|
|
// avoid accepting an oversized dispatch
|
|
if (!calledFromNavigateMap) {
|
|
if (_dispatchSize > strZnPtr->size + strZnPtr->offset - hookPosition)
|
|
return;
|
|
}
|
|
|
|
// Cap the dispatch size, if oversized
|
|
if (_dispatchSize > strZnPtr->size + strZnPtr->offset - hookPosition)
|
|
_dispatchSize = strZnPtr->size + strZnPtr->offset - hookPosition;
|
|
|
|
// This prevents starting a fade with an inverted stereo image
|
|
dispatchValidateFadeSize(dispatchPtr, _dispatchSize, "dispatchPrepareToJump");
|
|
|
|
if (_vm->_game.id == GID_DIG) {
|
|
if (hookPosition < jumpDestination)
|
|
_dispatchSize = 0;
|
|
} else {
|
|
if (dispatchPtr->fadeRemaining)
|
|
_dispatchSize = 0;
|
|
}
|
|
|
|
// Try allocating the two streamZones needed for the jump
|
|
if (_dispatchSize) {
|
|
zoneForJump = dispatchAllocateStreamZone();
|
|
if (!zoneForJump) {
|
|
debug(5, "IMuseDigital::dispatchPrepareToJump(): ERROR: couldn't allocate streamZone");
|
|
return;
|
|
}
|
|
}
|
|
|
|
zoneAfterJump = dispatchAllocateStreamZone();
|
|
if (!zoneAfterJump) {
|
|
debug(5, "IMuseDigital::dispatchPrepareToJump(): ERROR: couldn't allocate streamZone");
|
|
return;
|
|
}
|
|
|
|
strZnPtr->size = hookPosition - strZnPtr->offset;
|
|
streamOffset = hookPosition - strZnPtr->offset + _dispatchSize;
|
|
|
|
// Go to the interested stream zone to calculate the stream offset,
|
|
// and schedule the sound to stream with that offset
|
|
zoneCycle = dispatchPtr->streamZoneList;
|
|
while (zoneCycle != strZnPtr) {
|
|
streamOffset += zoneCycle->size;
|
|
zoneCycle = zoneCycle->next;
|
|
}
|
|
|
|
streamerSetLoadIndex(dispatchPtr->streamPtr, streamOffset);
|
|
|
|
while (strZnPtr->next) {
|
|
strZnPtr->next->useFlag = 0;
|
|
removeStreamZoneFromList(&strZnPtr->next, strZnPtr->next);
|
|
}
|
|
|
|
streamerSetSoundToStreamFromOffset(dispatchPtr->streamPtr,
|
|
dispatchPtr->trackPtr->soundId, jumpDestination);
|
|
|
|
// Prepare the fading zone for the jump
|
|
// and also a subsequent empty dummy zone
|
|
if (_dispatchSize) {
|
|
strZnPtr->next = zoneForJump;
|
|
zoneForJump->prev = strZnPtr;
|
|
strZnPtr = zoneForJump;
|
|
zoneForJump->next = 0;
|
|
zoneForJump->offset = hookPosition;
|
|
zoneForJump->size = _dispatchSize;
|
|
zoneForJump->fadeFlag = 1;
|
|
}
|
|
|
|
strZnPtr->next = zoneAfterJump;
|
|
zoneAfterJump->prev = strZnPtr;
|
|
zoneAfterJump->next = nullptr;
|
|
zoneAfterJump->offset = jumpDestination;
|
|
zoneAfterJump->size = 0;
|
|
zoneAfterJump->fadeFlag = 0;
|
|
}
|
|
|
|
void IMuseDigital::dispatchStreamNextZone(IMuseDigiDispatch *dispatchPtr, IMuseDigiStreamZone *strZnPtr) {
|
|
int32 cumulativeStreamOffset;
|
|
IMuseDigiStreamZone *szTmp;
|
|
|
|
if (strZnPtr->next) {
|
|
cumulativeStreamOffset = strZnPtr->size;
|
|
szTmp = dispatchPtr->streamZoneList;
|
|
while (szTmp != strZnPtr) {
|
|
cumulativeStreamOffset += szTmp->size;
|
|
szTmp = szTmp->next;
|
|
}
|
|
|
|
// Set the stream load index of the sound to the new streamZone
|
|
streamerSetLoadIndex(dispatchPtr->streamPtr, cumulativeStreamOffset);
|
|
|
|
// Remove che previous streamZone from the list, we don't need it anymore
|
|
while (strZnPtr->next->prev) {
|
|
strZnPtr->next->prev->useFlag = 0;
|
|
removeStreamZoneFromList(&strZnPtr->next, strZnPtr->next->prev);
|
|
}
|
|
|
|
streamerSetSoundToStreamFromOffset(
|
|
dispatchPtr->streamPtr,
|
|
dispatchPtr->trackPtr->soundId,
|
|
strZnPtr->size + strZnPtr->offset);
|
|
}
|
|
}
|
|
|
|
IMuseDigiStreamZone *IMuseDigital::dispatchAllocateStreamZone() {
|
|
for (int i = 0; i < DIMUSE_MAX_STREAMZONES; i++) {
|
|
if (_streamZones[i].useFlag == 0) {
|
|
_streamZones[i].prev = 0;
|
|
_streamZones[i].next = 0;
|
|
_streamZones[i].useFlag = 1;
|
|
_streamZones[i].offset = 0;
|
|
_streamZones[i].size = 0;
|
|
_streamZones[i].fadeFlag = 0;
|
|
|
|
return &_streamZones[i];
|
|
}
|
|
}
|
|
debug(5, "IMuseDigital::dispatchAllocateStreamZone(): ERROR: out of streamZones");
|
|
return nullptr;
|
|
}
|
|
|
|
uint8 *IMuseDigital::dispatchAllocateFade(int32 &fadeSize, const char *function) {
|
|
uint8 *allocatedFadeBuf = nullptr;
|
|
if (fadeSize > DIMUSE_LARGE_FADE_DIM) {
|
|
debug(5, "IMuseDigital::dispatchAllocateFade(): WARNING: requested fade too large (%d) in %s()", fadeSize, function);
|
|
fadeSize = DIMUSE_LARGE_FADE_DIM;
|
|
}
|
|
|
|
if (fadeSize <= DIMUSE_SMALL_FADE_DIM) { // Small fade
|
|
for (int i = 0; i <= DIMUSE_SMALL_FADES; i++) {
|
|
if (i == DIMUSE_SMALL_FADES) {
|
|
debug(5, "IMuseDigital::dispatchAllocateFade(): couldn't allocate small fade buffer in %s()", function);
|
|
allocatedFadeBuf = nullptr;
|
|
break;
|
|
}
|
|
|
|
if (!_dispatchSmallFadeFlags[i]) {
|
|
_dispatchSmallFadeFlags[i] = 1;
|
|
allocatedFadeBuf = &_dispatchSmallFadeBufs[DIMUSE_SMALL_FADE_DIM * i];
|
|
break;
|
|
}
|
|
}
|
|
} else { // Large fade
|
|
for (int i = 0; i <= DIMUSE_LARGE_FADES; i++) {
|
|
if (i == DIMUSE_LARGE_FADES) {
|
|
debug(5, "IMuseDigital::dispatchAllocateFade(): couldn't allocate large fade buffer in %s()", function);
|
|
allocatedFadeBuf = nullptr;
|
|
break;
|
|
}
|
|
|
|
if (!_dispatchLargeFadeFlags[i]) {
|
|
_dispatchLargeFadeFlags[i] = 1;
|
|
allocatedFadeBuf = &_dispatchLargeFadeBufs[DIMUSE_LARGE_FADE_DIM * i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Fallback to a small fade if large fades are unavailable
|
|
if (!allocatedFadeBuf) {
|
|
for (int i = 0; i <= DIMUSE_SMALL_FADES; i++) {
|
|
if (i == DIMUSE_SMALL_FADES) {
|
|
debug(5, "IMuseDigital::dispatchAllocateFade(): couldn't allocate small fade buffer in %s()", function);
|
|
allocatedFadeBuf = nullptr;
|
|
break;
|
|
}
|
|
|
|
if (!_dispatchSmallFadeFlags[i]) {
|
|
_dispatchSmallFadeFlags[i] = 1;
|
|
allocatedFadeBuf = &_dispatchSmallFadeBufs[DIMUSE_SMALL_FADE_DIM * i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return allocatedFadeBuf;
|
|
}
|
|
|
|
void IMuseDigital::dispatchDeallocateFade(IMuseDigiDispatch *dispatchPtr, const char *function) {
|
|
// This function flags the fade corresponding to our fadeBuf as unused
|
|
|
|
// First, check if our fade buffer is one of the large fade buffers
|
|
for (int i = 0; i < DIMUSE_LARGE_FADES; i++) {
|
|
if (_dispatchLargeFadeBufs + (DIMUSE_LARGE_FADE_DIM * i) == dispatchPtr->fadeBuf) { // Found it!
|
|
if (_dispatchLargeFadeFlags[i] == 0) {
|
|
debug(5, "IMuseDigital::dispatchDeallocateFade(): redundant large fade buf de-allocation in %s()", function);
|
|
}
|
|
_dispatchLargeFadeFlags[i] = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If not, check between the small fade buffers
|
|
for (int j = 0; j < DIMUSE_SMALL_FADES; j++) {
|
|
if (_dispatchSmallFadeBufs + (DIMUSE_SMALL_FADE_DIM * j) == dispatchPtr->fadeBuf) { // Found it!
|
|
if (_dispatchSmallFadeFlags[j] == 0) {
|
|
debug(5, "IMuseDigital::dispatchDeallocateFade(): redundant small fade buf de-allocation in %s()", function);
|
|
}
|
|
_dispatchSmallFadeFlags[j] = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
debug(5, "IMuseDigital::dispatchDeallocateFade(): couldn't find fade buf to de-allocate in %s()", function);
|
|
}
|
|
|
|
int IMuseDigital::dispatchGetFadeSize(IMuseDigiDispatch *dispatchPtr, int fadeLength) {
|
|
return (dispatchPtr->wordSize * dispatchPtr->channelCount * ((dispatchPtr->sampleRate * fadeLength / 1000) & 0xFFFFFFFE)) / 8;
|
|
}
|
|
|
|
void IMuseDigital::dispatchValidateFadeSize(IMuseDigiDispatch *dispatchPtr, int32 &dispatchSize, const char *function) {
|
|
int alignmentModDividend;
|
|
if (_vm->_game.id == GID_DIG || (_vm->_game.id == GID_CMI && _vm->_game.features & GF_DEMO)) {
|
|
alignmentModDividend = dispatchPtr->channelCount * (dispatchPtr->wordSize == 8 ? 1 : 3);
|
|
} else {
|
|
if (dispatchPtr->wordSize == 8) {
|
|
alignmentModDividend = dispatchPtr->channelCount * 1;
|
|
} else {
|
|
alignmentModDividend = dispatchPtr->channelCount * ((dispatchPtr->wordSize == 12) + 2);
|
|
}
|
|
}
|
|
|
|
if (alignmentModDividend) {
|
|
dispatchSize -= dispatchSize % alignmentModDividend;
|
|
} else {
|
|
debug(5, "IMuseDigital::dispatchValidateFadeSize(): WARNING: tried mod by 0 while validating fade size in %s(), ignored", function);
|
|
}
|
|
}
|
|
|
|
int IMuseDigital::dispatchUpdateFadeMixVolume(IMuseDigiDispatch *dispatchPtr, int32 remainingFade) {
|
|
int mixVolume = (((dispatchPtr->fadeVol / 65536) + 1) * dispatchPtr->trackPtr->effVol) / 128;
|
|
dispatchPtr->fadeVol += remainingFade * dispatchPtr->fadeSlope;
|
|
|
|
if (dispatchPtr->fadeVol < 0)
|
|
dispatchPtr->fadeVol = 0;
|
|
if (dispatchPtr->fadeVol > DIMUSE_MAX_FADE_VOLUME)
|
|
dispatchPtr->fadeVol = DIMUSE_MAX_FADE_VOLUME;
|
|
|
|
return mixVolume;
|
|
}
|
|
|
|
int IMuseDigital::dispatchUpdateFadeSlope(IMuseDigiDispatch *dispatchPtr) {
|
|
int32 updatedVolume, effRemainingFade;
|
|
|
|
updatedVolume = (dispatchPtr->trackPtr->effVol * (128 - (dispatchPtr->fadeVol / 65536))) / 128;
|
|
if (!dispatchPtr->fadeSlope) {
|
|
effRemainingFade = dispatchPtr->fadeRemaining;
|
|
if (effRemainingFade <= 1)
|
|
effRemainingFade = 2;
|
|
dispatchPtr->fadeSlope = -(DIMUSE_MAX_FADE_VOLUME / effRemainingFade);
|
|
}
|
|
|
|
return updatedVolume;
|
|
}
|
|
|
|
void IMuseDigital::dispatchVOCLoopCallback(int soundId) {
|
|
IMuseDigiDispatch *curDispatchPtr;
|
|
uint8 *dataBlockTag;
|
|
|
|
if (!soundId)
|
|
return;
|
|
|
|
for (int i = 0; i < _trackCount; i++) {
|
|
curDispatchPtr = &_dispatches[i];
|
|
if (curDispatchPtr->trackPtr->soundId == soundId) {
|
|
dataBlockTag = streamerGetStreamBufferAtOffset(curDispatchPtr->streamPtr, curDispatchPtr->audioRemaining, 1);
|
|
if (dataBlockTag && dataBlockTag[0] == 7) { // End of loop
|
|
streamerSetLoadIndex(curDispatchPtr->streamPtr, curDispatchPtr->audioRemaining + 1);
|
|
streamerSetSoundToStreamFromOffset(curDispatchPtr->streamPtr, curDispatchPtr->trackPtr->soundId, curDispatchPtr->vocLoopStartingPoint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int IMuseDigital::dispatchSeekToNextChunk(IMuseDigiDispatch *dispatchPtr) {
|
|
uint8 *headerBuf;
|
|
uint8 *soundAddrData;
|
|
|
|
while (1) {
|
|
if (dispatchPtr->streamPtr) {
|
|
headerBuf = streamerGetStreamBufferAtOffset(dispatchPtr->streamPtr, 0, 0x30);
|
|
if (headerBuf || (headerBuf = streamerGetStreamBufferAtOffset(dispatchPtr->streamPtr, 0, 1)) != 0) {
|
|
memcpy(_currentVOCHeader, headerBuf, 0x30);
|
|
} else {
|
|
return -3;
|
|
}
|
|
} else {
|
|
soundAddrData = _filesHandler->getSoundAddrData(dispatchPtr->trackPtr->soundId);
|
|
uint32 soundAddrSize = _filesHandler->getSoundAddrDataSize(dispatchPtr->trackPtr->soundId, dispatchPtr->streamPtr != 0);
|
|
uint32 fetchSize = (soundAddrSize - dispatchPtr->currentOffset) >= 0x30 ? 0x30 : (soundAddrSize - dispatchPtr->currentOffset);
|
|
|
|
if (soundAddrData && soundAddrSize > 0) {
|
|
memcpy(_currentVOCHeader, &soundAddrData[dispatchPtr->currentOffset], fetchSize);
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (READ_BE_UINT32(_currentVOCHeader) == MKTAG('C', 'r', 'e', 'a')) {
|
|
// We expect to find (everything in Little Endian except where noted):
|
|
// - The string "Creative Voice File" stored in Big Endian format;
|
|
// - 0x1A, which the interpreter doesn't check, so we don't either
|
|
// - Total size of the header, which has to be 0x001A (0x1A00 in LE)
|
|
// - Version tags: 0x0A (minor), 0x01 (major), this corresponds to version 1.10
|
|
if (_currentVOCHeader[20] == 0x1A && _currentVOCHeader[21] == 0x0 &&
|
|
_currentVOCHeader[22] == 0xA && _currentVOCHeader[23] == 0x1) {
|
|
dispatchPtr->currentOffset += 26;
|
|
if (dispatchPtr->streamPtr)
|
|
streamerGetStreamBuffer(dispatchPtr->streamPtr, 26);
|
|
continue;
|
|
}
|
|
return -1;
|
|
} else {
|
|
uint8 *headerTag = _currentVOCHeader;
|
|
|
|
// All blocks specification described here are documented as
|
|
// per the official SoundBlaster file format specification
|
|
switch (headerTag[0]) {
|
|
case VOC_DIGI_DATA_BLOCK:
|
|
// Block format:
|
|
// - BYTE bBlockId; Always 1 for this block
|
|
// - BYTE nBlockLen[3]; 3-byte block length (excluding this and the previous field size)
|
|
// - BYTE bTimeConstant; Used to determine the sample rate
|
|
// - BYTE bPackMethod; Packing method, unused here
|
|
dispatchPtr->sampleRate = headerTag[4] > 196 ? DIMUSE_BASE_SAMPLERATE : (DIMUSE_BASE_SAMPLERATE / 2);
|
|
dispatchPtr->audioRemaining = (READ_LE_UINT32(headerTag) >> 8) - 2;
|
|
dispatchPtr->currentOffset += 6;
|
|
|
|
if (dispatchPtr->streamPtr) {
|
|
streamerGetStreamBuffer(dispatchPtr->streamPtr, 6);
|
|
if (dispatchPtr->vocLoopStartingPoint)
|
|
streamerSetLoopFlag(dispatchPtr->streamPtr, dispatchPtr->audioRemaining + dispatchPtr->currentOffset);
|
|
}
|
|
return 0;
|
|
case VOC_MARKER_BLOCK:
|
|
// Block format:
|
|
// - BYTE bBlockId; Always 4 for this block
|
|
// - BYTE nBlockLen[3]; 3-byte block length (excluding this and the previous field size)
|
|
// - WORD wMarker; Marker value
|
|
//
|
|
// These markers are theoretically used for triggers, but never actually used in-game;
|
|
// We keep this case here, in order to correctly keep track of the offset.
|
|
dispatchPtr->currentOffset += 6;
|
|
continue;
|
|
case VOC_LOOP_START_BLOCK:
|
|
// Block format:
|
|
// - BYTE bBlockId; Always 6 for this block
|
|
// - BYTE nBlockLen[3]; 3-byte block length (excluding this and the previous field size)
|
|
// - WORD wRepeatTimes; Number of repeats (from 1 to 0xFFFE); 0xFFFF yields an endless loop
|
|
dispatchPtr->vocLoopStartingPoint = dispatchPtr->currentOffset;
|
|
dispatchPtr->currentOffset += 6;
|
|
if (dispatchPtr->streamPtr)
|
|
streamerGetStreamBuffer(dispatchPtr->streamPtr, 6);
|
|
continue;
|
|
case VOC_LOOP_END_BLOCK:
|
|
// Block format:
|
|
// - BYTE bBlockId; Always 7 for this block
|
|
// - BYTE nBlockLen[3]; 3-byte block length (excluding this and the previous field size)
|
|
//
|
|
// Works in conjunction with the previous block to reset the stream offset to the loop beginning
|
|
dispatchPtr->currentOffset = dispatchPtr->vocLoopStartingPoint;
|
|
if (dispatchPtr->streamPtr)
|
|
streamerGetStreamBuffer(dispatchPtr->streamPtr, 1);
|
|
continue;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
} // End of namespace Scumm
|