Files
2026-02-02 04:50:13 +01:00

715 lines
16 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 "lastexpress/sound/sound.h"
#include "lastexpress/lastexpress.h"
namespace LastExpress {
SoundManager::SoundManager(LastExpressEngine *engine) {
_engine = engine;
_mixer = _engine->_mixer;
for (int i = 0; i < ARRAYSIZE(_soundSlotChannels); i++) {
_soundSlotChannels[i] = nullptr;
}
memset(_soundChannelsMixBuffers, 0, sizeof(_soundChannelsMixBuffers));
soundDriverInit();
}
SoundManager::~SoundManager() {
_engine = nullptr;
}
int SoundManager::playSoundFile(const char *sndName, int typeFlags, int character, int delay) {
Slot *slot = new Slot(this, sndName, typeFlags, 30);
slot->setAssociatedCharacter(character);
if (delay) {
slot->setDelayedStartTime(getSoundDriver30HzCounter() + 2 * delay);
slot->addStatusFlag(kSoundFlagDelayedActivate);
} else {
Common::String subName = sndName;
subName.replace('.', '\0');
slot->setSub(subName.c_str());
slot->play();
}
return slot->getTag();
}
void SoundManager::startAmbient() {
_soundSlotAmbientFlag |= kAmbientSoundEnabled;
}
void SoundManager::startSteam(int cityIndex) {
Slot *cachedSlot;
bool playSoundNeeded = true;
_soundSlotAmbientFlag |= kAmbientSoundSteam;
if (_soundCache) {
cachedSlot = _soundCache;
do {
if (cachedSlot->hasTag(kSoundTagAmbient))
break;
cachedSlot = cachedSlot->getNext();
} while (cachedSlot);
if (cachedSlot)
playSoundNeeded = false;
}
if (playSoundNeeded)
playSoundFile("STEAM.SND", kSoundTypeAmbient | kSoundFlagLooped | kVolume7, kCharacterSteam, 0);
cachedSlot = _soundCache;
if (_soundCache) {
do {
if (cachedSlot->hasTag(kSoundTagAmbient))
break;
cachedSlot = cachedSlot->getNext();
} while (cachedSlot);
if (cachedSlot) {
cachedSlot = _soundCache;
if (_soundCache) {
do {
if (cachedSlot->hasTag(kSoundTagAmbient))
break;
cachedSlot = cachedSlot->getNext();
} while (cachedSlot);
if (cachedSlot) {
cachedSlot->setSub(_cities[cityIndex]);
}
}
}
}
}
void SoundManager::endAmbient() {
_soundSlotAmbientFlag = 0;
Slot *cachedSlot = _soundCache;
if (_soundCache) {
do {
if (cachedSlot->hasTag(kSoundTagAmbient))
break;
cachedSlot = cachedSlot->getNext();
} while (cachedSlot);
if (cachedSlot)
cachedSlot->setFade(0);
}
cachedSlot = _soundCache;
if (_soundCache) {
do {
if (cachedSlot->hasTag(kSoundTagOldAmbient))
break;
cachedSlot = cachedSlot->getNext();
} while (cachedSlot);
if (cachedSlot)
cachedSlot->setFade(0);
}
}
void SoundManager::killAmbient() {
_soundSlotAmbientFlag = 0;
Slot *cachedSlot = _soundCache;
if (_soundCache) {
do {
if (cachedSlot->hasTag(kSoundTagAmbient))
break;
cachedSlot = cachedSlot->getNext();
} while (cachedSlot);
if (cachedSlot) {
cachedSlot->addStatusFlag(kSoundFlagCloseRequested);
cachedSlot->setAssociatedCharacter(0);
cachedSlot->closeArchive();
}
}
cachedSlot = _soundCache;
if (_soundCache) {
do {
if (cachedSlot->hasTag(kSoundTagOldAmbient))
break;
cachedSlot = cachedSlot->getNext();
} while (cachedSlot);
if (cachedSlot) {
cachedSlot->addStatusFlag(kSoundFlagCloseRequested);
cachedSlot->setAssociatedCharacter(0);
cachedSlot->closeArchive();
}
}
}
void SoundManager::raiseAmbient(int level, int delay) {
if (level > 7) {
_soundAmbientFadeLevel = level;
_soundAmbientFadeTime = getSoundDriver30HzCounter() + 2 * delay;
}
}
void SoundManager::levelAmbient(int delay) {
_soundAmbientFadeLevel = 7;
_soundAmbientFadeTime = getSoundDriver30HzCounter() + 2 * delay;
}
Slot *SoundManager::findSlotWho(int32 character) {
Slot *result = _soundCache;
if (!_soundCache)
return nullptr;
while (result->getAssociatedCharacter() != character) {
result = result->getNext();
if (!result)
return nullptr;
}
return result;
}
Slot *SoundManager::findSlotName(char *name) {
Common::StackLock lock(*_engine->_soundMutex);
Slot *result = _soundCache;
if (!_soundCache)
return nullptr;
while (scumm_stricmp(name, result->getName2())) {
result = result->getNext();
if (!result)
return nullptr;
}
return result;
}
void SoundManager::ambientAI(int id) {
Slot *oldAmbientSlot1;
Slot *oldAmbientSlot2;
uint fileNameLen = 6;
int soundId = 1;
byte numLoops[9] = {0, 4, 2, 2, 2, 2, 2, 0, 0};
int positions[8] = {
8200, 7500, 6470, 5790,
4840, 4070, 3050, 2740
};
char newAmbientSoundName[80];
memset(newAmbientSoundName, 0, sizeof(newAmbientSoundName));
for (oldAmbientSlot1 = _soundCache; oldAmbientSlot1; oldAmbientSlot1 = oldAmbientSlot1->getNext()) {
if (oldAmbientSlot1->hasTag(kSoundTagAmbient))
break;
}
if ((_soundSlotAmbientFlag & kAmbientSoundEnabled) != 0 && (id == 69 || id == 70)) {
if ((_soundSlotAmbientFlag & kAmbientSoundSteam) != 0) {
Common::strcpy_s(newAmbientSoundName, "STEAM.SND");
_loopingSoundDuration = 0x7FFF;
} else {
if (getCharacter(kCharacterCath).characterPosition.location == kLocationOutsideTrain) {
soundId = 6;
} else if (_engine->getLogicManager()->inComp(kCharacterCath)) {
numLoops[0] = 0;
int objNum = (getCharacter(kCharacterCath).characterPosition.car == kCarGreenSleeping) ? 9 : 40;
for (int pos = 0; pos < ARRAYSIZE(positions); pos++) {
if (numLoops[0] == 1)
break;
if (_engine->getLogicManager()->inComp(kCharacterCath, getCharacter(kCharacterCath).characterPosition.car, positions[pos])) {
numLoops[0] = 1;
soundId = _engine->getLogicManager()->_doors[objNum].status == 2 ? 6 : 1;
}
objNum++;
}
} else {
switch (getCharacter(kCharacterCath).characterPosition.car) {
case kCarBaggageRear:
case kCarBaggage:
soundId = 4;
break;
case kCarKronos:
case kCarGreenSleeping:
case kCarRedSleeping:
case kCarRestaurant:
soundId = 1;
break;
case kCarCoalTender:
soundId = 5;
break;
case kCarLocomotive:
soundId = 99;
break;
case kCarVestibule:
soundId = 3;
break;
default:
soundId = 6;
break;
}
}
if (soundId != 99) {
char soundSection = (char)((rnd(UINT_MAX)) % numLoops[soundId]) + 'A';
Common::sprintf_s(newAmbientSoundName, "LOOP%d%c.SND", soundId, soundSection);
}
}
if (_scanAnySoundLoopingSection)
fileNameLen = 5;
if (!oldAmbientSlot1 || scumm_strnicmp(oldAmbientSlot1->_name2, newAmbientSoundName, fileNameLen)) {
_loopingSoundDuration = ((rnd(UINT_MAX)) % 320) + 260;
if (soundId != 99) {
if (_engine->isDemo()) {
playSoundFile(newAmbientSoundName, kSoundTypeAmbient | kSoundFlagLooped | kVolume2, kCharacterSteam, 0);
} else {
playSoundFile(newAmbientSoundName, kSoundTypeAmbient | kSoundFlagLooped | kVolume1, kCharacterSteam, 0);
}
if (oldAmbientSlot1)
oldAmbientSlot1->setFade(kVolumeNone);
oldAmbientSlot2 = _soundCache;
if (_soundCache) {
do {
if (oldAmbientSlot2->hasTag(kSoundTagAmbient))
break;
oldAmbientSlot2 = oldAmbientSlot2->getNext();
} while (oldAmbientSlot2);
if (oldAmbientSlot2)
oldAmbientSlot2->setFade(kVolume7);
}
}
}
}
}
void SoundManager::soundThread() {
int priority;
int maxPriority = 0;
bool loopedPlaying = false;
Slot *ambientSlot1;
Slot *ambientSlot2;
Slot *slotToDevirtualize;
Slot *cachedSlot;
Slot *next;
if (!isCopyingDataToSoundDriver()) {
ambientSlot1 = _soundCache;
_inSoundThreadFunction++;
if (_soundCache) {
do {
if (ambientSlot1->hasTag(kSoundTagAmbient))
break;
ambientSlot1 = ambientSlot1->getNext();
} while (ambientSlot1);
}
if ((_soundSlotAmbientFlag & kAmbientSoundEnabled) != 0) {
ambientSlot2 = _soundCache;
if (!_soundCache) {
loopedPlaying = true;
} else {
do {
if (ambientSlot2->hasTag(kSoundTagAmbient))
break;
ambientSlot2 = ambientSlot2->getNext();
} while (ambientSlot2);
if (!ambientSlot2 || _scanAnySoundLoopingSection || (ambientSlot1 && ambientSlot1->getTime() > _loopingSoundDuration))
loopedPlaying = true;
}
if (loopedPlaying) {
ambientAI(kAmbientLooping);
} else if (_soundAmbientFadeTime && getSoundDriver30HzCounter() >= _soundAmbientFadeTime) {
assert(ambientSlot1);
ambientSlot1->setFade(_soundAmbientFadeLevel);
_soundAmbientFadeTime = 0;
}
}
slotToDevirtualize = nullptr;
cachedSlot = _soundCache;
if (_soundCache) {
do {
next = cachedSlot->getNext();
if ((cachedSlot->getStatusFlags() & kSoundFlagMuteProcessed) != 0) {
if (cachedSlot->getSoundBuffer())
cachedSlot->releaseBuffer();
if (cachedSlot->closeArchive()) {
cachedSlot->setNumLoadedBytes(3);
}
if (_numActiveChannels < 6 && (cachedSlot->getStatusFlags() & kSoundVolumeMask) != 0) {
priority = cachedSlot->getPriority();
if (priority + (int)(cachedSlot->getStatusFlags() & kSoundVolumeMask) > maxPriority) {
slotToDevirtualize = cachedSlot;
maxPriority = (cachedSlot->getStatusFlags() & kSoundVolumeMask) + priority;
}
}
}
if (!cachedSlot->update() && (cachedSlot->getStatusFlags() & kSoundFlagKeepAfterFinish) == 0) {
if (slotToDevirtualize == cachedSlot) {
maxPriority = 0;
slotToDevirtualize = nullptr;
}
if (cachedSlot) {
if (cachedSlot == _engine->getNISManager()->getChainedSound()) {
// The original deleted the cachedSlot and probably set
// all its values to zero, which might not be the case on
// modern compilers and might instead trigger an exception
// on the NIS code...
_engine->getNISManager()->setChainedSound(nullptr);
}
delete cachedSlot;
cachedSlot = nullptr;
}
}
cachedSlot = next;
} while (next);
}
if (slotToDevirtualize)
slotToDevirtualize->devirtualize();
_scanAnySoundLoopingSection = false;
_inSoundThreadFunction--;
}
}
void SoundManager::killAllSlots() {
for (Slot *i = _soundCache; i; i = i->getNext())
i->addStatusFlag(kSoundFlagCloseRequested);
}
void SoundManager::killAllExcept(int tag1, int tag2, int tag3, int tag4, int tag5, int tag6, int tag7) {
Common::StackLock lock(*_engine->_soundMutex);
Slot *slot = _soundCache;
if (!tag2)
tag2 = tag1;
if (!tag3)
tag3 = tag1;
if (!tag4)
tag4 = tag1;
if (!tag5)
tag5 = tag1;
if (!tag6)
tag6 = tag1;
if (!tag7)
tag7 = tag1;
if (_soundCache) {
do {
int tag = slot->getTag();
if (tag1 != tag && tag2 != tag && tag3 != tag && tag4 != tag && tag5 != tag && tag6 != tag && tag7 != tag) {
slot->addStatusFlag(kSoundFlagCloseRequested);
slot->setAssociatedCharacter(0);
slot->closeArchive();
}
slot = slot->getNext();
} while (slot);
}
}
void SoundManager::saveSoundInfo(CVCRFile *file) {
Common::StackLock lock(*_engine->_soundMutex);
SaveSlot *saveSlot = new SaveSlot();
int numSounds = 0;
file->writeRLE(&_soundSlotAmbientFlag, 4, 1);
file->writeRLE(&_curSoundSlotTag, 4, 1);
for (Slot *i = _soundCache; i; i = i->_next) {
if (scumm_stricmp("NISSND?", i->_name2) && (i->_statusFlags & kSoundTypeMask) != kSoundTypeMenu)
numSounds++;
}
file->writeRLE(&numSounds, 4, 1);
for (Slot *j = _soundCache; j; j = j->_next) {
if (scumm_stricmp("NISSND?", j->_name2) && (j->_statusFlags & kSoundTypeMask) != kSoundTypeMenu) {
saveSlot->tag = j->_tag;
saveSlot->blockCount = j->_blockCount;
saveSlot->status = j->_statusFlags;
saveSlot->time = j->_time;
saveSlot->fadeDelayCounter = j->_fadeDelayCounter;
saveSlot->unusedVar = j->_unusedVar;
saveSlot->character = j->_character;
saveSlot->delayTicks = j->_delayedStartTime - _sound30HzCounter;
if (saveSlot->delayTicks > 0x8000000)
saveSlot->delayTicks = 0;
saveSlot->priority = j->_priority;
strncpy(saveSlot->name1, j->_name1, sizeof(saveSlot->name1));
strncpy(saveSlot->name2, j->_name2, sizeof(saveSlot->name2));
// I have verified that ANY possible name in here won't be longer than 12 characters,
// so it's safe to put this here, like Coverity asked to...
saveSlot->name1[15] = '\0';
saveSlot->name2[15] = '\0';
file->writeRLE(saveSlot, sizeof(SaveSlot), 1);
}
}
delete saveSlot;
}
void SoundManager::destroyAllSound() {
Slot *i;
Slot *next;
int32 waitCycles = 0;
addSoundDriverFlags(kSoundDriverClearBufferRequested);
// Wait for the driver to clear the mix buffer
for (i = _soundCache; (getSoundDriverFlags() & kSoundDriverClearBufferProcessed) == 0; waitCycles++) {
if (waitCycles >= 3000000)
break;
}
addSoundDriverFlags(kSoundDriverClearBufferProcessed);
if (_soundCache) {
do {
next = i->getNext();
i->setAssociatedCharacter(0);
if (i->getSoundBuffer())
i->releaseBuffer();
if (i) {
delete i;
i = nullptr;
}
i = next;
} while (next);
}
_engine->getSubtitleManager()->subThread();
}
void SoundManager::loadSoundInfo(CVCRFile *file, bool skipSoundLoading) {
Common::StackLock lock(*_engine->_soundMutex);
int numSounds;
SaveSlot *saveSlot = new SaveSlot();
if (skipSoundLoading) {
int skippedValue;
file->readRLE(&skippedValue, 4, 1);
file->readRLE(&skippedValue, 4, 1);
file->readRLE(&numSounds, 4, 1);
for (int j = 0; j < numSounds; j++) {
file->readRLE(saveSlot, sizeof(SaveSlot), 1);
}
} else {
file->readRLE(&_soundSlotAmbientFlag, 4, 1);
file->readRLE(&_curSoundSlotTag, 4, 1);
file->readRLE(&numSounds, 4, 1);
for (int j = 0; j < numSounds; j++) {
file->readRLE(saveSlot, sizeof(SaveSlot), 1);
// This apparently useless instruction automatically adds the saveSlot pointer to the cache
Slot *tmp = new Slot(this, saveSlot);
assert(tmp);
}
for (Slot *i = _soundCache; i; i = i->_next) {
if ((i->_statusFlags & kSoundFlagHasLinkAfter) != 0) {
Slot *cachedSlot = _soundCache;
if (_soundCache) {
while (scumm_stricmp(cachedSlot->_name2, i->_name1)) {
cachedSlot = cachedSlot->_next;
if (!cachedSlot)
break;
}
if (cachedSlot)
i->_chainedSound = cachedSlot;
}
}
}
_soundDriverFlags &= ~(kSoundDriverClearBufferRequested | kSoundDriverClearBufferProcessed);
}
delete saveSlot;
}
void SoundManager::addSlot(Slot *entry) {
Slot *cachedSlot = _soundCache;
if (_soundCache) {
if (_soundCache->getNext()) {
do {
cachedSlot = cachedSlot->getNext();
} while (cachedSlot->getNext());
}
cachedSlot->setNext(entry);
_soundCacheCount++;
} else {
_soundCacheCount++;
_soundCache = entry;
}
}
void SoundManager::removeSlot(Slot *entry) {
Slot *cachedSlot;
Slot *next;
cachedSlot = _soundCache;
if (_soundCache && entry) {
if (_soundCache == entry) {
_soundCache = _soundCache->getNext();
_soundCacheCount--;
} else {
if (_soundCache->getNext() != entry) {
do {
next = cachedSlot->getNext();
if (!next)
break;
cachedSlot = cachedSlot->getNext();
} while (next->getNext() != entry);
}
if (cachedSlot->getNext()) {
cachedSlot->setNext(cachedSlot->getNext()->getNext());
_soundCacheCount--;
}
}
}
}
void SoundManager::NISFadeOut() {
for (Slot *i = _soundCache; i; i = i->getNext()) {
i->assignDirectVolume(i->getStatusFlags() & kSoundVolumeMask);
if (i->getVolume())
i->setFade((i->getVolume() >> 1) + 1);
}
addSoundDriverFlags(kSoundDriverNISHasRequestedFade);
}
void SoundManager::NISFadeIn() {
removeSoundDriverFlags(kSoundDriverNISHasRequestedFade);
for (Slot *i = _soundCache; i; i = i->getNext()) {
if (i->getVolume()) {
if (!i->hasTag(kSoundTagNIS) && !i->hasTag(kSoundTagLink))
i->setFade(i->getVolume());
}
}
}
int SoundManager::getMasterVolume() {
int result = soundDriverGetVolume();
if (result < 0)
return 0;
if (result > 7)
return 7;
return result;
}
void SoundManager::setMasterVolume(int volume) {
int effVolume = volume;
if (volume < 0) {
effVolume = 0;
}
if (volume > 7)
effVolume = 7;
soundDriverSetVolume(effVolume);
}
} // End of namespace LastExpress