Files
scummvm-cursorfix/engines/scumm/imuse_digi/dimuse_streamer.cpp
2026-02-02 04:50:13 +01:00

387 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