[Scummvm-git-logs] scummvm master -> 8d6737e16fd1fbebdb40366eb87319c947fe90cf

AndywinXp noreply at scummvm.org
Tue Jan 10 20:17:28 UTC 2023


This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
8d0e97af70 SCUMM: DIMUSE: Implement low latency mode
a41b1174ed SCUMM: v7-8: Add GUI option for toggling low latency audio mode
8d6737e16f SCUMM: DIMUSE: Increase buffer size for low latency mode


Commit: 8d0e97af70b816127f7ba56554bde8c2155227ee
    https://github.com/scummvm/scummvm/commit/8d0e97af70b816127f7ba56554bde8c2155227ee
Author: AndywinXp (andywinxp at gmail.com)
Date: 2023-01-10T21:17:23+01:00

Commit Message:
SCUMM: DIMUSE: Implement low latency mode

This fixes bug #13462

Changed paths:
    engines/scumm/imuse_digi/dimuse_defs.h
    engines/scumm/imuse_digi/dimuse_engine.cpp
    engines/scumm/imuse_digi/dimuse_engine.h
    engines/scumm/imuse_digi/dimuse_internalmixer.cpp
    engines/scumm/imuse_digi/dimuse_internalmixer.h
    engines/scumm/imuse_digi/dimuse_tracks.cpp
    engines/scumm/imuse_digi/dimuse_waveout.cpp
    engines/scumm/scumm.cpp


