/* 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 . * */ #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