scummvm/engines/scumm/imuse_digi/dimuse_streamer.cpp
AndywinXp a7a4d9f120 SCUMM: DiMUSE: Implement antiskip system for devices with slow load times
This code is present in all the original interpreters using Digital iMUSE, and it was implemented
in order to allow slower CD drives to fetch new data without having music skipping during load times.
This works by basically flooding the music buffer with data whenever a big resource, a voice file, or
a SMUSH movie is loaded.
Arguably this is not needed for devices using SSD drives, but since this change had its benefits on a
quite old laptop of mine, and since the memory overhead is very minimal, I have decided to implement it.
2022-10-04 11:34:14 +02:00

386 lines
11 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"
namespace Scumm {
int IMuseDigital::streamerInit() {
for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
_streams[l].soundId = 0;
}
_lastStreamLoaded = nullptr;
return 0;
}
IMuseDigiStream *IMuseDigital::streamerAllocateSound(int soundId, int bufId, int32 maxRead) {
IMuseDigiSndBuffer *bufInfoPtr = _filesHandler->getBufInfo(bufId);
if (!bufInfoPtr) {
debug(5, "IMuseDigital::streamerAlloc(): ERROR: couldn't get buffer info");
return nullptr;
}
if ((bufInfoPtr->bufSize / 4) <= maxRead) {
debug(5, "IMuseDigital::streamerAlloc(): ERROR: maxRead too big for buffer");
return nullptr;
}
for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
if (_streams[l].soundId && _streams[l].bufId == bufId) {
debug(5, "IMuseDigital::streamerAlloc(): ERROR: stream bufId %d already in use", bufId);
return nullptr;
}
}
for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
if (!_streams[l].soundId) {
_streams[l].endOffset = _filesHandler->seek(soundId, 0, SEEK_END, bufId);
_streams[l].curOffset = 0;
_streams[l].soundId = soundId;
_streams[l].bufId = bufId;
_streams[l].buf = bufInfoPtr->buffer;
_streams[l].bufFreeSize = bufInfoPtr->bufSize - maxRead - (_isEarlyDiMUSE ? 0 : 4);
_streams[l].loadSize = bufInfoPtr->loadSize;
_streams[l].criticalSize = bufInfoPtr->criticalSize;
_streams[l].maxRead = maxRead;
_streams[l].loadIndex = 0;
_streams[l].readIndex = 0;
_streams[l].paused = 0;
_streams[l].vocLoopFlag = 0;
_streams[l].vocLoopTriggerOffset = 0;
return &_streams[l];
}
}
debug(5, "IMuseDigital::streamerAlloc(): ERROR: no spare streams");
return nullptr;
}
int IMuseDigital::streamerClearSoundInStream(IMuseDigiStream *streamPtr) {
streamPtr->soundId = 0;
if (_lastStreamLoaded == streamPtr) {
_lastStreamLoaded = 0;
}
return 0;
}
int IMuseDigital::streamerProcessStreams() {
if (!_isEarlyDiMUSE)
dispatchPredictFirstStream();
IMuseDigiStream *stream1 = nullptr;
IMuseDigiStream *stream2 = nullptr;
for (int l = 0; l < DIMUSE_MAX_STREAMS; l++) {
if ((_streams[l].soundId) && (!_streams[l].paused)) {
if (stream2) {
if (stream1) {
debug(5, "IMuseDigital::streamerProcessStreams(): WARNING: three streams in use");
} else {
stream1 = &_streams[l];
}
} else {
stream2 = &_streams[l];
}
}
}
if (!stream1) {
if (stream2)
streamerFetchData(stream2);
return 0;
}
if (!stream2) {
if (stream1)
streamerFetchData(stream1);
return 0;
}
// Check if we've reached or surpassed criticalSize
bool critical1 = (streamerGetFreeBufferAmount(stream1) >= stream1->criticalSize);
bool critical2 = (streamerGetFreeBufferAmount(stream2) >= stream2->criticalSize);
if (!critical1) {
if (!critical2) {
if (stream1 == _lastStreamLoaded) {
streamerFetchData(stream1);
streamerFetchData(stream2);
return 0;
} else {
streamerFetchData(stream2);
streamerFetchData(stream1);
return 0;
}
} else {
streamerFetchData(stream1);
return 0;
}
}
if (!critical2) {
streamerFetchData(stream2);
return 0;
} else {
if (stream1 == _lastStreamLoaded) {
streamerFetchData(stream1);
} else {
streamerFetchData(stream2);
}
return 0;
}
}
uint8 *IMuseDigital::streamerGetStreamBuffer(IMuseDigiStream *streamPtr, int size) {
if (size > streamerGetFreeBufferAmount(streamPtr) || streamPtr->maxRead < size)
return 0;
if (size > streamPtr->bufFreeSize - streamPtr->readIndex) {
memcpy(&streamPtr->buf[streamPtr->bufFreeSize],
streamPtr->buf,
size - streamPtr->bufFreeSize + streamPtr->readIndex + (_isEarlyDiMUSE ? 0 : 4));
}
int32 readIndex_tmp = streamPtr->readIndex;
uint8 *ptr = &streamPtr->buf[readIndex_tmp];
streamPtr->readIndex = readIndex_tmp + size;
if (streamPtr->bufFreeSize <= readIndex_tmp + size)
streamPtr->readIndex = readIndex_tmp + size - streamPtr->bufFreeSize;
return ptr;
}
uint8 *IMuseDigital::streamerGetStreamBufferAtOffset(IMuseDigiStream *streamPtr, int32 offset, int size) {
if (offset + size > streamerGetFreeBufferAmount(streamPtr) || streamPtr->maxRead < size)
return 0;
int32 offsetReadIndex = offset + streamPtr->readIndex;
if (offsetReadIndex >= streamPtr->bufFreeSize) {
offsetReadIndex -= streamPtr->bufFreeSize;
}
if (size > streamPtr->bufFreeSize - offsetReadIndex) {
memcpy(&streamPtr->buf[streamPtr->bufFreeSize], streamPtr->buf, size + offsetReadIndex - streamPtr->bufFreeSize);
}
return &streamPtr->buf[offsetReadIndex];
}
int IMuseDigital::streamerSetReadIndex(IMuseDigiStream *streamPtr, int offset) {
_streamerBailFlag = 1;
if (offset > streamerGetFreeBufferAmount(streamPtr))
return -1;
streamPtr->readIndex += offset;
if (streamPtr->readIndex >= streamPtr->bufFreeSize) {
streamPtr->readIndex -= streamPtr->bufFreeSize;
}
return 0;
}
int IMuseDigital::streamerSetLoadIndex(IMuseDigiStream *streamPtr, int offset) {
_streamerBailFlag = 1;
if (offset > streamerGetFreeBufferAmount(streamPtr))
return -1;
streamPtr->loadIndex = streamPtr->readIndex + offset;
if (streamPtr->loadIndex >= streamPtr->bufFreeSize) {
streamPtr->loadIndex -= streamPtr->bufFreeSize;
}
return 0;
}
int IMuseDigital::streamerGetFreeBufferAmount(IMuseDigiStream *streamPtr) {
int32 freeBufferSize = streamPtr->loadIndex - streamPtr->readIndex;
if (freeBufferSize < 0)
freeBufferSize += streamPtr->bufFreeSize;
return freeBufferSize;
}
int IMuseDigital::streamerSetSoundToStreamFromOffset(IMuseDigiStream *streamPtr, int soundId, int32 offset) {
_streamerBailFlag = 1;
streamPtr->soundId = soundId;
streamPtr->curOffset = offset;
streamPtr->endOffset = _isEarlyDiMUSE ? _filesHandler->seek(streamPtr->soundId, 0, SEEK_END, streamPtr->bufId) : 0;
streamPtr->paused = 0;
if (_lastStreamLoaded == streamPtr) {
_lastStreamLoaded = nullptr;
}
return 0;
}
void IMuseDigital::streamerQueryStream(IMuseDigiStream *streamPtr, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused) {
if (!isFTSoundEngine())
dispatchPredictFirstStream();
bufSize = streamPtr->bufFreeSize;
criticalSize = (isFTSoundEngine() && streamPtr->paused) ? 0 : streamPtr->criticalSize;
freeSpace = streamerGetFreeBufferAmount(streamPtr);
paused = streamPtr->paused;
}
int IMuseDigital::streamerFeedStream(IMuseDigiStream *streamPtr, uint8 *srcBuf, int32 sizeToFeed, int paused) {
int32 size = streamPtr->readIndex - streamPtr->loadIndex;
if (size <= 0)
size += streamPtr->bufFreeSize;
if (sizeToFeed > (size - 4)) {
// Don't worry, judging from the intro of The Dig, this is totally expected
// to happen, and when it does, the output matches the EXE behavior
debug(5, "IMuseDigital::streamerFeedStream(): WARNING: buffer overflow");
_streamerBailFlag = 1;
int32 newTentativeSize = sizeToFeed - (size - 4) - (sizeToFeed - (size - 4)) % 12 + 12;
size = streamPtr->loadIndex - streamPtr->readIndex;
if (size < 0)
size += streamPtr->bufFreeSize;
if (size >= newTentativeSize) {
streamPtr->readIndex = newTentativeSize + streamPtr->readIndex;
if (streamPtr->bufFreeSize <= streamPtr->readIndex)
streamPtr->readIndex -= streamPtr->bufFreeSize;
}
}
if (sizeToFeed > 0) {
do {
size = streamPtr->bufFreeSize - streamPtr->loadIndex;
if (size >= sizeToFeed) {
size = sizeToFeed;
}
sizeToFeed -= size;
memcpy(&streamPtr->buf[streamPtr->loadIndex], srcBuf, size);
srcBuf += size;
int val = size + streamPtr->loadIndex;
streamPtr->curOffset += size;
streamPtr->loadIndex += size;
if (val >= streamPtr->bufFreeSize) {
streamPtr->loadIndex = val - streamPtr->bufFreeSize;
}
} while (sizeToFeed > 0);
}
streamPtr->paused = paused;
return 0;
}
int IMuseDigital::streamerFetchData(IMuseDigiStream *streamPtr) {
if (!_isEarlyDiMUSE && streamPtr->endOffset == 0) {
streamPtr->endOffset = _filesHandler->seek(streamPtr->soundId, 0, SEEK_END, streamPtr->bufId);
}
int32 size = streamPtr->readIndex - streamPtr->loadIndex;
if (size <= 0)
size += streamPtr->bufFreeSize;
int32 loadSize = streamPtr->loadSize;
int32 remainingSize = streamPtr->endOffset - streamPtr->curOffset;
if (loadSize >= size - (_isEarlyDiMUSE ? 1 : 4)) {
loadSize = size - (_isEarlyDiMUSE ? 1 : 4);
}
if (loadSize >= remainingSize) {
loadSize = remainingSize;
}
if (remainingSize <= 0) {
streamPtr->paused = 1;
if (!_isEarlyDiMUSE) {
// Pad the buffer
streamPtr->buf[streamPtr->loadIndex] = 127;
streamPtr->loadIndex++;
streamPtr->buf[streamPtr->loadIndex] = 127;
streamPtr->loadIndex++;
streamPtr->buf[streamPtr->loadIndex] = 127;
streamPtr->loadIndex++;
streamPtr->buf[streamPtr->loadIndex] = 127;
streamPtr->loadIndex++;
}
}
int32 actualAmount;
int32 requestedAmount;
while (1) {
if (!_isEarlyDiMUSE && loadSize <= 0)
return 0;
requestedAmount = streamPtr->bufFreeSize - streamPtr->loadIndex;
if (requestedAmount >= loadSize) {
requestedAmount = loadSize;
}
if (_filesHandler->seek(streamPtr->soundId, streamPtr->curOffset, SEEK_SET, streamPtr->bufId) != streamPtr->curOffset) {
debug(5, "IMuseDigital::streamerFetchData(): ERROR: invalid seek in streamer (%d), pausing stream...", streamPtr->curOffset);
streamPtr->paused = 1;
return 0;
}
_streamerBailFlag = 0;
_mutex->lock();
actualAmount = _filesHandler->read(streamPtr->soundId, &streamPtr->buf[streamPtr->loadIndex], requestedAmount, streamPtr->bufId);
_mutex->unlock();
// FT has no bailFlag
if (!_isEarlyDiMUSE && _streamerBailFlag)
return 0;
loadSize -= actualAmount;
streamPtr->curOffset += actualAmount;
_lastStreamLoaded = streamPtr;
int32 newLoadIndex = actualAmount + streamPtr->loadIndex;
streamPtr->loadIndex = newLoadIndex;
if (newLoadIndex >= streamPtr->bufFreeSize) {
streamPtr->loadIndex = newLoadIndex - streamPtr->bufFreeSize;
}
if (_isEarlyDiMUSE && streamPtr->vocLoopFlag) {
if (streamPtr->vocLoopTriggerOffset <= streamPtr->curOffset) {
dispatchVOCLoopCallback(streamPtr->soundId);
streamPtr->vocLoopFlag = 0;
}
}
if (actualAmount != requestedAmount)
break;
// FT checks for negative or zero loadSize here
if (_isEarlyDiMUSE && loadSize <= 0)
return 0;
}
debug(5, "IMuseDigital::streamerFetchData(): ERROR: unable to load the correct amount of data (req=%d, act=%d)", requestedAmount, actualAmount);
_lastStreamLoaded = nullptr;
return 0;
}
void IMuseDigital::streamerSetLoopFlag(IMuseDigiStream *streamPtr, int offset) {
streamPtr->vocLoopFlag = 1;
streamPtr->vocLoopTriggerOffset = offset;
}
void IMuseDigital::streamerRemoveLoopFlag(IMuseDigiStream *streamPtr) {
streamPtr->vocLoopFlag = 0;
}
} // End of namespace Scumm