diff --git a/engines/scumm/imuse_digi/dimuse_defs.h b/engines/scumm/imuse_digi/dimuse_defs.h
index 010a6b0308a..f9774897bea 100644
--- a/engines/scumm/imuse_digi/dimuse_defs.h
+++ b/engines/scumm/imuse_digi/dimuse_defs.h
@@ -181,6 +181,7 @@ typedef struct {
 } IMuseDigiFade;
 
 struct IMuseDigiTrack {
+	int index;
 	IMuseDigiTrack *prev;
 	IMuseDigiTrack *next;
 	IMuseDigiDispatch *dispatchPtr;
diff --git a/engines/scumm/imuse_digi/dimuse_engine.cpp b/engines/scumm/imuse_digi/dimuse_engine.cpp
index f05fad7b7d6..49ceb7c0b55 100644
--- a/engines/scumm/imuse_digi/dimuse_engine.cpp
+++ b/engines/scumm/imuse_digi/dimuse_engine.cpp
@@ -41,7 +41,7 @@ void IMuseDigital::timer_handler(void *refCon) {
 	diMUSE->callback();
 }
 
-IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, int sampleRate, Audio::Mixer *mixer, Common::Mutex *mutex)
+IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, int sampleRate, Audio::Mixer *mixer, Common::Mutex *mutex, bool lowLatencyMode)
 	: _vm(scumm), _mixer(mixer), _mutex(mutex) {
 	assert(_vm);
 	assert(mixer);
@@ -83,12 +83,13 @@ IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, int sampleRate, Audio::Mixer *
 	_isEngineDisabled = false;
 	_checkForUnderrun = false;
 	_underrunCooldown = 0;
+	_lowLatencyMode = lowLatencyMode;
 
 	_audioNames = nullptr;
 	_numAudioNames = 0;
 
 	_emptyMarker[0] = '\0';
-	_internalMixer = new IMuseDigiInternalMixer(mixer, _internalSampleRate, _isEarlyDiMUSE);
+	_internalMixer = new IMuseDigiInternalMixer(mixer, _internalSampleRate, _isEarlyDiMUSE, _lowLatencyMode);
 	_groupsHandler = new IMuseDigiGroupsHandler(this);
 	_fadesHandler = new IMuseDigiFadesHandler(this);
 	_triggersHandler = new IMuseDigiTriggersHandler(this);
@@ -159,6 +160,9 @@ IMuseDigital::~IMuseDigital() {
 	free(_waveOutOutputBuffer);
 	_waveOutOutputBuffer = nullptr;
 
+	free(_waveOutLowLatencyOutputBuffer);
+	_waveOutLowLatencyOutputBuffer = nullptr;
+
 	free(_audioNames);
 }
 
diff --git a/engines/scumm/imuse_digi/dimuse_engine.h b/engines/scumm/imuse_digi/dimuse_engine.h
index 2daba5e2455..f563518bca3 100644
--- a/engines/scumm/imuse_digi/dimuse_engine.h
+++ b/engines/scumm/imuse_digi/dimuse_engine.h
@@ -86,6 +86,7 @@ private:
 	bool _isEngineDisabled;
 	bool _checkForUnderrun;
 	int _underrunCooldown;
+	bool _lowLatencyMode;
 
 	int _internalFeedSize;
 	int _internalSampleRate;
@@ -95,6 +96,9 @@ private:
 	int _outputFeedSize;
 	int _outputSampleRate;
 
+	// Used in low latency mode only
+	uint8 *_outputLowLatencyAudioBuffers[DIMUSE_MAX_TRACKS];
+
 	int _maxQueuedStreams; // maximum number of streams which can be queued before they are played
 	int _nominalBufferCount;
 
@@ -200,6 +204,7 @@ private:
 	void tracksSaveLoad(Common::Serializer &ser);
 	void tracksSetGroupVol();
 	void tracksCallback();
+	void tracksLowLatencyCallback();
 	int tracksStartSound(int soundId, int tryPriority, int group);
 	int tracksStopSound(int soundId);
 	int tracksStopAllSounds();
@@ -307,8 +312,14 @@ private:
 	void waveOutCallback();
 	byte waveOutGetStreamFlags();
 
+	// Low latency mode
+	void waveOutLowLatencyWrite(uint8 **audioBuffer, int &feedSize, int &sampleRate, int idx);
+	void waveOutEmptyBuffer(int idx);
+
+	uint8 *_waveOutLowLatencyOutputBuffer;
+
 public:
-	IMuseDigital(ScummEngine_v7 *scumm, int sampleRate, Audio::Mixer *mixer, Common::Mutex *mutex);
+	IMuseDigital(ScummEngine_v7 *scumm, int sampleRate, Audio::Mixer *mixer, Common::Mutex *mutex, bool lowLatencyMode = false);
 	~IMuseDigital() override;
 
 	// Wrapper functions used by the main engine
diff --git a/engines/scumm/imuse_digi/dimuse_internalmixer.cpp b/engines/scumm/imuse_digi/dimuse_internalmixer.cpp
index 74b2171798f..fccff781bac 100644
--- a/engines/scumm/imuse_digi/dimuse_internalmixer.cpp
+++ b/engines/scumm/imuse_digi/dimuse_internalmixer.cpp
@@ -30,10 +30,21 @@
 
 namespace Scumm {
 
-IMuseDigiInternalMixer::IMuseDigiInternalMixer(Audio::Mixer *mixer, int sampleRate, bool isEarlyDiMUSE) {
+IMuseDigiInternalMixer::IMuseDigiInternalMixer(Audio::Mixer *mixer, int sampleRate, bool isEarlyDiMUSE, bool lowLatencyMode) {
 	_mixer = mixer;
-	_stream = Audio::makeQueuingAudioStream(sampleRate, _mixer->getOutputStereo());
+	_sampleRate = sampleRate;
+	_lowLatencyMode = lowLatencyMode;
+
+	// Mark all separate streams for low latency mode as available...
+	for (int i = 0; i < ARRAYSIZE(_separateStreams); i++) {
+		_separateStreams[i] = nullptr;
+	}
+
+	if (!_lowLatencyMode || _isEarlyDiMUSE)
+		_stream = Audio::makeQueuingAudioStream(_sampleRate, _mixer->getOutputStereo());
+
 	_isEarlyDiMUSE = isEarlyDiMUSE;
+
 	_radioChatter = 0;
 	_amp8Table = nullptr;
 }
@@ -148,10 +159,21 @@ int IMuseDigiInternalMixer::init(int bytesPerSample, int numChannels, uint8 *mix
 				((int16 *)_softLMID)[-i - 1] = -1 - (int16)softLcurValue;
 			}
 		}
-		_mixer->playStream(Audio::Mixer::kPlainSoundType, &_channelHandle, _stream, -1, Audio::Mixer::kMaxChannelVolume, false);
+
+		// In low latency the following stream is still used
+		// for FTSMUSH cutscenes, so let's still play it.
+		if (!_lowLatencyMode || _isEarlyDiMUSE) {
+			_mixer->playStream(
+				Audio::Mixer::kPlainSoundType,
+				&_channelHandle,
+				_stream,
+				-1,
+				Audio::Mixer::kMaxChannelVolume);
+		}
+
 		return 0;
 	} else {
-		debug(5, "DiMUSE_InternalMixer::init(): ERROR: couldn't allocate mixer tables");
+		debug(5, "IMuseDigiInternalMixer::init(): ERROR: couldn't allocate mixer tables");
 		return -1;
 	}
 }
@@ -309,6 +331,44 @@ int IMuseDigiInternalMixer::loop(uint8 **destBuffer, int len) {
 	return 0;
 }
 
