Initial commit
This commit is contained in:
195
engines/scumm/imuse_digi/dimuse_waveout.cpp
Normal file
195
engines/scumm/imuse_digi/dimuse_waveout.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
/* 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::waveOutInit(waveOutParamsStruct *waveOutSettingsStruct) {
|
||||
_waveOutBytesPerSample = 2;
|
||||
_waveOutNumChannels = _mixer->getOutputStereo() ? 2 : 1;
|
||||
_waveOutZeroLevel = 0;
|
||||
|
||||
_waveOutSampleRate = _internalSampleRate;
|
||||
_waveOutPreferredFeedSize = _internalFeedSize;
|
||||
|
||||
_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; // Note: in low latency mode this initialization is a dummy
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
void IMuseDigital::waveOutWrite(uint8 **audioData, int &feedSize, int &sampleRate) {
|
||||
uint8 *curBufferBlock;
|
||||
if (_waveOutDisableWrite)
|
||||
return;
|
||||
|
||||
feedSize = 0;
|
||||
|
||||
// A bit of context for what follows:
|
||||
//
|
||||
// In COMI, when entering certain rooms (e.g. barber shop in Plunder Island), the game
|
||||
// sends a State music event followed immediately by a Sequence music event.
|
||||
// If the iMUSE callback happens to run between these two commands, it can cause a brief
|
||||
// audio glitch where a few milliseconds of the intermediate state music is heard
|
||||
// before being replaced by the sequence music, with the fade in between canceled.
|
||||
//
|
||||
// To prevent this, we skip the very first audio callback after any music change
|
||||
// by setting _waveOutXorTrigger to 1 in playComiMusic(). This gives the scripts
|
||||
// enough time to send any follow-up sequence events before iMUSE processes
|
||||
// the audio transition, ensuring the absence of glitches and most importantly
|
||||
// no permanent audio delay on other audio feeds.
|
||||
//
|
||||
// This is very strongly inspired by what the disassembly does:
|
||||
//
|
||||
// The original DirectSound implementation uses the XOR flag combined with play
|
||||
// cursor position checks that naturally skip audio processing cycles when the
|
||||
// play cursor gets too close to the write cursor. In addition to this, the main
|
||||
// portion of tracksCallback() is executed in loop until feedSide is finally 0.
|
||||
// This creates the exact timing conditions where rapid music events have sufficient
|
||||
// time to accumulate before iMUSE processes any audio transitions.
|
||||
//
|
||||
// We're not using DirectSound, so recreate this I could either simulate the
|
||||
// play cursor handling shenanigans, or I could just do exactly what I have done :-)
|
||||
if (_vm->_game.id == GID_CMI && _waveOutXorTrigger != 0) {
|
||||
_waveOutXorTrigger = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isEarlyDiMUSE && _vm->_game.id == GID_DIG) {
|
||||
_waveOutXorTrigger ^= 1;
|
||||
if (!_waveOutXorTrigger)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mixer->isReady()) {
|
||||
curBufferBlock = &_waveOutOutputBuffer[_waveOutPreferredFeedSize * _waveOutWriteIndex * _waveOutBytesPerSample * _waveOutNumChannels];
|
||||
|
||||
*audioData = curBufferBlock;
|
||||
|
||||
sampleRate = _waveOutSampleRate;
|
||||
feedSize = _waveOutPreferredFeedSize;
|
||||
_waveOutWriteIndex = (_waveOutWriteIndex + 1) % 7;
|
||||
|
||||
byte *ptr = (byte *)malloc(_outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
|
||||
memcpy(ptr, curBufferBlock, _outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels);
|
||||
|
||||
_internalMixer->getStream(-1)->queueBuffer(ptr,
|
||||
_outputFeedSize * _waveOutBytesPerSample * _waveOutNumChannels,
|
||||
DisposeAfterUse::YES,
|
||||
waveOutGetStreamFlags());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
int IMuseDigital::waveOutDeinit() {
|
||||
_waveOutDisableWrite = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IMuseDigital::waveOutCallback() {
|
||||
Common::StackLock lock(*_mutex);
|
||||
if (_lowLatencyMode) {
|
||||
tracksLowLatencyCallback();
|
||||
} else {
|
||||
tracksCallback();
|
||||
}
|
||||
}
|
||||
|
||||
byte IMuseDigital::waveOutGetStreamFlags() {
|
||||
byte flags = Audio::FLAG_16BITS;
|
||||
|
||||
if (_mixer->getOutputStereo()) {
|
||||
flags |= Audio::FLAG_STEREO;
|
||||
}
|
||||
|
||||
#ifdef SCUMM_LITTLE_ENDIAN
|
||||
flags |= Audio::FLAG_LITTLE_ENDIAN;
|
||||
#endif
|
||||
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
|
||||
Reference in New Issue
Block a user