scummvm/engines/scumm/imuse_digi/dimuse_engine.cpp
AndywinXp ca36141f9b JANITORIAL: SCUMM: Remove magic numbers from DiMUSE engine
Also improve readability on some bits
2023-08-03 23:58:38 +02:00

1123 lines
37 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 "common/system.h"
#include "common/timer.h"
#include "scumm/actor.h"
#include "scumm/scumm_v7.h"
#include "scumm/sound.h"
#include "scumm/imuse_digi/dimuse_engine.h"
#include "scumm/imuse_digi/dimuse_defs.h"
#include "scumm/imuse_digi/dimuse_bndmgr.h"
#include "scumm/imuse_digi/dimuse_sndmgr.h"
#include "scumm/imuse_digi/dimuse_tables.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
namespace Scumm {
void IMuseDigital::timer_handler(void *refCon) {
IMuseDigital *diMUSE = (IMuseDigital *)refCon;
diMUSE->callback();
}
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);
// 50 Hz rate for the callback
_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));
if (_isEarlyDiMUSE) {
memset(_ftCrossfadeBuffer, 0, sizeof(_ftCrossfadeBuffer));
}
_curMixerMusicVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
_curMixerSpeechVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
_curMixerSFXVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
_currentSpeechVolume = 0;
_currentSpeechFrequency = 0;
_currentSpeechPan = 0;
_waveOutXorTrigger = 0;
_waveOutWriteIndex = 0;
_waveOutDisableWrite = 0;
_waveOutPreferredFeedSize = 0;
_dispatchFadeSize = 0;
_stopSequenceFlag = 0;
_scriptInitializedFlag = 0;
_callbackInterruptFlag = 0;
_spooledMusicEnabled = true;
_radioChatterSFX = false;
_isEngineDisabled = false;
_checkForUnderrun = false;
_underrunCooldown = 0;
_audioNames = nullptr;
_numAudioNames = 0;
_emptyMarker[0] = '\0';
_internalMixer = new IMuseDigiInternalMixer(mixer, _internalSampleRate, _isEarlyDiMUSE, _lowLatencyMode);
_groupsHandler = new IMuseDigiGroupsHandler(this);
_fadesHandler = new IMuseDigiFadesHandler(this);
_triggersHandler = new IMuseDigiTriggersHandler(this);
_filesHandler = new IMuseDigiFilesHandler(this, scumm);
diMUSEInitialize();
diMUSEInitializeScript();
if (_vm->_game.id == GID_CMI) {
_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_SPEECH, 176000, 44000, 88000);
_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_MUSIC, 528000, 44000, 352000);
} else if (_vm->_game.id == GID_DIG && !isFTSoundEngine()) {
_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_SPEECH, 132000, 22000, 44000);
_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_MUSIC, 660000, 11000, 132000);
} else {
_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_SPEECH, 110000, 22000, 44000);
_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_MUSIC, 220000, 22000, 44000);
}
_filesHandler->allocSoundBuffer(DIMUSE_BUFFER_SFX, 198000, 0, 0);
if (_mixer->getOutputBufSize() != 0) {
// Let's find the optimal value for the maximum number of streams which can stay in the queue at once;
// (A number which is too low can lead to buffer underrun, while the higher the number is, the higher is the audio latency)
_maxQueuedStreams = (int)ceil((_mixer->getOutputBufSize() / _waveOutPreferredFeedSize) / ((float)_mixer->getOutputRate() / _internalSampleRate));
// This mixer's optimal output sample rate for this audio engine is one which is a multiple of 22050Hz;
// if we're dealing with one which is a multiple of 48000Hz, compensate the number of queued streams...
if (_mixer->getOutputRate() % _internalSampleRate) {
_maxQueuedStreams++;
}
// The lower optimal bound is always 4, except if we're operating in low latency mode
_maxQueuedStreams = MAX(_mixer->getOutputBufSize() <= 1024 ? 3 : 4, _maxQueuedStreams);
} else {
debug(5, "IMuseDigital::IMuseDigital(): WARNING: output audio buffer size not specified for this platform, defaulting _maxQueuedStreams to 4");
_maxQueuedStreams = 4;
}
// This value has been calculated in a way that is a good compromise
// between the absence of underruns and the lowest latency achievable.
// Of course this is never perfect, given the vast number of platforms
// we support, so the value might eventually be corrected by our custom
// adaptive buffer underrun correction routine.
_nominalBufferCount = _maxQueuedStreams;
_underrunCooldown = _maxQueuedStreams;
_vm->getTimerManager()->installTimerProc(timer_handler, 1000000 / _callbackFps, this, "IMuseDigital");
}
IMuseDigital::~IMuseDigital() {
_vm->getTimerManager()->removeTimerProc(timer_handler);
_filesHandler->deallocSoundBuffer(DIMUSE_BUFFER_SPEECH);
_filesHandler->deallocSoundBuffer(DIMUSE_BUFFER_MUSIC);
_filesHandler->deallocSoundBuffer(DIMUSE_BUFFER_SFX);
cmdsDeinit();
diMUSETerminate();
delete _internalMixer;
delete _groupsHandler;
delete _fadesHandler;
delete _triggersHandler;
delete _filesHandler;
// Deinit the Dispatch module
free(_dispatchBuffer);
_dispatchBuffer = nullptr;
// Deinit the WaveOut module
free(_waveOutOutputBuffer);
_waveOutOutputBuffer = nullptr;
free(_waveOutLowLatencyOutputBuffer);
_waveOutLowLatencyOutputBuffer = nullptr;
free(_audioNames);
}
int IMuseDigital::roundRobinSetBufferCount() {
int minStreams = MAX<int>(_nominalBufferCount - 5, 1);
int maxStreams = _nominalBufferCount + 5;
_maxQueuedStreams++;
if (_maxQueuedStreams > maxStreams) {
_maxQueuedStreams = minStreams;
}
return _maxQueuedStreams;
}
void IMuseDigital::adaptBufferCount() {
_maxQueuedStreams++;
_nominalBufferCount = _maxQueuedStreams;
}
void IMuseDigital::stopSound(int sound) {
diMUSEStopSound(sound);
}
void IMuseDigital::stopAllSounds() {
diMUSEStopAllSounds();
}
int IMuseDigital::isSoundRunning(int soundId) {
return diMUSEGetParam(soundId, DIMUSE_P_SND_TRACK_NUM) > 0;
}
int IMuseDigital::startVoice(int soundId, const char *soundName, byte speakingActorId) {
_filesHandler->closeSoundImmediatelyById(soundId);
int fileDoesNotExist = 0;
if (_vm->_game.id == GID_DIG) {
if (!strcmp(soundName, "PIG.018"))
fileDoesNotExist = _filesHandler->setCurrentSpeechFilename("PIG.019");
else
fileDoesNotExist = _filesHandler->setCurrentSpeechFilename(soundName);
if (fileDoesNotExist)
return 1;
fillStreamsWhileMusicCritical(5);
// WORKAROUND for this particular sound file not playing (this is a bug in the original):
// this is happening because the sound buffer responsible for speech
// is still busy with the previous speech file playing during the SAN
// movie. We just stop the SMUSH speech sound before playing NEXUS.029.
if (!strcmp(soundName, "NEXUS.029")) {
diMUSEStopSound(DIMUSE_SMUSH_SOUNDID + DIMUSE_BUFFER_SPEECH);
}
// Set up a trigger for extracting mouth sync times;
// see Sound::extractSyncsFromDiMUSEMarker() for details.
// Setting up a trigger with an empty marker is a shortcut for
// activating the trigger for any marker.
diMUSESetTrigger(kTalkSoundID, 0, DIMUSE_C_GET_MARKER_SYNCS);
diMUSEStartStream(kTalkSoundID, 127, DIMUSE_BUFFER_SPEECH);
diMUSESetParam(kTalkSoundID, DIMUSE_P_GROUP, DIMUSE_GROUP_SPEECH);
if (speakingActorId == _vm->VAR(_vm->VAR_EGO)) {
diMUSESetParam(kTalkSoundID, DIMUSE_P_MAILBOX, 0);
diMUSESetParam(kTalkSoundID, DIMUSE_P_VOLUME, 127);
} else {
diMUSESetParam(kTalkSoundID, DIMUSE_P_MAILBOX, _radioChatterSFX);
diMUSESetParam(kTalkSoundID, DIMUSE_P_VOLUME, 88);
}
_filesHandler->closeSound(kTalkSoundID);
} else if (_vm->_game.id == GID_CMI) {
fileDoesNotExist = _filesHandler->setCurrentSpeechFilename(soundName);
if (fileDoesNotExist)
return 1;
diMUSEStartStream(kTalkSoundID, 127, DIMUSE_BUFFER_SPEECH);
diMUSESetParam(kTalkSoundID, DIMUSE_P_GROUP, DIMUSE_GROUP_SPEECH);
// Let's not give the occasion to raise errors here
if (_vm->isValidActor(_vm->VAR(_vm->VAR_TALK_ACTOR))) {
Actor *a = _vm->derefActor(_vm->VAR(_vm->VAR_TALK_ACTOR), "IMuseDigital::startVoice");
if (_vm->VAR(_vm->VAR_VOICE_MODE) == 2)
diMUSESetParam(kTalkSoundID, DIMUSE_P_VOLUME, 0);
else
diMUSESetParam(kTalkSoundID, DIMUSE_P_VOLUME, a->_talkVolume);
diMUSESetParam(kTalkSoundID, DIMUSE_P_TRANSPOSE, a->_talkFrequency);
diMUSESetParam(kTalkSoundID, DIMUSE_P_PAN, a->_talkPan);
_currentSpeechVolume = a->_talkVolume;
_currentSpeechFrequency = a->_talkFrequency;
_currentSpeechPan = a->_talkPan;
}
// The interpreter really calls for processStreams two times in a row,
// and who am I to contradict it?
diMUSEProcessStreams();
diMUSEProcessStreams();
}
return 0;
}
// Used by FT and DIG demo
int IMuseDigital::startVoice(const char *fileName, ScummFile *file, uint32 offset, uint32 size) {
_filesHandler->setCurrentFtSpeechFile(fileName, file, offset, size);
diMUSEStopSound(kTalkSoundID);
diMUSEStartStream(kTalkSoundID, 127, DIMUSE_BUFFER_SPEECH);
diMUSESetParam(kTalkSoundID, DIMUSE_P_GROUP, DIMUSE_GROUP_SPEECH);
return 0;
}
static void skipLegacyTrackEntry(Common::Serializer &s) {
s.skip(1, VER(31)); // t.pan
s.skip(4, VER(31)); // t.vol
s.skip(4, VER(31)); // t.volFadeDest
s.skip(4, VER(31)); // t.volFadeStep
s.skip(4, VER(31)); // t.volFadeDelay
s.skip(1, VER(31)); // t.volFadeUsed
s.skip(4, VER(31)); // t.soundId
s.skip(15, VER(31)); // t.soundName
s.skip(1, VER(31)); // t.used
s.skip(1, VER(31)); // t.toBeRemoved
s.skip(1, VER(31)); // t.souStreamUsed
s.skip(1, VER(31), VER(76)); // mixerStreamRunning
s.skip(4, VER(31)); // t.soundPriority
s.skip(4, VER(31)); // t.regionOffset
s.skip(4, VER(31), VER(31)); // trackOffset
s.skip(4, VER(31)); // t.dataOffset
s.skip(4, VER(31)); // t.curRegion
s.skip(4, VER(31)); // t.curHookId
s.skip(4, VER(31)); // t.volGroupId
s.skip(4, VER(31)); // t.soundType
s.skip(4, VER(31)); // t.feedSize
s.skip(4, VER(31)); // t.dataMod12Bit
s.skip(4, VER(31)); // t.mixerFlags
s.skip(4, VER(31), VER(42)); // mixerVol
s.skip(4, VER(31), VER(42)); // mixerPan
s.skip(1, VER(45)); // t.sndDataExtComp
}
void IMuseDigital::saveLoadEarly(Common::Serializer &s) {
Common::StackLock lock(*_mutex, "IMuseDigital::saveLoadEarly()");
if (s.isLoading()) {
diMUSEStopAllSounds();
_filesHandler->closeSoundImmediatelyById(kTalkSoundID);
}
if (s.getVersion() < 103) {
// Just load the current state and sequence, and play them
debug(5, "IMuseDigital::saveLoadEarly(): old savegame detected (version %d), game may load with an undesired audio status", s.getVersion());
s.skip(4, VER(31), VER(42)); // _volVoice
s.skip(4, VER(31), VER(42)); // _volSfx
s.skip(4, VER(31), VER(42)); // _volMusic
s.syncAsSint32LE(_curMusicState, VER(31));
s.syncAsSint32LE(_curMusicSeq, VER(31));
s.syncAsSint32LE(_curMusicCue, VER(31));
s.syncAsSint32LE(_nextSeqToPlay, VER(31));
s.syncAsByte(_radioChatterSFX, VER(76));
s.syncArray(_attributes, 188, Common::Serializer::Sint32LE, VER(31));
for (int j = 0; j < 16; ++j)
skipLegacyTrackEntry(s);
int stateSoundId = 0;
int seqSoundId = 0;
if (_vm->_game.id == GID_DIG) {
stateSoundId = _digStateMusicTable[_curMusicState].soundId;
seqSoundId = _digSeqMusicTable[_curMusicSeq].soundId;
} else {
if (_vm->_game.features & GF_DEMO) {
stateSoundId = _comiDemoStateMusicTable[_curMusicState].soundId;
} else {
stateSoundId = _comiStateMusicTable[_curMusicState].soundId;
seqSoundId = _comiSeqMusicTable[_curMusicSeq].soundId;
}
}
_curMusicState = 0;
_curMusicSeq = 0;
scriptSetSequence(seqSoundId);
scriptSetState(stateSoundId);
scriptSetCuePoint(_curMusicCue);
_curMusicCue = 0;
} else {
diMUSESaveLoad(s);
if (s.isLoading() && _vm->isUsingOriginalGUI()) {
diMUSESetMusicGroupVol(diMUSEGetMusicGroupVol());
diMUSESetVoiceGroupVol(diMUSEGetVoiceGroupVol());
diMUSESetSFXGroupVol(diMUSEGetSFXGroupVol());
}
}
if (_vm->_game.id == GID_CMI && s.isLoading()) {
fillStreamsWhileMusicCritical(10);
}
}
void IMuseDigital::refreshScripts() {
if (isFTSoundEngine()) {
diMUSEProcessStreams();
} else if (!_vm->isSmushActive()) {
diMUSEProcessStreams();
diMUSERefreshScript();
}
}
void IMuseDigital::setRadioChatterSFX(bool state) {
_radioChatterSFX = state;
}
int IMuseDigital::startSfx(int soundId, int priority) {
diMUSEStartSound(soundId, priority);
diMUSESetParam(soundId, DIMUSE_P_GROUP, DIMUSE_GROUP_SFX);
return 0;
}
void IMuseDigital::callback() {
if (_cmdsPauseCount)
return;
if (!_callbackInterruptFlag) {
_callbackInterruptFlag = 1;
diMUSEHeartbeat();
_callbackInterruptFlag = 0;
}
}
void IMuseDigital::diMUSEHeartbeat() {
// This is what happens:
// - Usual audio stuff like fetching and playing sound (and everything
// within waveOutCallback()) happens at a base 50Hz rate;
// - Triggers and fades handling happens at a (somewhat hacky) 60Hz rate;
// - Music gain reduction happens at a 10Hz rate.
int soundId, foundGroupId, musicTargetVolume, musicEffVol, musicVol, tempVol, tempEffVol, factor, step;
waveOutCallback();
if (!_vm->isUsingOriginalGUI()) {
// Update volumes
if (_curMixerMusicVolume != _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType)) {
_curMixerMusicVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType);
diMUSESetMusicGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 2, 0, 127));
}
if (_curMixerSpeechVolume != _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType)) {
_curMixerSpeechVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType);
diMUSESetVoiceGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2, 0, 127));
}
if (_curMixerSFXVolume != _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType)) {
_curMixerSFXVolume = _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType);
diMUSESetSFXGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 2, 0, 127));
}
}
// Handle fades and triggers
_cmdsRunning60HzCount += _usecPerInt;
while (_cmdsRunning60HzCount >= DIMUSE_TIMER_FADES_RATE_USEC) {
_cmdsRunning60HzCount -= DIMUSE_TIMER_FADES_RATE_USEC;
_fadesHandler->loop();
_triggersHandler->loop();
}
_cmdsRunning10HzCount += _usecPerInt;
if (_cmdsRunning10HzCount < DIMUSE_TIMER_GAIN_RED_RATE_USEC)
return;
do {
// SPEECH GAIN REDUCTION 10Hz
_cmdsRunning10HzCount -= DIMUSE_TIMER_GAIN_RED_RATE_USEC;
soundId = 0;
musicTargetVolume = _groupsHandler->setGroupVol(DIMUSE_GROUP_MUSIC, -1);
while (1) { // Check all tracks to see if there's a speech file playing
soundId = waveGetNextSound(soundId);
if (!soundId)
break;
foundGroupId = -1;
if (_filesHandler->getNextSound(soundId) == 2) {
foundGroupId = waveGetParam(soundId, DIMUSE_P_GROUP); // Check the groupId of this sound
}
if (foundGroupId == DIMUSE_GROUP_SPEECH) {
// Remember: when a speech file stops playing this block stops
// being executed, so musicTargetVolume returns back to its original value
factor = _isEarlyDiMUSE ? 82 : 80;
musicTargetVolume = (musicTargetVolume * factor) / 128;
break;
}
}
musicEffVol = _groupsHandler->setGroupVol(DIMUSE_GROUP_MUSICEFF, -1); // MUSIC EFFECTIVE VOLUME GROUP (used for gain reduction)
musicVol = _groupsHandler->setGroupVol(DIMUSE_GROUP_MUSIC, -1); // MUSIC VOLUME SUBGROUP (keeps track of original music volume)
if (musicEffVol < musicTargetVolume) { // If there is gain reduction already going on...
tempEffVol = musicEffVol + 3;
if (tempEffVol < musicTargetVolume) {
if (musicVol <= tempEffVol) {
musicVol = tempEffVol;
}
} else if (musicVol <= musicTargetVolume) { // Bring up the music volume immediately when speech stops playing
musicVol = musicTargetVolume;
}
_groupsHandler->setGroupVol(DIMUSE_GROUP_MUSICEFF, musicVol);
} else if (musicEffVol > musicTargetVolume) {
// Bring music volume down to target volume with a -18 (or -6 for FT & DIG demo) step
// if there's speech playing or else, just cap it to the target if it's out of range
step = _isEarlyDiMUSE ? 6 : 18;
tempVol = musicEffVol - step;
if (tempVol <= musicTargetVolume) {
if (musicVol >= musicTargetVolume) {
musicVol = musicTargetVolume;
}
} else {
if (musicVol >= tempVol) {
musicVol = tempVol;
}
}
_groupsHandler->setGroupVol(DIMUSE_GROUP_MUSICEFF, musicVol);
}
} while (_cmdsRunning10HzCount >= DIMUSE_TIMER_GAIN_RED_RATE_USEC);
}
void IMuseDigital::setPriority(int soundId, int priority) {
diMUSESetParam(soundId, DIMUSE_P_PRIORITY, priority);
}
void IMuseDigital::setVolume(int soundId, int volume) {
diMUSESetParam(soundId, DIMUSE_P_VOLUME, volume);
if (soundId == kTalkSoundID)
_currentSpeechVolume = volume;
}
void IMuseDigital::setPan(int soundId, int pan) {
diMUSESetParam(soundId, DIMUSE_P_PAN, pan);
if (soundId == kTalkSoundID)
_currentSpeechPan = pan;
}
void IMuseDigital::setFrequency(int soundId, int frequency) {
diMUSESetParam(soundId, DIMUSE_P_TRANSPOSE, frequency);
if (soundId == kTalkSoundID)
_currentSpeechFrequency = frequency;
}
int IMuseDigital::getCurSpeechVolume() const {
return _currentSpeechVolume;
}
int IMuseDigital::getCurSpeechPan() const {
return _currentSpeechPan;
}
int IMuseDigital::getCurSpeechFrequency() const {
return _currentSpeechFrequency;
}
void IMuseDigital::flushTracks() {
_filesHandler->flushSounds();
}
// This is used in order to avoid crash everything
// if a compressed audio resource file is found
void IMuseDigital::disableEngine() {
_isEngineDisabled = true;
}
bool IMuseDigital::isEngineDisabled() {
return _isEngineDisabled;
}
void IMuseDigital::stopSMUSHAudio() {
if (!isFTSoundEngine()) {
if (_vm->_game.id == GID_DIG) {
int foundSoundId, paused;
int32 bufSize, criticalSize, freeSpace;
foundSoundId = diMUSEGetNextSound(0);
while (foundSoundId) {
if (diMUSEGetParam(foundSoundId, DIMUSE_P_SND_HAS_STREAM)) {
diMUSEQueryStream(foundSoundId, bufSize, criticalSize, freeSpace, paused);
// Here, the original engine for DIG explicitly asks for "bufSize == 193900";
// since this works half of the time in ScummVM (because of how we handle sound buffers),
// we do the check but we alternatively check for the SMUSH channel soundId, so to cover the
// remaining cases. This fixes instances in which exiting from a cutscene leaves both
// DiMUSE streams locked, with speech consequently unable to play and a "WARNING: three
// streams in use" message from streamerProcessStreams()
if (bufSize == 193900 || foundSoundId == DIMUSE_SMUSH_SOUNDID + DIMUSE_BUFFER_SFX)
diMUSEStopSound(foundSoundId);
}
foundSoundId = diMUSEGetNextSound(foundSoundId);
}
}
diMUSESetSequence(0);
}
}
bool IMuseDigital::isFTSoundEngine() {
return _isEarlyDiMUSE;
}
int32 IMuseDigital::getCurMusicPosInMs() {
int soundId, curSoundId;
curSoundId = 0;
soundId = 0;
while (1) {
curSoundId = diMUSEGetNextSound(curSoundId);
if (!curSoundId)
break;
if (diMUSEGetParam(curSoundId, DIMUSE_P_SND_HAS_STREAM) && diMUSEGetParam(curSoundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC) {
soundId = curSoundId;
return diMUSEGetParam(soundId, DIMUSE_P_SND_POS_IN_MS);
}
}
return diMUSEGetParam(soundId, DIMUSE_P_SND_POS_IN_MS);
}
int32 IMuseDigital::getCurVoiceLipSyncWidth() {
int32 width, height;
getSpeechLipSyncInfo(width, height);
return width;
}
int32 IMuseDigital::getCurVoiceLipSyncHeight() {
int32 width, height;
getSpeechLipSyncInfo(width, height);
return height;
}
int32 IMuseDigital::getCurMusicLipSyncWidth(int syncId) {
int32 width, height;
getMusicLipSyncInfo(syncId, width, height);
return width;
}
int32 IMuseDigital::getCurMusicLipSyncHeight(int syncId) {
int32 width, height;
getMusicLipSyncInfo(syncId, width, height);
return height;
}
void IMuseDigital::getSpeechLipSyncInfo(int32 &width, int32 &height) {
int curSpeechPosInMs;
width = 0;
height = 0;
if (diMUSEGetParam(kTalkSoundID, DIMUSE_P_SND_TRACK_NUM) > 0) {
curSpeechPosInMs = diMUSEGetParam(kTalkSoundID, DIMUSE_P_SND_POS_IN_MS);
diMUSELipSync(kTalkSoundID, 0, _vm->VAR(_vm->VAR_SYNC) + curSpeechPosInMs + 50, width, height);
}
}
void IMuseDigital::getMusicLipSyncInfo(int syncId, int32 &width, int32 &height) {
int soundId;
int speechSoundId;
int curSpeechPosInMs;
soundId = 0;
speechSoundId = 0;
width = 0;
height = 0;
while (1) {
soundId = diMUSEGetNextSound(soundId);
if (!soundId)
break;
if (diMUSEGetParam(soundId, DIMUSE_P_SND_HAS_STREAM)) {
if (diMUSEGetParam(soundId, DIMUSE_P_STREAM_BUFID) == DIMUSE_BUFFER_MUSIC) {
speechSoundId = soundId;
break;
}
}
}
if (speechSoundId) {
curSpeechPosInMs = diMUSEGetParam(speechSoundId, DIMUSE_P_SND_POS_IN_MS);
diMUSELipSync(speechSoundId, syncId, _vm->VAR(_vm->VAR_SYNC) + curSpeechPosInMs + 50, width, height);
}
}
int32 IMuseDigital::getSoundElapsedTimeInMs(int soundId) {
if (diMUSEGetParam(soundId, DIMUSE_P_SND_HAS_STREAM)) {
return diMUSEGetParam(soundId, DIMUSE_P_SND_POS_IN_MS);
}
return 0;
}
void IMuseDigital::pause(bool p) {
if (p) {
debug(5, "IMuseDigital::pause(): pausing...");
diMUSEPause();
} else {
debug(5, "IMuseDigital::pause(): resuming...");
diMUSEResume();
}
}
void IMuseDigital::setAudioNames(int32 num, char *names) {
free(_audioNames);
_numAudioNames = num;
_audioNames = names;
}
int IMuseDigital::getSoundIdByName(const char *soundName) {
if (soundName && soundName[0] != 0) {
for (int r = 0; r < _numAudioNames; r++) {
if (strcmp(soundName, &_audioNames[r * 9]) == 0) {
return r;
}
}
}
return 0;
}
void IMuseDigital::setSmushPlayer(SmushPlayer *splayer) {
_splayer = splayer;
// Perform a first-time volume update for both SMUSH and iMUSE
diMUSESetMusicGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 2, 0, 127));
diMUSESetVoiceGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2, 0, 127));
diMUSESetSFXGroupVol(CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 2, 0, 127));
}
void IMuseDigital::receiveAudioFromSMUSH(uint8 *srcBuf, int32 inFrameCount, int32 feedSize, int32 mixBufStartIndex, int volume, int pan, bool is11025Hz) {
_internalMixer->mix(srcBuf, inFrameCount, 8, 1, feedSize, mixBufStartIndex, volume, pan, is11025Hz);
}
void IMuseDigital::floodMusicBuffer() {
while (!isMusicStreamIdle()) {
diMUSEProcessStreams();
}
}
void IMuseDigital::fillStreamsWhileMusicCritical(int fillTimesAfter) {
if (!isFTSoundEngine()) {
while (isMusicCritical()) {
diMUSEProcessStreams();
}
}
for (int i = 0; i < fillTimesAfter; i++) {
diMUSEProcessStreams();
}
}
bool IMuseDigital::isMusicStreamIdle() {
int32 bufSize, criticalSize, freeSpace;
int paused;
IMuseDigiSndBuffer *bufInfo = _filesHandler->getBufInfo(DIMUSE_BUFFER_MUSIC);
if (!queryNextSoundFile(bufSize, criticalSize, freeSpace, paused))
return true;
return (paused > 0) || (bufSize - bufInfo->loadSize < freeSpace);
}
bool IMuseDigital::isMusicCritical() {
int32 bufSize, criticalSize, freeSpace;
int paused;
if (!queryNextSoundFile(bufSize, criticalSize, freeSpace, paused))
return false;
return (paused == 0) && freeSpace <= criticalSize;
}
bool IMuseDigital::queryNextSoundFile(int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused) {
int soundId;
if (isFTSoundEngine()) {
soundId = diMUSEQueryStream(0, bufSize, criticalSize, freeSpace, paused);
if (soundId) {
while (freeSpace >= criticalSize) {
soundId = diMUSEQueryStream(soundId, bufSize, criticalSize, freeSpace, paused);
if (!soundId)
return false;
}
return true;
}
} else {
soundId = diMUSEGetNextSound(0);
while (soundId) {
if (diMUSEGetParam(soundId, DIMUSE_P_SND_HAS_STREAM) &&
(diMUSEGetParam(soundId, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSIC || diMUSEGetParam(soundId, DIMUSE_P_GROUP) == DIMUSE_GROUP_MUSICEFF)) {
diMUSEQueryStream(soundId, bufSize, criticalSize, freeSpace, paused);
return true;
}
soundId = diMUSEGetNextSound(soundId);
}
}
return false;
}
int IMuseDigital::getSampleRate() {
return _internalSampleRate;
}
int IMuseDigital::getFeedSize() {
return _internalFeedSize;
}
void IMuseDigital::parseScriptCmds(int cmd, int soundId, int sub_cmd, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n, int o, int p) {
int b = soundId;
int c = sub_cmd;
int id;
int volume = b;
switch (cmd) {
case DIMUSE_C_KLUDGE_SET_STATE:
diMUSESetState(soundId);
break;
case DIMUSE_C_KLUDGE_SET_SEQUENCE:
diMUSESetSequence(soundId);
break;
case DIMUSE_C_KLUDGE_SET_CUE_POINT:
diMUSESetCuePoint(soundId);
break;
case DIMUSE_C_KLUDGE_SET_ATTRIBUTE:
diMUSESetAttribute(b, c);
break;
case DIMUSE_C_KLUDGE_SET_SFX_VOLUME:
if (!_vm->isUsingOriginalGUI()) {
volume = CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 2, 0, 127);
}
diMUSESetSFXGroupVol(volume);
break;
case DIMUSE_C_KLUDGE_SET_VOICE_VOLUME:
if (!_vm->isUsingOriginalGUI()) {
volume = CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2, 0, 127);
}
diMUSESetVoiceGroupVol(volume);
break;
case DIMUSE_C_KLUDGE_SET_MUSIC_VOLUME:
if (!_vm->isUsingOriginalGUI()) {
volume = CLIP(_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 2, 0, 127);
}
diMUSESetMusicGroupVol(volume);
break;
case DIMUSE_C_KLUDGE_STOP_ALL_SNDS:
case DIMUSE_C_KLUDGE_SET_PARAM:
case DIMUSE_C_KLUDGE_FADE_PARAM:
cmdsHandleCmd(cmd, nullptr, soundId, sub_cmd, d, e, f, g, h, i, j, k, l, m, n, o);
break;
case DIMUSE_C_KLUDGE_START_STREAM:
if (_vm->_game.id == GID_FT) {
id = getSoundIdByName("kstand");
_filesHandler->openSound(id);
} else if (_vm->_game.id == GID_DIG && _vm->_game.features & GF_DEMO) {
// Special opcode used in place of the first setState instruction
_filesHandler->openSound(soundId);
diMUSEStartStream(soundId, 126, DIMUSE_BUFFER_MUSIC);
}
break;
case DIMUSE_C_KLUDGE_SWITCH_STREAM:
if (_vm->_game.id == GID_DIG && _vm->_game.features & GF_DEMO) {
_filesHandler->openSound(c);
diMUSESwitchStream(soundId, c, _ftCrossfadeBuffer, sizeof(_ftCrossfadeBuffer), 0);
_filesHandler->closeSound(soundId);
}
break;
default:
debug("IMuseDigital::parseScriptCmds(): WARNING: unhandled command %d", cmd);
}
}
int IMuseDigital::diMUSETerminate() {
if (_scriptInitializedFlag) {
diMUSEStopAllSounds();
_filesHandler->closeAllSounds();
}
return 0;
}
int IMuseDigital::diMUSEInitialize() {
return cmdsHandleCmd(DIMUSE_C_INIT);
}
int IMuseDigital::diMUSEPause() {
return cmdsHandleCmd(DIMUSE_C_PAUSE);
}
int IMuseDigital::diMUSEResume() {
return cmdsHandleCmd(DIMUSE_C_RESUME);
}
void IMuseDigital::diMUSESaveLoad(Common::Serializer &ser) {
cmdsSaveLoad(ser);
}
int IMuseDigital::diMUSESetGroupVol(int groupId, int volume) {
return cmdsHandleCmd(DIMUSE_C_SET_GRP_VOL, nullptr, groupId, volume);
}
int IMuseDigital::diMUSEStartSound(int soundId, int priority) {
return cmdsHandleCmd(DIMUSE_C_START_SND, nullptr, soundId, priority);
}
int IMuseDigital::diMUSEStopSound(int soundId) {
debug(5, "IMuseDigital::diMUSEStopSound(): %d", soundId);
return cmdsHandleCmd(DIMUSE_C_STOP_SND, nullptr, soundId);
}
int IMuseDigital::diMUSEStopAllSounds() {
debug(5, "IMuseDigital::diMUSEStopAllSounds()");
return cmdsHandleCmd(DIMUSE_C_STOP_ALL_SNDS);
}
int IMuseDigital::diMUSEGetNextSound(int soundId) {
return cmdsHandleCmd(DIMUSE_C_GET_NEXT_SND, nullptr, soundId);
}
int IMuseDigital::diMUSESetParam(int soundId, int paramId, int value) {
return cmdsHandleCmd(DIMUSE_C_SET_PARAM, nullptr, soundId, paramId, value);
}
int IMuseDigital::diMUSEGetParam(int soundId, int paramId) {
return cmdsHandleCmd(DIMUSE_C_GET_PARAM, nullptr, soundId, paramId);
}
int IMuseDigital::diMUSEFadeParam(int soundId, int opcode, int destValue, int fadeLength) {
return cmdsHandleCmd(DIMUSE_C_FADE_PARAM, nullptr, soundId, opcode, destValue, fadeLength);
}
int IMuseDigital::diMUSESetHook(int soundId, int hookId) {
return cmdsHandleCmd(DIMUSE_C_SET_HOOK, nullptr, soundId, hookId);
}
int IMuseDigital::diMUSESetTrigger(int soundId, int marker, int opcode, int d, int e, int f, int g, int h, int i, int j, int k, int l, int m, int n) {
return cmdsHandleCmd(DIMUSE_C_SET_TRIGGER, nullptr, soundId, marker, opcode, d, e, f, g, h, i, j, k, l, m, n);
}
int IMuseDigital::diMUSEStartStream(int soundId, int priority, int bufferId) {
return cmdsHandleCmd(DIMUSE_C_START_STREAM, nullptr, soundId, priority, bufferId);
}
int IMuseDigital::diMUSESwitchStream(int oldSoundId, int newSoundId, int fadeDelay, int fadeSyncFlag2, int fadeSyncFlag1) {
return cmdsHandleCmd(DIMUSE_C_SWITCH_STREAM, nullptr, oldSoundId, newSoundId, fadeDelay, fadeSyncFlag2, fadeSyncFlag1);
}
// Variation for FT and DIG demo
int IMuseDigital::diMUSESwitchStream(int oldSoundId, int newSoundId, uint8 *crossfadeBuffer, int crossfadeBufferSize, int vocLoopFlag) {
return cmdsHandleCmd(DIMUSE_C_SWITCH_STREAM, crossfadeBuffer, oldSoundId, newSoundId, -1, crossfadeBufferSize, vocLoopFlag);
}
int IMuseDigital::diMUSEProcessStreams() {
return cmdsHandleCmd(DIMUSE_C_PROCESS_STREAMS);
}
int IMuseDigital::diMUSEQueryStream(int soundId, int32 &bufSize, int32 &criticalSize, int32 &freeSpace, int &paused) {
return waveQueryStream(soundId, bufSize, criticalSize, freeSpace, paused);
}
int IMuseDigital::diMUSEFeedStream(int soundId, uint8 *srcBuf, int32 sizeToFeed, int paused) {
return cmdsHandleCmd(DIMUSE_C_FEED_STREAM, srcBuf, soundId, -1, sizeToFeed, paused);
}
int IMuseDigital::diMUSELipSync(int soundId, int syncId, int msPos, int32 &width, int32 &height) {
return waveLipSync(soundId, syncId, msPos, width, height);
}
int IMuseDigital::diMUSEGetMusicGroupVol() {
if (_vm->isUsingOriginalGUI()) {
return diMUSESetGroupVol(DIMUSE_GROUP_MUSIC, -1);
}
return _mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 2;
}
int IMuseDigital::diMUSEGetSFXGroupVol() {
if (_vm->isUsingOriginalGUI()) {
return diMUSESetGroupVol(DIMUSE_GROUP_SFX, -1);
}
return _mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 2;
}
int IMuseDigital::diMUSEGetVoiceGroupVol() {
if (_vm->isUsingOriginalGUI()) {
return diMUSESetGroupVol(DIMUSE_GROUP_SPEECH, -1);
}
return _mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2;
}
int IMuseDigital::diMUSESetMusicGroupVol(int volume) {
debug(5, "IMuseDigital::diMUSESetMusicGroupVol(): %d", volume);
if (_isEarlyDiMUSE)
_splayer->setGroupVolume(GRP_BKGMUS, volume);
return diMUSESetGroupVol(DIMUSE_GROUP_MUSIC, volume);
}
int IMuseDigital::diMUSESetSFXGroupVol(int volume) {
debug(5, "IMuseDigital::diMUSESetSFXGroupVol(): %d", volume);
if (_isEarlyDiMUSE)
_splayer->setGroupVolume(GRP_SFX, volume);
return diMUSESetGroupVol(DIMUSE_GROUP_SFX, volume);
}
int IMuseDigital::diMUSESetVoiceGroupVol(int volume) {
debug(5, "IMuseDigital::diMUSESetVoiceGroupVol(): %d", volume);
if (_isEarlyDiMUSE)
_splayer->setGroupVolume(GRP_SPEECH, volume);
return diMUSESetGroupVol(DIMUSE_GROUP_SPEECH, volume);
}
void IMuseDigital::diMUSEUpdateGroupVolumes() {
waveUpdateGroupVolumes();
}
int IMuseDigital::diMUSEInitializeScript() {
return scriptParse(DIMUSE_C_SCRIPT_INIT, -1, -1);
}
void IMuseDigital::diMUSERefreshScript() {
scriptParse(DIMUSE_C_SCRIPT_REFRESH, -1, -1);
}
int IMuseDigital::diMUSESetState(int soundId) {
return scriptParse(DIMUSE_C_SCRIPT_SET_STATE, soundId, -1);
}
int IMuseDigital::diMUSESetSequence(int soundId) {
return scriptParse(DIMUSE_C_SCRIPT_SET_SEQUENCE, soundId, -1);
}
int IMuseDigital::diMUSESetCuePoint(int cueId) {
return scriptParse(DIMUSE_C_SCRIPT_CUE_POINT, cueId, -1);
}
int IMuseDigital::diMUSESetAttribute(int attrIndex, int attrVal) {
return scriptParse(DIMUSE_C_SCRIPT_SET_ATTRIBUTE, attrIndex, attrVal);
}
void IMuseDigital::diMUSEEnableSpooledMusic() {
_spooledMusicEnabled = true;
}
void IMuseDigital::diMUSEDisableSpooledMusic() {
_spooledMusicEnabled = true;
diMUSESetState(0);
diMUSESetSequence(0);
_spooledMusicEnabled = false;
}
// Debugger utility functions
void IMuseDigital::listStates() {
_vm->getDebugger()->debugPrintf("+---------------------------------+\n");
_vm->getDebugger()->debugPrintf("| stateId | name |\n");
_vm->getDebugger()->debugPrintf("+---------+-----------------------+\n");
if (_vm->_game.id == GID_CMI) {
if (_vm->_game.features & GF_DEMO) {
for (int i = 0; _comiDemoStateMusicTable[i].soundId != -1; i++) {
_vm->getDebugger()->debugPrintf("| %4d | %20s |\n", _comiDemoStateMusicTable[i].soundId, _comiDemoStateMusicTable[i].name);
}
} else {
for (int i = 0; _comiStateMusicTable[i].soundId != -1; i++) {
_vm->getDebugger()->debugPrintf("| %4d | %20s |\n", _comiStateMusicTable[i].soundId, _comiStateMusicTable[i].name);
}
}
} else if (_vm->_game.id == GID_DIG) {
for (int i = 0; _digStateMusicTable[i].soundId != -1; i++) {
_vm->getDebugger()->debugPrintf("| %4d | %20s |\n", _digStateMusicTable[i].soundId, _digStateMusicTable[i].name);
}
} else if (_vm->_game.id == GID_FT) {
for (int i = 0; _ftStateMusicTable[i].name[0]; i++) {
_vm->getDebugger()->debugPrintf("| %4d | %21s |\n", i, _ftStateMusicTable[i].name);
}
}
_vm->getDebugger()->debugPrintf("+---------+-----------------------+\n\n");
}
void IMuseDigital::listSeqs() {
_vm->getDebugger()->debugPrintf("+--------------------------------+\n");
_vm->getDebugger()->debugPrintf("| seqId | name |\n");
_vm->getDebugger()->debugPrintf("+---------+----------------------+\n");
if (_vm->_game.id == GID_CMI) {
for (int i = 0; _comiSeqMusicTable[i].soundId != -1; i++) {
_vm->getDebugger()->debugPrintf("| %4d | %20s |\n", _comiSeqMusicTable[i].soundId, _comiSeqMusicTable[i].name);
}
} else if (_vm->_game.id == GID_DIG) {
for (int i = 0; _digSeqMusicTable[i].soundId != -1; i++) {
_vm->getDebugger()->debugPrintf("| %4d | %20s |\n", _digSeqMusicTable[i].soundId, _digSeqMusicTable[i].name);
}
} else if (_vm->_game.id == GID_FT) {
for (int i = 0; _ftSeqNames[i].name[0]; i++) {
_vm->getDebugger()->debugPrintf("| %4d | %20s |\n", i, _ftSeqNames[i].name);
}
}
_vm->getDebugger()->debugPrintf("+---------+----------------------+\n\n");
}
void IMuseDigital::listCues() {
int curId = -1;
if (_curMusicSeq) {
_vm->getDebugger()->debugPrintf("Available cues for current sequence:\n");
_vm->getDebugger()->debugPrintf("+---------------------------------------+\n");
_vm->getDebugger()->debugPrintf("| cueName | transitionType | volume |\n");
_vm->getDebugger()->debugPrintf("+-------------+----------------+--------+\n");
for (int i = 0; i < 4; i++) {
curId = ((_curMusicSeq - 1) * 4) + i;
_vm->getDebugger()->debugPrintf("| %9s | %d | %3d |\n",
_ftSeqMusicTable[curId].audioName, (int)_ftSeqMusicTable[curId].transitionType, (int)_ftSeqMusicTable[curId].volume);
}
_vm->getDebugger()->debugPrintf("+-------------+----------------+--------+\n\n");
} else {
_vm->getDebugger()->debugPrintf("Current sequence is NULL, no cues available.\n\n");
}
}
void IMuseDigital::listTracks() {
_vm->getDebugger()->debugPrintf("Virtual audio tracks currently playing:\n");
_vm->getDebugger()->debugPrintf("+-------------------------------------------------------------------------+\n");
_vm->getDebugger()->debugPrintf("| # | soundId | group | hasStream | vol/effVol/pan | priority | jumpHook |\n");
_vm->getDebugger()->debugPrintf("+---+---------+-------+-----------+-----------------+----------+----------+\n");
for (int i = 0; i < _trackCount; i++) {
IMuseDigiTrack curTrack = _tracks[i];
if (curTrack.soundId != 0) {
_vm->getDebugger()->debugPrintf("| %1d | %5d | %d | %d | %3d/%3d/%3d | %3d | %3d |\n",
i, curTrack.soundId, curTrack.group, diMUSEGetParam(curTrack.soundId, DIMUSE_P_SND_HAS_STREAM),
curTrack.vol, curTrack.effVol, curTrack.pan, curTrack.priority, curTrack.jumpHook);
} else {
_vm->getDebugger()->debugPrintf("| %1d | --- | --- | --- | ---/---/--- | --- | --- |\n", i);
}
}
_vm->getDebugger()->debugPrintf("+---+---------+-------+-----------+-----------------+----------+----------+\n\n");
}
void IMuseDigital::listGroups() {
_vm->getDebugger()->debugPrintf("Volume groups:\n");
_vm->getDebugger()->debugPrintf("\tSFX: %3d\n", _groupsHandler->getGroupVol(DIMUSE_GROUP_SFX));
_vm->getDebugger()->debugPrintf("\tSPEECH: %3d\n", _groupsHandler->getGroupVol(DIMUSE_GROUP_SPEECH));
_vm->getDebugger()->debugPrintf("\tMUSIC: %3d\n", _groupsHandler->getGroupVol(DIMUSE_GROUP_MUSIC));
_vm->getDebugger()->debugPrintf("\tMUSICEFF: %3d\n\n", _groupsHandler->getGroupVol(DIMUSE_GROUP_MUSICEFF));
}
} // End of namespace Scumm