+void IMuseDigiInternalMixer::setCurrentMixerBuffer(uint8 *newBuf) {
+	// Used to swap mixer buffers in low latency mode
+	_mixBuf = newBuf;
+}
+
+void IMuseDigiInternalMixer::endStream(int idx) {
+	// We mark the stream as finished and then remove
+	// the reference: in this way, the stream will have time
+	// to flush the remaining sound data and will be disposed
+	// by the mixer, while we freed up a slot for a new stream.
+	if (_lowLatencyMode && idx != -1) {
+		_separateStreams[idx]->finish();
+		_separateStreams[idx] = nullptr;
+	}
+}
+
+Audio::QueuingAudioStream *IMuseDigiInternalMixer::getStream(int idx) {
+	if (!_lowLatencyMode || idx == -1) {
+		// Stream for the original mode
+		return _stream;
+	} else if (!_separateStreams[idx]) {
+		// New stream for a new sound
+		_separateStreams[idx] = Audio::makeQueuingAudioStream(_sampleRate, _mixer->getOutputStereo());
+
+		_mixer->playStream(
+			Audio::Mixer::kPlainSoundType,
+			&_separateChannelHandles[idx],
+			_separateStreams[idx],
+			-1,
+			Audio::Mixer::kMaxChannelVolume);
+
+		return _separateStreams[idx];
+	} else {
+		// On-going stream
+		return _separateStreams[idx];
+	}
+}
+
 void IMuseDigiInternalMixer::mixBits8Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable, bool ftIs11025Hz) {
 	uint16 *mixBufCurCell;
 	uint8 *srcBuf_ptr;
diff --git a/engines/scumm/imuse_digi/dimuse_internalmixer.h b/engines/scumm/imuse_digi/dimuse_internalmixer.h
index e9ecf18dc77..2edc7379bc9 100644
--- a/engines/scumm/imuse_digi/dimuse_internalmixer.h
+++ b/engines/scumm/imuse_digi/dimuse_internalmixer.h
@@ -58,8 +58,10 @@ private:
 	int _radioChatter;
 	int _outWordSize;
 	int _outChannelCount;
+	int _sampleRate;
 	int _stereoReverseFlag;
 	bool _isEarlyDiMUSE;
+	bool _lowLatencyMode;
 
 	void mixBits8Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable, bool ftIs11025Hz);
 	void mixBits12Mono(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
@@ -78,7 +80,7 @@ private:
 	void mixBits16Stereo(uint8 *srcBuf, int32 inFrameCount, int feedSize, int32 mixBufStartIndex, int32 *ampTable);
 
 public:
-	IMuseDigiInternalMixer(Audio::Mixer *mixer, int sampleRate, bool isEarlyDiMUSE);
+	IMuseDigiInternalMixer(Audio::Mixer *mixer, int sampleRate, bool isEarlyDiMUSE, bool lowLatencyMode = false);
 	~IMuseDigiInternalMixer();
 	int  init(int bytesPerSample, int numChannels, uint8 *mixBuf, int mixBufSize, int sizeSampleKB, int mixChannelsNum);
 	void setRadioChatter();
@@ -88,6 +90,14 @@ public:
 	void mix(uint8 *srcBuf, int32 inFrameCount, int wordSize, int channelCount, int feedSize, int32 mixBufStartIndex, int volume, int pan, bool ftIs11025Hz);
 	int  loop(uint8 **destBuffer, int len);
 	Audio::QueuingAudioStream *_stream;
+
+	// For low latency audio
+	void setCurrentMixerBuffer(uint8 *newBuf);
+	void endStream(int idx);
+	Audio::QueuingAudioStream *getStream(int idx);
+
+	Audio::QueuingAudioStream *_separateStreams[DIMUSE_MAX_TRACKS];
+	Audio::SoundHandle _separateChannelHandles[DIMUSE_MAX_TRACKS];
 };
 
 } // End of namespace Scumm
