/* 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 . * */ #include "gui/error.h" #include "common/stream.h" #include "common/mutex.h" #include "common/timer.h" #include "common/translation.h" #include "audio/audiostream.h" #include "audio/decoders/raw.h" #include "audio/mixer.h" #include "engines/grim/debug.h" #include "engines/grim/sound.h" #include "engines/grim/grim.h" #include "engines/grim/resource.h" #include "engines/grim/savegame.h" #include "engines/grim/textsplit.h" #include "engines/grim/emi/sound/emisound.h" #include "engines/grim/emi/sound/track.h" #include "engines/grim/emi/sound/aifftrack.h" #include "engines/grim/emi/sound/mp3track.h" #include "engines/grim/emi/sound/scxtrack.h" #include "engines/grim/emi/sound/vimatrack.h" #include "engines/grim/movie/codecs/vima.h" namespace Grim { EMISound *g_emiSound = nullptr; extern uint16 imuseDestTable[]; struct PlainMusicEntry { int sync; const char * type; const char * filename; }; static PlainMusicEntry emiPS2MusicTable[126] = { { 0, "", "" }, { 1, "state", "1115.scx" }, { 2, "state", "1170.scx" }, { 2, "state", "1170.scx" }, { 2, "state", "1170.scx" }, { 3, "state", "1165.scx" }, { 4, "state", "1145.scx" }, { 4, "state", "1145.scx" }, { 1, "state", "1115.scx" }, { 1, "state", "1115.scx" }, { 0, "episode", "7200.scx" }, { 0, "episode", "1210.scx" }, { 0, "state", "1180.scx" }, { 0, "state", "1110.scx" }, { 1, "state", "1115.scx" }, { 0, "state", "1105.scx" }, { 4, "state", "1145.scx" }, { 0, "state", "1150.scx" }, { 0, "state", "1100.scx" }, { 5, "state", "1120.scx" }, { 5, "state", "1120.scx" }, { 5, "state", "1120.scx" }, { 3, "state", "1165.scx" }, { 0, "state", "1155.scx" }, { 0, "state", "1160.scx" }, { 0, "state", "1140.scx" }, { 0, "state", "1140.scx" }, { 2, "state", "1170.scx" }, { 2, "state", "1175.scx" }, { 0, "episode", "1205.scx" }, { 0, "state", "1000.scx" }, { 0, "state", "1185.scx" }, { 0, "state", "2127.scx" }, { 0, "state", "2119.scx" }, { 0, "episode", "2208.scx" }, { 0, "state", "2195.scx" }, { 0, "state", "2190.scx" }, { 0, "state", "2185.scx" }, { 1, "state", "2175.scx" }, { 0, "state", "2170.scx" }, { 0, "state", "2165.scx" }, { 0, "state", "2160.scx" }, { 0, "state", "2155.scx" }, { 0, "state", "2120.scx" }, { 0, "state", "2150.scx" }, { 0, "state", "2145.scx" }, { 2, "state", "2105.scx" }, { 0, "state", "2115.scx" }, { 0, "state", "2125.scx" }, { 0, "state", "2130.scx" }, { 0, "state", "2100.scx" }, { 0, "state", "2140.scx" }, { 0, "episode", "2200.scx" }, { 0, "state", "2116.scx" }, { 0, "episode", "2207.scx" }, { 0, "state", "2107.scx" }, { 0, "episode", "2215.scx" }, { 0, "episode", "2220.scx" }, { 0, "episode", "2225.scx" }, { 0, "episode", "2210.scx" }, { 0, "state", "2135.scx" }, { 2, "state", "2105.scx" }, { 0, "state", "2108.scx" }, { 0, "state", "2117.scx" }, { 0, "state", "2118.scx" }, { 1, "state", "2175.scx" }, { 0, "state", "4120.scx" }, { 1, "state", "3100.scx" }, { 0, "state", "4115.scx" }, { 2, "state", "4100.scx" }, { 0, "state", "3150.scx" }, { 0, "state", "3145.scx" }, { 0, "state", "4110.scx" }, { 0, "state", "3140.scx" }, { 3, "state", "3135.scx" }, { 3, "state", "3120.scx" }, { 4, "state", "3130.scx" }, { 4, "state", "3115.scx" }, { 1, "state", "3100.scx" }, { 5, "state", "3125.scx" }, { 5, "state", "3110.scx" }, { 6, "state", "3105.scx" }, { 0, "episode", "3210.scx" }, { 0, "episode", "3200.scx" }, { 0, "episode", "3205.scx" }, { 0, "state", "3147.scx" }, { 0, "episode", "4215.scx" }, { 0, "state", "4105.scx" }, { 6, "state", "3106.scx" }, { 6, "state", "3107.scx" }, { 2, "state", "4100.scx" }, { 1, "state", "5145.scx" }, { 2, "state", "5140.scx" }, { 2, "state", "5140.scx" }, { 3, "state", "5135.scx" }, { 3, "state", "5135.scx" }, { 3, "state", "5135.scx" }, { 0, "state", "5170.scx" }, { 0, "episode", "5205.scx" }, { 0, "state", "5120.scx" }, { 0, "episode", "5215.scx" }, { 0, "episode", "5230.scx" }, { 0, "episode", "5225.scx" }, { 0, "state", "5117.scx" }, { 0, "state", "5115.scx" }, { 0, "episode", "5220.scx" }, { 0, "state", "6105.scx" }, { 0, "state", "6100.scx" }, { 0, "state", "5165.scx" }, { 0, "state", "5160.scx" }, { 0, "episode", "5200.scx" }, { 2, "state", "5140.scx" }, { 3, "state", "5135.scx" }, { 0, "state", "5155.scx" }, { 0, "state", "5150.scx" }, { 0, "state", "5130.scx" }, { 0, "state", "5125.scx" }, { 0, "state", "5110.scx" }, { 1, "state", "5105.scx" }, { 0, "state", "5100.scx" }, { 0, "state", "6110.scx" }, { 0, "state", "5106.scx" }, { 0, "episode", "7210.scx" }, { 0, "episode", "1200.scx" }, { 0, "state", "1195.scx" }, { 0, "episode", "1215.scx" } }; void EMISound::timerHandler(void *refCon) { EMISound *emiSound = (EMISound *)refCon; emiSound->callback(); } EMISound::EMISound(int fps) { _curMusicState = -1; _numMusicStates = 0; _musicTrack = nullptr; _curTrackId = 0; _callbackFps = fps; vimaInit(imuseDestTable); initMusicTable(); g_system->getTimerManager()->installTimerProc(timerHandler, 1000000 / _callbackFps, this, "emiSoundCallback"); } EMISound::~EMISound() { g_system->getTimerManager()->removeTimerProc(timerHandler); freePlayingSounds(); freeLoadedSounds(); delete _musicTrack; delete[] _musicTable; } EMISound::TrackList::iterator EMISound::getPlayingTrackByName(const Common::String &name) { for (TrackList::iterator it = _playingTracks.begin(); it != _playingTracks.end(); ++it) { if ((*it)->getSoundName() == name) { return it; } } return _playingTracks.end(); } void EMISound::freePlayingSounds() { for (TrackList::iterator it = _playingTracks.begin(); it != _playingTracks.end(); ++it) { delete (*it); } _playingTracks.clear(); } void EMISound::freeLoadedSounds() { for (TrackMap::iterator it = _preloadedTrackMap.begin(); it != _preloadedTrackMap.end(); ++it) { delete it->_value; } _preloadedTrackMap.clear(); } bool EMISound::startVoice(const Common::String &soundName, int volume, int pan) { return startSound(soundName, Audio::Mixer::kSpeechSoundType, volume, pan); } bool EMISound::startSfx(const Common::String &soundName, int volume, int pan) { return startSound(soundName, Audio::Mixer::kSFXSoundType, volume, pan); } bool EMISound::startSfxFrom(const Common::String &soundName, const Math::Vector3d &pos, int volume) { return startSoundFrom(soundName, Audio::Mixer::kSFXSoundType, pos, volume); } bool EMISound::startSound(const Common::String &soundName, Audio::Mixer::SoundType soundType, int volume, int pan) { Common::StackLock lock(_mutex); SoundTrack *track = initTrack(soundName, soundType); if (track) { track->setBalance(pan * 2 - 127); track->setVolume(volume); track->play(); _playingTracks.push_back(track); return true; } return false; } bool EMISound::startSoundFrom(const Common::String &soundName, Audio::Mixer::SoundType soundType, const Math::Vector3d &pos, int volume) { Common::StackLock lock(_mutex); SoundTrack *track = initTrack(soundName, soundType); if (track) { track->setVolume(volume); track->setPosition(true, pos); track->play(); _playingTracks.push_back(track); return true; } return false; } bool EMISound::getSoundStatus(const Common::String &soundName) { TrackList::iterator it = getPlayingTrackByName(soundName); if (it == _playingTracks.end()) // We have no such sound. return false; return (*it)->isPlaying(); } void EMISound::stopSound(const Common::String &soundName) { Common::StackLock lock(_mutex); TrackList::iterator it = getPlayingTrackByName(soundName); if (it == _playingTracks.end()) { warning("Sound track '%s' could not be found to stop", soundName.c_str()); } else { delete (*it); _playingTracks.erase(it); } } int32 EMISound::getPosIn16msTicks(const Common::String &soundName) { TrackList::iterator it = getPlayingTrackByName(soundName); if (it == _playingTracks.end()) { warning("Sound track '%s' could not be found to get ticks", soundName.c_str()); return 0; } else { return (*it)->getPos().msecs() / 16; } } void EMISound::setVolume(const Common::String &soundName, int volume) { Common::StackLock lock(_mutex); TrackList::iterator it = getPlayingTrackByName(soundName); if (it == _playingTracks.end()) { warning("Sound track '%s' could not be found to set volume", soundName.c_str()); } else { (*it)->setVolume(volume); } } void EMISound::setPan(const Common::String &soundName, int pan) { Common::StackLock lock(_mutex); TrackList::iterator it = getPlayingTrackByName(soundName); if (it == _playingTracks.end()) { warning("Sound track '%s' could not be found to set pan", soundName.c_str()); } else { (*it)->setBalance(pan * 2 - 127); } } bool EMISound::loadSfx(const Common::String &soundName, int &id) { Common::StackLock lock(_mutex); SoundTrack *track = initTrack(soundName, Audio::Mixer::kSFXSoundType); if (track) { id = _curTrackId++; _preloadedTrackMap[id] = track; return true; } else { return false; } } void EMISound::playLoadedSound(int id, bool looping) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { it->_value->setLooping(looping); it->_value->setPosition(false); it->_value->play(); } else { warning("EMISound::playLoadedSound called with invalid sound id"); } } void EMISound::playLoadedSoundFrom(int id, const Math::Vector3d &pos, bool looping) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { it->_value->setLooping(looping); it->_value->setPosition(true, pos); it->_value->play(); } else { warning("EMISound::playLoadedSoundFrom called with invalid sound id"); } } void EMISound::setLoadedSoundLooping(int id, bool looping) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { it->_value->setLooping(looping); } else { warning("EMISound::setLoadedSoundLooping called with invalid sound id"); } } void EMISound::stopLoadedSound(int id) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { it->_value->stop(); } else { warning("EMISound::stopLoadedSound called with invalid sound id"); } } void EMISound::freeLoadedSound(int id) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { delete it->_value; _preloadedTrackMap.erase(it); } else { warning("EMISound::freeLoadedSound called with invalid sound id"); } } void EMISound::setLoadedSoundVolume(int id, int volume) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { it->_value->setVolume(volume); } else { warning("EMISound::setLoadedSoundVolume called with invalid sound id"); } } void EMISound::setLoadedSoundPan(int id, int pan) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { it->_value->setBalance(pan * 2 - 127); } else { warning("EMISound::setLoadedSoundPan called with invalid sound id"); } } void EMISound::setLoadedSoundPosition(int id, const Math::Vector3d &pos) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { it->_value->setPosition(true, pos); } else { warning("EMISound::setLoadedSoundPosition called with invalid sound id"); } } bool EMISound::getLoadedSoundStatus(int id) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { return it->_value->isPlaying(); } warning("EMISound::getLoadedSoundStatus called with invalid sound id"); return false; } int EMISound::getLoadedSoundVolume(int id) { Common::StackLock lock(_mutex); TrackMap::iterator it = _preloadedTrackMap.find(id); if (it != _preloadedTrackMap.end()) { return it->_value->getVolume(); } warning("EMISound::getLoadedSoundVolume called with invalid sound id"); return false; } SoundTrack *EMISound::initTrack(const Common::String &soundName, Audio::Mixer::SoundType soundType, const Audio::Timestamp *start) const { SoundTrack *track; Common::String soundNameLower(soundName); soundNameLower.toLowercase(); if (soundNameLower.hasSuffix(".scx")) { track = new SCXTrack(soundType); } else if (soundNameLower.hasSuffix(".m4b") || soundNameLower.hasSuffix(".lab")) { track = new MP3Track(soundType); } else if (soundNameLower.hasSuffix(".aif")) { track = new AIFFTrack(soundType); } else { track = new VimaTrack(); } Common::String filename; if (soundType == Audio::Mixer::kMusicSoundType) { filename = _musicPrefix + soundName; } else { filename = soundName; } if (track->openSound(filename, soundName, start)) { return track; } return nullptr; } bool EMISound::stateHasLooped(int stateId) { if (stateId == _curMusicState) { if (_curMusicState != 0 && _musicTrack) { return _musicTrack->hasLooped(); } } else { warning("EMISound::stateHasLooped called for a different music state than the current one"); } return false; } bool EMISound::stateHasEnded(int stateId) { if (stateId == _curMusicState) { if (_curMusicState != 0 && _musicTrack) { return !_musicTrack->isPlaying(); } } return true; } void EMISound::setMusicState(int stateId) { Common::StackLock lock(_mutex); // The demo calls ImSetState with state id 1000, which exceeds the number of states in the // music table. if (stateId >= _numMusicStates) stateId = 0; if (stateId == _curMusicState) return; if (_musicTable == nullptr) { Debug::debug(Debug::Sound, "No music table loaded"); return; } Common::String soundName = _musicTable[stateId]._filename; int sync = _musicTable[stateId]._sync; Audio::Timestamp musicPos; int prevSync = -1; if (_musicTrack) { if (_musicTrack->isPlaying()) { musicPos = _musicTrack->getPos(); prevSync = _musicTrack->getSync(); if (sync == prevSync && soundName == _musicTrack->getSoundName()) { // If the previous music track is the same track as the new one, we'll just // keep playing the previous track. This happens in the PS2 version where they // removed some of the music variations, but kept the states associated with // those. _curMusicState = stateId; return; } _musicTrack->fadeOut(); _playingTracks.push_back(_musicTrack); _musicTrack = nullptr; } } bool fadeMusicIn = false; for (TrackList::iterator it = _playingTracks.begin(); it != _playingTracks.end(); ++it) { if ((*it)->isPlaying() && (*it)->getSoundType() == Audio::Mixer::kMusicSoundType) { fadeMusicIn = true; break; } } if (!fadeMusicIn) { for (uint i = 0; i < _stateStack.size(); ++i) { if (_stateStack[i]._track && _stateStack[i]._track->isPlaying() && !_stateStack[i]._track->isPaused()) { fadeMusicIn = true; break; } } } if (stateId == 0) { _curMusicState = 0; return; } if (_musicTable[stateId]._id != stateId) { Debug::debug(Debug::Sound, "Attempted to play track #%d, not found in music table!", stateId); return; } _curMusicState = stateId; Audio::Timestamp *start = nullptr; if (prevSync != 0 && sync != 0 && prevSync == sync) start = &musicPos; Debug::debug(Debug::Sound, "Loading music: %s", soundName.c_str()); SoundTrack *music = initTrack(soundName, Audio::Mixer::kMusicSoundType, start); if (music) { music->play(); music->setSync(sync); if (fadeMusicIn) { music->setFade(0.0f); music->fadeIn(); } _musicTrack = music; } } uint32 EMISound::getMsPos(int stateId) { if (!_musicTrack) { Debug::debug(Debug::Sound, "EMISound::getMsPos: Music track is null", stateId); return 0; } return _musicTrack->getPos().msecs(); } MusicEntry *EMISound::initMusicTableDemo(const Common::String &filename) { Common::SeekableReadStream *data = g_resourceloader->openNewStreamFile(filename); if (!data) error("Couldn't open %s", filename.c_str()); // FIXME, for now we use a fixed-size table, as I haven't looked at the retail-data yet. _numMusicStates = 15; MusicEntry *musicTable = new MusicEntry[_numMusicStates]; for (int i = 0; i < 15; ++i) { musicTable->_x = 0; musicTable->_y = 0; musicTable->_sync = 0; musicTable->_trim = 0; musicTable->_id = i; } TextSplitter *ts = new TextSplitter(filename, data); int id, x, y, sync; char musicfilename[64]; char name[64]; while (!ts->isEof()) { while (!ts->checkString("*/")) { while (!ts->checkString(".cuebutton")) ts->nextLine(); ts->scanString(".cuebutton id %d x %d y %d sync %d \"%[^\"]64s", 5, &id, &x, &y, &sync, name); ts->scanString(".playfile \"%[^\"]64s", 1, musicfilename); musicTable[id]._id = id; musicTable[id]._x = x; musicTable[id]._y = y; musicTable[id]._sync = sync; musicTable[id]._name = name; musicTable[id]._filename = musicfilename; } ts->nextLine(); } delete ts; delete data; return musicTable; } void EMISound::initMusicTableRetail(MusicEntry *musicTable, const Common::String &filename) { Common::SeekableReadStream *data = g_resourceloader->openNewStreamFile(filename); // Remember to check, in case we forgot to copy over those files from the CDs. if (!data) { warning("Couldn't open %s", filename.c_str()); return; } TextSplitter *ts = new TextSplitter(filename, data); int id, x, y, sync, trim; char musicfilename[64]; char type[16]; // Every block is followed by 3 lines of commenting/uncommenting, except the last. while (!ts->isEof()) { while (!ts->checkString("*/")) { while (!ts->checkString(".cuebutton")) ts->nextLine(); ts->scanString(".cuebutton id %d x %d y %d sync %d type %16s", 5, &id, &x, &y, &sync, type); ts->scanString(".playfile trim %d \"%[^\"]64s", 2, &trim, musicfilename); if (musicfilename[1] == '\\') musicfilename[1] = '/'; musicTable[id]._id = id; musicTable[id]._x = x; musicTable[id]._y = y; musicTable[id]._sync = sync; musicTable[id]._type = type; musicTable[id]._name = ""; musicTable[id]._trim = trim; musicTable[id]._filename = musicfilename; } ts->nextLine(); } delete ts; delete data; } void tableLoadErrorDialog() { Common::U32String errorMessage = _("ERROR: Not enough music tracks found!\n" "Escape from Monkey Island has two versions of FullMonkeyMap.imt,\n" "you need to copy both files from both CDs to Textures/, and rename\n" "them as follows to get music-support in-game: \n" "CD 1: \"FullMonkeyMap.imt\" -> \"FullMonkeyMap1.imt\"\n" "CD 2: \"FullMonkeyMap.imt\" -> \"FullMonkeyMap2.imt\"\n" "\n" "Alternatively, a Steam or GOG copy has a combined FullMonkeyMap.imt"); GUI::displayErrorDialog(errorMessage); } void EMISound::initMusicTable() { if (g_grim->getGameFlags() & ADGF_DEMO) { _musicTable = initMusicTableDemo("Music/FullMonkeyMap.imt"); _musicPrefix = "Music/"; } else if (g_grim->getGamePlatform() == Common::kPlatformPS2) { static_assert(ARRAYSIZE(emiPS2MusicTable) == 126, "emiPS2MusicTable bad size"); _numMusicStates = 126; _musicTable = new MusicEntry[126]; for (int i = 0; i < 126; ++i) { _musicTable[i]._x = 0; _musicTable[i]._y = 0; _musicTable[i]._sync = emiPS2MusicTable[i].sync; _musicTable[i]._trim = 127; _musicTable[i]._id = i; _musicTable[i]._type = emiPS2MusicTable[i].type; _musicTable[i]._filename = emiPS2MusicTable[i].filename; } _musicPrefix = ""; } else { MusicEntry *musicTable = new MusicEntry[126]; for (int i = 0; i < 126; ++i) { musicTable[i]._x = 0; musicTable[i]._y = 0; musicTable[i]._sync = 0; musicTable[i]._trim = 0; musicTable[i]._id = i; } initMusicTableRetail(musicTable, "Textures/FullMonkeyMap1.imt"); initMusicTableRetail(musicTable, "Textures/FullMonkeyMap2.imt"); initMusicTableRetail(musicTable, "Textures/FullMonkeyMap.imt"); // There seem to be 69+60 music tracks, for a total of 125 unique tracks. int numTracks = 0; for (int i = 0; i < 126; i++) { if (!musicTable[i]._filename.empty()) { numTracks++; } } warning("Found %d music tracks, expected at least 100", numTracks); if (numTracks < 100) { delete[] musicTable; _numMusicStates = 0; _musicTable = nullptr; tableLoadErrorDialog(); } else { _numMusicStates = 126; _musicTable = musicTable; _musicPrefix = "Textures/spago/"; // Default to high-quality music. } } } void EMISound::selectMusicSet(int setId) { if (g_grim->getGamePlatform() == Common::kPlatformPS2) { assert(setId == 0); _musicPrefix = ""; return; } if (setId == 0) { _musicPrefix = "Textures/spago/"; } else if (setId == 1) { _musicPrefix = "Textures/mego/"; } else { error("EMISound::selectMusicSet - Unknown setId %d", setId); } // Immediately switch all currently active music tracks to the new quality. for (TrackList::iterator it = _playingTracks.begin(); it != _playingTracks.end(); ++it) { SoundTrack *track = (*it); if (track && track->getSoundType() == Audio::Mixer::kMusicSoundType) { (*it) = restartTrack(track); delete track; } } for (uint32 i = 0; i < _stateStack.size(); ++i) { SoundTrack *track = _stateStack[i]._track; if (track) { _stateStack[i]._track = restartTrack(track); delete track; } } } SoundTrack *EMISound::restartTrack(SoundTrack *track) { Audio::Timestamp pos = track->getPos(); SoundTrack *newTrack = initTrack(track->getSoundName(), track->getSoundType(), &pos); if (newTrack) { newTrack->setVolume(track->getVolume()); newTrack->setBalance(track->getBalance()); newTrack->setFadeMode(track->getFadeMode()); newTrack->setFade(track->getFade()); if (track->isPlaying()) { newTrack->play(); } if (track->isPaused()) { newTrack->pause(); } } return newTrack; } void EMISound::pushStateToStack() { Common::StackLock lock(_mutex); if (_musicTrack) { _musicTrack->fadeOut(); StackEntry entry = { _curMusicState, _musicTrack }; _stateStack.push(entry); _musicTrack = nullptr; } else { StackEntry entry = { _curMusicState, nullptr }; _stateStack.push(entry); } _curMusicState = 0; } void EMISound::popStateFromStack() { Common::StackLock lock(_mutex); if (_musicTrack) { _musicTrack->fadeOut(); _playingTracks.push_back(_musicTrack); } //even pop state from stack if music isn't set StackEntry entry = _stateStack.pop(); SoundTrack *track = entry._track; _musicTrack = track; _curMusicState = entry._state; if (track) { if (track->isPaused()) { track->pause(); } track->fadeIn(); } } void EMISound::flushStack() { Common::StackLock lock(_mutex); while (!_stateStack.empty()) { SoundTrack *temp = _stateStack.pop()._track; delete temp; } } void EMISound::pause(bool paused) { Common::StackLock lock(_mutex); for (TrackList::iterator it = _playingTracks.begin(); it != _playingTracks.end(); ++it) { SoundTrack *track = (*it); if (paused == track->isPaused()) continue; // Do not pause music. if (track == _musicTrack) continue; track->pause(); } for (TrackMap::iterator it = _preloadedTrackMap.begin(); it != _preloadedTrackMap.end(); ++it) { SoundTrack *track = (*it)._value; if (!track->isPlaying() || paused == track->isPaused()) continue; track->pause(); } } void EMISound::callback() { Common::StackLock lock(_mutex); if (_musicTrack) { updateTrack(_musicTrack); } for (uint i = 0; i < _stateStack.size(); ++i) { SoundTrack *track = _stateStack[i]._track; if (track == nullptr || track->isPaused() || !track->isPlaying()) continue; updateTrack(track); if (track->getFadeMode() == SoundTrack::FadeOut && track->getFade() == 0.0f) { track->pause(); } } for (TrackList::iterator it = _playingTracks.begin(); it != _playingTracks.end(); ++it) { SoundTrack *track = (*it); if (track->isPaused() || !track->isPlaying()) continue; updateTrack(track); if (track->getFadeMode() == SoundTrack::FadeOut && track->getFade() == 0.0f) { track->stop(); } } } void EMISound::updateTrack(SoundTrack *track) { if (track->getFadeMode() != SoundTrack::FadeNone) { float fadeStep = 0.5f / _callbackFps; float fade = track->getFade(); if (track->getFadeMode() == SoundTrack::FadeIn) { fade += fadeStep; if (fade > 1.0f) fade = 1.0f; track->setFade(fade); } else { fade -= fadeStep; if (fade < 0.0f) fade = 0.0f; track->setFade(fade); } } } void EMISound::flushTracks() { Common::StackLock lock(_mutex); for (TrackList::iterator it = _playingTracks.begin(); it != _playingTracks.end(); ++it) { SoundTrack *track = (*it); if (!track->isPlaying()) { delete track; it = _playingTracks.erase(it); } } } void EMISound::restoreState(SaveGame *savedState) { Common::StackLock lock(_mutex); // Clear any current music flushStack(); setMusicState(0); freePlayingSounds(); freeLoadedSounds(); delete _musicTrack; _musicTrack = nullptr; // Actually load: savedState->beginSection('SOUN'); _musicPrefix = savedState->readString(); if (savedState->saveMinorVersion() >= 21) { _curMusicState = savedState->readLESint32(); } // Stack: uint32 stackSize = savedState->readLEUint32(); for (uint32 i = 0; i < stackSize; i++) { SoundTrack *track = nullptr; int state = 0; if (savedState->saveMinorVersion() >= 21) { state = savedState->readLESint32(); bool hasTrack = savedState->readBool(); if (hasTrack) { track = restoreTrack(savedState); } } else { Common::String soundName = savedState->readString(); track = initTrack(soundName, Audio::Mixer::kMusicSoundType); if (track) { track->play(); track->pause(); } } StackEntry entry = { state, track }; _stateStack.push(entry); } // Music: if (savedState->saveMinorVersion() < 21) { uint32 hasActiveTrack = savedState->readLEUint32(); if (hasActiveTrack) { Common::String soundName = savedState->readString(); _musicTrack = initTrack(soundName, Audio::Mixer::kMusicSoundType); if (_musicTrack) { _musicTrack->play(); } else { error("Couldn't reopen %s", soundName.c_str()); } } } else if (savedState->saveMinorVersion() >= 21) { bool musicActive = savedState->readBool(); if (musicActive) { _musicTrack = restoreTrack(savedState); } } // Effects and voices: uint32 numTracks = savedState->readLEUint32(); for (uint32 i = 0; i < numTracks; i++) { bool channelIsActive = true; if (savedState->saveMinorVersion() < 21) { channelIsActive = (savedState->readLESint32() != 0); } if (channelIsActive) { SoundTrack *track = restoreTrack(savedState); _playingTracks.push_back(track); } } // Preloaded sounds: if (savedState->saveMinorVersion() >= 21) { _curTrackId = savedState->readLESint32(); uint32 numLoaded = savedState->readLEUint32(); for (uint32 i = 0; i < numLoaded; ++i) { int id = savedState->readLESint32(); _preloadedTrackMap[id] = restoreTrack(savedState); } } savedState->endSection(); } void EMISound::saveState(SaveGame *savedState) { Common::StackLock lock(_mutex); savedState->beginSection('SOUN'); savedState->writeString(_musicPrefix); savedState->writeLESint32(_curMusicState); // Stack: uint32 stackSize = _stateStack.size(); savedState->writeLEUint32(stackSize); for (uint32 i = 0; i < stackSize; i++) { savedState->writeLESint32(_stateStack[i]._state); if (!_stateStack[i]._track) { savedState->writeBool(false); } else { savedState->writeBool(true); saveTrack(_stateStack[i]._track, savedState); } } // Music: savedState->writeBool(_musicTrack != nullptr); if (_musicTrack) { saveTrack(_musicTrack, savedState); } // Effects and voices: savedState->writeLEUint32(_playingTracks.size()); for (TrackList::iterator it = _playingTracks.begin(); it != _playingTracks.end(); ++it) { saveTrack((*it), savedState); } // Preloaded sounds: savedState->writeLESint32(_curTrackId); uint32 numLoaded = _preloadedTrackMap.size(); savedState->writeLEUint32(numLoaded); for (TrackMap::iterator it = _preloadedTrackMap.begin(); it != _preloadedTrackMap.end(); ++it) { savedState->writeLESint32(it->_key); saveTrack(it->_value, savedState); } savedState->endSection(); } void EMISound::saveTrack(SoundTrack *track, SaveGame *savedState) { savedState->writeString(track->getSoundName()); savedState->writeLEUint32(track->getVolume()); savedState->writeLEUint32(track->getBalance()); savedState->writeLEUint32(track->getPos().msecs()); savedState->writeBool(track->isPlaying()); savedState->writeBool(track->isPaused()); savedState->writeLESint32((int)track->getSoundType()); savedState->writeLESint32((int)track->getFadeMode()); savedState->writeFloat(track->getFade()); savedState->writeLESint32(track->getSync()); savedState->writeBool(track->isLooping()); savedState->writeBool(track->isPositioned()); savedState->writeVector3d(track->getWorldPos()); } SoundTrack *EMISound::restoreTrack(SaveGame *savedState) { Common::String soundName = savedState->readString(); int volume = savedState->readLESint32(); int balance = savedState->readLESint32(); Audio::Timestamp pos(savedState->readLESint32()); bool playing = savedState->readBool(); if (savedState->saveMinorVersion() < 21) { SoundTrack *track = initTrack(soundName, Audio::Mixer::kSpeechSoundType); if (track) track->play(); return track; } bool paused = savedState->readBool(); Audio::Mixer::SoundType soundType = (Audio::Mixer::SoundType)savedState->readLESint32(); SoundTrack::FadeMode fadeMode = (SoundTrack::FadeMode)savedState->readLESint32(); float fade = savedState->readFloat(); int sync = savedState->readLESint32(); bool looping = savedState->saveMinorVersion() >= 21 ? savedState->readBool() : false; bool positioned = false; Math::Vector3d worldPos; if (savedState->saveMinorVersion() >= 23) { positioned = savedState->readBool(); worldPos = savedState->readVector3d(); } SoundTrack *track = initTrack(soundName, soundType, &pos); track->setVolume(volume); track->setBalance(balance); track->setPosition(positioned, worldPos); track->setLooping(looping); track->setFadeMode(fadeMode); track->setFade(fade); track->setSync(sync); if (playing) track->play(); if (paused) track->pause(); return track; } void EMISound::updateSoundPositions() { Common::StackLock lock(_mutex); for (TrackList::iterator it = _playingTracks.begin(); it != _playingTracks.end(); ++it) { SoundTrack *track = (*it); track->updatePosition(); } for (TrackMap::iterator it = _preloadedTrackMap.begin(); it != _preloadedTrackMap.end(); ++it) { SoundTrack *track = (*it)._value; track->updatePosition(); } } } // end of namespace Grim