diff --git a/engines/scumm/imuse_digi/dimuse_tracks.cpp b/engines/scumm/imuse_digi/dimuse_tracks.cpp
index 86b86c031cb..5b9e7888391 100644
--- a/engines/scumm/imuse_digi/dimuse_tracks.cpp
+++ b/engines/scumm/imuse_digi/dimuse_tracks.cpp
@@ -24,7 +24,7 @@
 namespace Scumm {
 
 int IMuseDigital::tracksInit() {
-	_trackCount = 6;
+	_trackCount = _lowLatencyMode ? DIMUSE_MAX_TRACKS : 6;
 	_tracksPauseTimer = 0;
 	_trackList = nullptr;
 
@@ -43,6 +43,7 @@ int IMuseDigital::tracksInit() {
 	}
 
 	for (int l = 0; l < _trackCount; l++) {
+		_tracks[l].index = l;
 		_tracks[l].prev = nullptr;
 		_tracks[l].next = nullptr;
 		_tracks[l].dispatchPtr = dispatchGetDispatchByTrackId(l);
@@ -213,6 +214,109 @@ void IMuseDigital::tracksCallback() {
 	}
 }
 
+void IMuseDigital::tracksLowLatencyCallback() {
+	// Why do we need a low latency mode?
+	//
+	// For every audio callback, this engine works by collecting all the sound
+	// data for every track and by mixing it up in a single output stream.
+	// This is exactly how the original executables worked, so our implementation
+	// provides a very faithful recreation of that experience. And it comes with
+	// a compromise that e.g. The Dig and Full Throttle didn't have to front:
+	//
+	// in order to provide glitchless audio, an appropriate stream queue size
+	// has to be enforced: a longer queue yields a lower probability of audio glitches
+	// but a higher latency, and viceversa. In our case, this depends on the audio backend
+	// configuration. As such: some configurations might encounter audible latency (#13462).
+	//
+	// We solve this issue by offering this alternate low latency mode which, instead
+	// of keeping a single stream for everything, creates (and disposes) streams on the fly
+	// for every different sound. This means that whenever the new sound data is ready,
+	// a new stream is initialized and played immediately, without having to wait for all
+	// the other sounds to be processed and mixed in the same sample pool.
+
+	if (_tracksPauseTimer) {
+		if (++_tracksPauseTimer < 3)
+			return;
+		_tracksPauseTimer = 3;
+	}
+
+	// The callback path is heavily inspired from the original one (see tracksCallback()),
+	// but it handles each track separatedly, with the exception of SMUSH audio for Full
+	// Throttle: this is why we operate on two parallel paths...
+
+	if (!_isEarlyDiMUSE)
+		dispatchPredictFirstStream();
+
+	IMuseDigiTrack *track = _trackList;
+
+	// This flag ensures that, even when no track is available,
+	// FT SMUSH audio can still be played. At least, and AT MOST once :-)
+	bool runSMUSHAudio = _isEarlyDiMUSE;
+
+	while (track || runSMUSHAudio) {
+
+		IMuseDigiTrack *next = track ? track->next : nullptr;
+		int idx = track ? track->index : -1;
+
+		// We use a separate queue cardinality handling, since SMUSH audio and iMUSE audio can overlap...
+		bool canQueueBufs = (int)_internalMixer->getStream(idx)->numQueuedStreams() < _maxQueuedStreams;
+		bool canQueueFtSmush = (int)_internalMixer->getStream(-1)->numQueuedStreams() < _maxQueuedStreams;
+
+		if (canQueueBufs) {
+			if (track)
+				waveOutLowLatencyWrite(&_outputLowLatencyAudioBuffers[idx], _outputFeedSize, _outputSampleRate, idx);
+
+			// Notice how SMUSH audio for Full Throttle uses the original single-stream mode:
+			// this is necessary both for code cleanliness and for correct audio sync.
+			if (runSMUSHAudio && canQueueFtSmush)
+				waveOutWrite(&_outputAudioBuffer, _outputFeedSize, _outputSampleRate);
+
+			if (_outputFeedSize != 0) {
+				// FT SMUSH dispatch processing...
+				if (runSMUSHAudio && canQueueFtSmush && _isEarlyDiMUSE && _splayer && _splayer->isAudioCallbackEnabled()) {
+					_internalMixer->setCurrentMixerBuffer(_outputAudioBuffer);
+					_internalMixer->clearMixerBuffer();
+
+					_splayer->processDispatches(_outputFeedSize);
+					_internalMixer->loop(&_outputAudioBuffer, _outputFeedSize);
+				}
+
+				// Ordinary audio tracks handling...
+				if (track) {
+					_internalMixer->setCurrentMixerBuffer(_outputLowLatencyAudioBuffers[idx]);
+					_internalMixer->clearMixerBuffer();
+
+					if (!_tracksPauseTimer) {
+						if (_isEarlyDiMUSE) {
+							dispatchProcessDispatches(track, _outputFeedSize);
+						} else {
+							dispatchProcessDispatches(track, _outputFeedSize, _outputSampleRate);
+						}
+					}
+
+					_internalMixer->loop(&_outputLowLatencyAudioBuffers[idx], _outputFeedSize);
+
+					// The Dig tries to write a second time
+					if (!_isEarlyDiMUSE && _vm->_game.id == GID_DIG) {
+						waveOutLowLatencyWrite(&_outputLowLatencyAudioBuffers[idx], _outputFeedSize, _outputSampleRate, idx);
+					}
+
+					// If, after processing the track dispatch, the sound is set to zero
+					// it means that it has reached the end: let's notify its stream...
+					if (track->soundId == 0) {
+						_internalMixer->endStream(idx);
+					}
+				}
+			}
+		}
+
+		if (track)
+			track = next;
+
+		runSMUSHAudio = false;
+	}
+}
+
 int IMuseDigital::tracksStartSound(int soundId, int tryPriority, int group) {
 	int priority = clampNumber(tryPriority, 0, 127);
 
@@ -400,6 +504,9 @@ void IMuseDigital::tracksClear(IMuseDigiTrack *trackPtr) {
 		_vm->_res->unlock(rtSound, trackPtr->soundId);
 	}
 
+	if (_lowLatencyMode)
+		waveOutEmptyBuffer(trackPtr->index);
+
 	trackPtr->soundId = 0;
 }
 
diff --git a/engines/scumm/imuse_digi/dimuse_waveout.cpp b/engines/scumm/imuse_digi/dimuse_waveout.cpp
index 56080d9b6e8..c9712a998d3 100644
--- a/engines/scumm/imuse_digi/dimuse_waveout.cpp
+++ b/engines/scumm/imuse_digi/dimuse_waveout.cpp
@@ -31,19 +31,36 @@ int IMuseDigital::waveOutInit(waveOutParamsStruct *waveOutSettingsStruct) {
 	_waveOutSampleRate = _internalSampleRate;
 	_waveOutPreferredFeedSize = _internalFeedSize;
 
-	// Nine buffers (waveOutPreferredFeedSize * 4 bytes each), two will be used for the mixer
-	_waveOutOutputBuffer = (uint8 *)malloc(_waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
-	_waveOutMixBuffer = _waveOutOutputBuffer + (_waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 7); // 8-th buffer
+	_waveOutOutputBuffer = nullptr;
+	_waveOutMixBuffer = nullptr;
+	_waveOutLowLatencyOutputBuffer = nullptr;
+
+	if (!_lowLatencyMode || _isEarlyDiMUSE) {
+		// Nine buffers (waveOutPreferredFeedSize * 4 bytes each), two will be used for the mixer
+		_waveOutOutputBuffer = (uint8 *)malloc(_waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
+		_waveOutMixBuffer = _waveOutOutputBuffer + (_waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 7); // 8-th buffer
+	}
+
+	// Replicate another set of buffers for the low latency mode, we will use the previous ones for cutscenes if the mode is active
+	if (_lowLatencyMode) {
+		_waveOutLowLatencyOutputBuffer = (uint8 *)malloc(_waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
+	}
 
 	// This information will be fed to the internal mixer during its initialization
 	waveOutSettingsStruct->bytesPerSample = _waveOutBytesPerSample * 8;
 	waveOutSettingsStruct->numChannels = _waveOutNumChannels;
 	waveOutSettingsStruct->mixBufSize = (_waveOutBytesPerSample * _waveOutNumChannels) * _waveOutPreferredFeedSize;
 	waveOutSettingsStruct->sizeSampleKB = 0;
-	waveOutSettingsStruct->mixBuf = _waveOutMixBuffer;
+	waveOutSettingsStruct->mixBuf = _waveOutMixBuffer; // Note: in low latency mode this initialization is a dummy
 
-	// Init the buffer filling it with zero volume samples
-	memset(_waveOutOutputBuffer, _waveOutZeroLevel, _waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
+	// Init the buffers filling them with zero volume samples
+	if (!_lowLatencyMode || _isEarlyDiMUSE) {
+		memset(_waveOutOutputBuffer, _waveOutZeroLevel, _waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
+	}
+
+	if (_lowLatencyMode) {
+		memset(_waveOutLowLatencyOutputBuffer, _waveOutZeroLevel, _waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize * 9);
+	}
 
 	_waveOutDisableWrite = 0;
 	return 0;
@@ -73,7 +90,7 @@ void IMuseDigital::waveOutWrite(uint8 **audioData, int &feedSize, int &sampleRat
 		byte *ptr = (byte *)malloc(_outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
 		memcpy(ptr, curBufferBlock, _outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
 
-		_internalMixer->_stream->queueBuffer(ptr,
+		_internalMixer->getStream(-1)->queueBuffer(ptr,
 			_outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels,
 			DisposeAfterUse::YES,
 			waveOutGetStreamFlags());
@@ -88,7 +105,11 @@ int IMuseDigital::waveOutDeinit() {
 
 void IMuseDigital::waveOutCallback() {
 	Common::StackLock lock(*_mutex);
-	tracksCallback();
+	if (_lowLatencyMode) {
+		tracksLowLatencyCallback();
+	} else {
+		tracksCallback();
+	}
 }
 
 byte IMuseDigital::waveOutGetStreamFlags() {
@@ -104,4 +125,40 @@ byte IMuseDigital::waveOutGetStreamFlags() {
 	return flags;
 }
 
+void IMuseDigital::waveOutLowLatencyWrite(uint8 **audioData, int &feedSize, int &sampleRate, int idx) {
+	uint8 *curBufferBlock;
+	if (_waveOutDisableWrite)
+		return;
+
+	if (!_isEarlyDiMUSE && _vm->_game.id == GID_DIG) {
+		_waveOutXorTrigger ^= 1;
+		if (!_waveOutXorTrigger)
+			return;
+	}
+
+	feedSize = 0;
+	if (_mixer->isReady()) {
+		curBufferBlock = &_waveOutLowLatencyOutputBuffer[_waveOutPreferredFeedSize * idx * _waveOutBytesPerSample * _waveOutNumChannels];
+
+		*audioData = curBufferBlock;
+
+		sampleRate = _waveOutSampleRate;
+		feedSize = _waveOutPreferredFeedSize;
+
+		byte *ptr = (byte *)malloc(_outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
+		memcpy(ptr, curBufferBlock, _outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
+
+		_internalMixer->getStream(idx)->queueBuffer(ptr,
+			 _outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels,
+			 DisposeAfterUse::YES,
+			 waveOutGetStreamFlags());
+	}
+}
+
+void IMuseDigital::waveOutEmptyBuffer(int idx) {
+	// This is necessary in low latency mode to clean-up the buffers of stale/finished sounds
+	int bufferSize = _waveOutNumChannels * _waveOutBytesPerSample * _waveOutPreferredFeedSize;
+	memset(&_waveOutLowLatencyOutputBuffer[bufferSize * idx], _waveOutZeroLevel, bufferSize);
+}
+
 } // End of namespace Scumm
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 0287e20a73d..583a35dc570 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -1443,6 +1443,12 @@ void ScummEngine_v7::setupScumm(const Common::String &macResourceFile) {
 		}
 	}
 
+	bool lowLatencyMode = false;
+	ConfMan.registerDefault("dimuse_low_latency_mode", false);
+	if (ConfMan.hasKey("dimuse_low_latency_mode", _targetName)) {
+		lowLatencyMode = ConfMan.getBool("dimuse_low_latency_mode");
+	}
+
 	_musicEngine = _imuseDigital = new IMuseDigital(this, sampleRate, _mixer, &_resourceAccessMutex);
 
 	if (filesAreCompressed) {


Commit: a41b1174ed3e64853e9f9796430ef901fa3c08af
    https://github.com/scummvm/scummvm/commit/a41b1174ed3e64853e9f9796430ef901fa3c08af
Author: AndywinXp (andywinxp at gmail.com)
Date: 2023-01-10T21:17:23+01:00

Commit Message:
SCUMM: v7-8: Add GUI option for toggling low latency audio mode

Changed paths:
    engines/scumm/detection.h
    engines/scumm/detection_tables.h
    engines/scumm/metaengine.cpp
    engines/scumm/scumm.cpp


diff --git a/engines/scumm/detection.h b/engines/scumm/detection.h
index 4eb36ca1408..51927c822ef 100644
--- a/engines/scumm/detection.h
+++ b/engines/scumm/detection.h
@@ -33,6 +33,7 @@ namespace Scumm {
 #define GUIO_ENHANCEMENTS                              GUIO_GAMEOPTIONS2
 #define GUIO_AUDIO_OVERRIDE                            GUIO_GAMEOPTIONS3
 #define GUIO_ORIGINALGUI                               GUIO_GAMEOPTIONS4
+#define GUIO_LOWLATENCYAUDIO                           GUIO_GAMEOPTIONS5
 
 /**
  * Descriptor of a specific SCUMM game. Used internally to store
diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h
index ab85848be6b..4092f8b5ef7 100644
--- a/engines/scumm/detection_tables.h
+++ b/engines/scumm/detection_tables.h
@@ -213,15 +213,15 @@ static const GameSettings gameVariantsTable[] = {
 	{"samnmax",  "", 0, GID_SAMNMAX,  6, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_GM, GF_USE_KEY, UNK, GUIO3(GUIO_RENDEREGA, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI)},
 	{"samnmax",  "Floppy", 0, GID_SAMNMAX,  6, 0, MDT_ADLIB | MDT_MIDI | MDT_PREFER_GM, GF_USE_KEY, UNK, GUIO4(GUIO_NOSPEECH, GUIO_RENDEREGA, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI)},
 
-	{"ft",   "", 0, GID_FT,  7, 0, MDT_NONE, 0, UNK, GUIO3(GUIO_NOMIDI, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI)},
-	{"ft",   "Demo", 0, GID_FT,  7, 0, MDT_NONE, GF_DEMO, UNK, GUIO2(GUIO_NOMIDI, GUIO_ORIGINALGUI)},
+	{"ft",   "", 0, GID_FT,  7, 0, MDT_NONE, 0, UNK, GUIO4(GUIO_NOMIDI, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI, GUIO_LOWLATENCYAUDIO)},
+	{"ft",   "Demo", 0, GID_FT,  7, 0, MDT_NONE, GF_DEMO, UNK, GUIO3(GUIO_NOMIDI, GUIO_ORIGINALGUI, GUIO_LOWLATENCYAUDIO)},
 
-	{"dig",  "", 0, GID_DIG, 7, 0, MDT_NONE, 0, UNK, GUIO3(GUIO_NOMIDI, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI)},
-	{"dig",  "Demo", 0, GID_DIG, 7, 0, MDT_NONE, GF_DEMO, UNK, GUIO2(GUIO_NOMIDI, GUIO_ORIGINALGUI)},
-	{"dig",  "Steam", "steam", GID_DIG, 7, 0, MDT_NONE, 0, UNK, GUIO3(GUIO_NOMIDI, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI)},
+	{"dig",  "", 0, GID_DIG, 7, 0, MDT_NONE, 0, UNK, GUIO4(GUIO_NOMIDI, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI, GUIO_LOWLATENCYAUDIO)},
+	{"dig",  "Demo", 0, GID_DIG, 7, 0, MDT_NONE, GF_DEMO, UNK, GUIO3(GUIO_NOMIDI, GUIO_ORIGINALGUI, GUIO_LOWLATENCYAUDIO)},
+	{"dig",  "Steam", "steam", GID_DIG, 7, 0, MDT_NONE, 0, UNK, GUIO4(GUIO_NOMIDI, GUIO_ENHANCEMENTS, GUIO_ORIGINALGUI, GUIO_LOWLATENCYAUDIO)},
 
-	{"comi", "", 0, GID_CMI, 8, 0, MDT_NONE, 0, Common::kPlatformWindows, GUIO3(GUIO_NOMIDI, GUIO_NOASPECT, GUIO_ORIGINALGUI)},
-	{"comi", "Demo", 0, GID_CMI, 8, 0, MDT_NONE, GF_DEMO, Common::kPlatformWindows, GUIO3(GUIO_NOMIDI, GUIO_NOASPECT, GUIO_ORIGINALGUI)},
+	{"comi", "", 0, GID_CMI, 8, 0, MDT_NONE, 0, Common::kPlatformWindows, GUIO4(GUIO_NOMIDI, GUIO_NOASPECT, GUIO_ORIGINALGUI, GUIO_LOWLATENCYAUDIO)},
+	{"comi", "Demo", 0, GID_CMI, 8, 0, MDT_NONE, GF_DEMO, Common::kPlatformWindows, GUIO4(GUIO_NOMIDI, GUIO_NOASPECT, GUIO_ORIGINALGUI, GUIO_LOWLATENCYAUDIO)},
 
 	// Humongous Entertainment Scumm Version 6
 	{"activity", "", 0, GID_HEGAME, 6, 62, MDT_ADLIB | MDT_MIDI, GF_USE_KEY, UNK, GUIO2(GUIO_NOLAUNCHLOAD, GUIO_AUDIO_OVERRIDE)},
diff --git a/engines/scumm/metaengine.cpp b/engines/scumm/metaengine.cpp
index 20b220a2a07..ec82248d205 100644
--- a/engines/scumm/metaengine.cpp
+++ b/engines/scumm/metaengine.cpp
@@ -670,6 +670,15 @@ static const ExtraGuiOption enableOriginalGUI = {
 	0
 };
 
+static const ExtraGuiOption enableLowLatencyAudio = {
+	_s("Enable low latency audio mode"),
+	_s("Allows the game to use low latency audio, at the cost of sound accuracy."),
+	"dimuse_low_latency_mode",
+	false,
+	0,
+	0
+};
+
 const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &target) const {
 	ExtraGuiOptions options;
 	// Query the GUI options
@@ -685,6 +694,9 @@ const ExtraGuiOptions ScummMetaEngine::getExtraGuiOptions(const Common::String &
 	if (target.empty() || guiOptions.contains(GUIO_ENHANCEMENTS)) {
 		options.push_back(enableEnhancements);
 	}
+	if (target.empty() || guiOptions.contains(GUIO_LOWLATENCYAUDIO)) {
+		options.push_back(enableLowLatencyAudio);
+	}
 	if (target.empty() || guiOptions.contains(GUIO_AUDIO_OVERRIDE)) {
 		options.push_back(audioOverride);
 	}
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 583a35dc570..91b72ac5614 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -1449,7 +1449,7 @@ void ScummEngine_v7::setupScumm(const Common::String &macResourceFile) {
 		lowLatencyMode = ConfMan.getBool("dimuse_low_latency_mode");
 	}
 
-	_musicEngine = _imuseDigital = new IMuseDigital(this, sampleRate, _mixer, &_resourceAccessMutex);
+	_musicEngine = _imuseDigital = new IMuseDigital(this, sampleRate, _mixer, &_resourceAccessMutex, lowLatencyMode);
 
 	if (filesAreCompressed) {
 		GUI::MessageDialog dialog(_(


Commit: 8d6737e16fd1fbebdb40366eb87319c947fe90cf
    https://github.com/scummvm/scummvm/commit/8d6737e16fd1fbebdb40366eb87319c947fe90cf
Author: AndywinXp (andywinxp at gmail.com)
Date: 2023-01-10T21:17:23+01:00

Commit Message:
SCUMM: DIMUSE: Increase buffer size for low latency mode

Changed paths:
    engines/scumm/imuse_digi/dimuse_engine.cpp
    engines/scumm/imuse_digi/dimuse_tracks.cpp


diff --git a/engines/scumm/imuse_digi/dimuse_engine.cpp b/engines/scumm/imuse_digi/dimuse_engine.cpp
index 49ceb7c0b55..2856115d62d 100644
--- a/engines/scumm/imuse_digi/dimuse_engine.cpp
+++ b/engines/scumm/imuse_digi/dimuse_engine.cpp
@@ -50,9 +50,14 @@ IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, int sampleRate, Audio::Mixer *
 	_callbackFps = DIMUSE_TIMER_BASE_RATE_HZ;
 	_usecPerInt = DIMUSE_TIMER_BASE_RATE_USEC;
 
+	_lowLatencyMode = lowLatencyMode;
 	_internalSampleRate = sampleRate;
 	_internalFeedSize = (int)(DIMUSE_BASE_FEEDSIZE * ((float)_internalSampleRate / DIMUSE_BASE_SAMPLERATE));
 
+	if (_lowLatencyMode) {
+		_internalFeedSize *= 2;
+	}
+
 	_splayer = nullptr;
 	_isEarlyDiMUSE = (_vm->_game.id == GID_FT || (_vm->_game.id == GID_DIG && _vm->_game.features & GF_DEMO));
 
@@ -83,7 +88,6 @@ IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, int sampleRate, Audio::Mixer *
 	_isEngineDisabled = false;
 	_checkForUnderrun = false;
 	_underrunCooldown = 0;
-	_lowLatencyMode = lowLatencyMode;
 
 	_audioNames = nullptr;
 	_numAudioNames = 0;
diff --git a/engines/scumm/imuse_digi/dimuse_tracks.cpp b/engines/scumm/imuse_digi/dimuse_tracks.cpp
index 5b9e7888391..811f08e8e32 100644
--- a/engines/scumm/imuse_digi/dimuse_tracks.cpp
+++ b/engines/scumm/imuse_digi/dimuse_tracks.cpp
@@ -259,8 +259,8 @@ void IMuseDigital::tracksLowLatencyCallback() {
 		int idx = track ? track->index : -1;
 
 		// We use a separate queue cardinality handling, since SMUSH audio and iMUSE audio can overlap...
-		bool canQueueBufs = (int)_internalMixer->getStream(idx)->numQueuedStreams() < _maxQueuedStreams;
-		bool canQueueFtSmush = (int)_internalMixer->getStream(-1)->numQueuedStreams() < _maxQueuedStreams;
+		bool canQueueBufs = (int)_internalMixer->getStream(idx)->numQueuedStreams() < (_maxQueuedStreams + 1);
+		bool canQueueFtSmush = (int)_internalMixer->getStream(-1)->numQueuedStreams() < (_maxQueuedStreams + 1);
 
 		if (canQueueBufs) {
 			if (track)




More information about the Scummvm-git-logs mailing list