/* 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 "audio/audiostream.h" #include "audio/decoders/raw.h" #include "common/config-manager.h" #include "dgds/dgds.h" #include "dgds/sound/resource/sci_resource.h" #include "dgds/sound/midiparser_sci.h" #include "dgds/sound/music.h" //#define DEBUG_REMAP namespace Dgds { SciMusic::SciMusic(bool useDigitalSFX) : _mutex(g_system->getMixer()->mutex()), _soundOn(true), _masterVolume(15), _globalReverb(0), _useDigitalSFX(useDigitalSFX), _needsResume(true), _globalPause(0), _pMidiDrv(nullptr), _pMixer(nullptr), _dwTempo(0), _needsRemap(false) , _musicType(MusicType::MT_NULL), _driverFirstChannel(0), _driverLastChannel(0), _currentlyPlayingSample(nullptr), _timeCounter(0) { // Reserve some space in the playlist, to avoid expensive insertion // operations _playList.reserve(10); for (int i = 0; i < 16; i++) { _usedChannel[i] = nullptr; _channelRemap[i] = -1; _channelMap[i]._song = nullptr; _channelMap[i]._channel = -1; } _queuedCommands.reserve(1000); } SciMusic::~SciMusic() { clearPlayList(); if (_pMidiDrv) { _pMidiDrv->close(); delete _pMidiDrv; } } void SciMusic::init() { // system init _pMixer = g_system->getMixer(); // SCI sound init _dwTempo = 0; const Common::Platform platform = DgdsEngine::getInstance()->getPlatform(); // // TODO: This list seems right for the basic versions of the DOS games. // // Do we ever want to support MDT_CMS? It looks like Willy Beamish // has *some* audio for that track type, but it's not complete and not // supported in the game's installer. // uint32 deviceFlags = MDT_PCSPK | MDT_ADLIB | MDT_MIDI; uint32 dev = MidiDriver::detectDevice(deviceFlags); _musicType = MidiDriver::getMusicType(dev); switch (_musicType) { case MT_ADLIB: // FIXME: There's no Amiga sound option, so we hook it up to AdLib if (platform == Common::kPlatformMacintosh || platform == Common::kPlatformAmiga) { _pMidiDrv = MidiPlayer_AmigaMac1_create(platform); } else _pMidiDrv = MidiPlayer_AdLib_create(); break; case MT_PCSPK: error("TODO: Implement PC speaker driver?"); //_pMidiDrv = MidiPlayer_PCSpeaker_create(); break; case MT_CMS: _pMidiDrv = MidiPlayer_CMS_create(); break; default: _pMidiDrv = MidiPlayer_Midi_create(); } if (_pMidiDrv && !_pMidiDrv->open()) { _pMidiDrv->setTimerCallback(this, &miditimerCallback); _dwTempo = _pMidiDrv->getBaseTempo(); } else { error("Failed to initialize sound driver"); } // Find out what the first possible channel is (used, when doing channel // remapping). _driverFirstChannel = _pMidiDrv->getFirstChannel(); _driverLastChannel = _pMidiDrv->getLastChannel(); _currentlyPlayingSample = nullptr; _timeCounter = 0; _needsRemap = false; } void SciMusic::miditimerCallback(void *p) { SciMusic *sciMusic = (SciMusic *)p; Common::StackLock lock(sciMusic->_mutex); sciMusic->onTimer(); } void SciMusic::onTimer() { // sending out queued commands that were "sent" via main thread sendMidiCommandsFromQueue(); // remap channels, if requested if (_needsRemap) remapChannels(false); _needsRemap = false; for (auto &music : _playList) music->onTimer(); } void SciMusic::putMidiCommandInQueue(byte status, byte firstOp, byte secondOp) { putMidiCommandInQueue(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16)); } void SciMusic::putMidiCommandInQueue(uint32 midi) { _queuedCommands.push_back(MidiCommand(MidiCommand::kTypeMidiMessage, midi)); } void SciMusic::putTrackInitCommandInQueue(MusicEntry *psnd) { _queuedCommands.push_back(MidiCommand(MidiCommand::kTypeTrackInit, psnd)); } void SciMusic::removeTrackInitCommandsFromQueue(MusicEntry *psnd) { for (MidiCommandQueue::iterator i = _queuedCommands.begin(); i != _queuedCommands.end(); ) i = (i->_type == MidiCommand::kTypeTrackInit && i->_dataPtr == (void*)psnd) ? _queuedCommands.erase(i) : i + 1; } // This sends the stored commands from queue to driver (is supposed to get // called only during onTimer()). At least mt32 emulation doesn't like getting // note-on commands from main thread (if we directly send, we would get a crash // during piano scene in lsl5). void SciMusic::sendMidiCommandsFromQueue() { uint curCommand = 0; uint commandCount = _queuedCommands.size(); while (curCommand < commandCount) { if (_queuedCommands[curCommand]._type == MidiCommand::kTypeTrackInit) { if (_queuedCommands[curCommand]._dataPtr) { MusicList::iterator psnd = Common::find(_playList.begin(), _playList.end(), static_cast(_queuedCommands[curCommand]._dataPtr)); if (psnd != _playList.end() && (*psnd)->pMidiParser) (*psnd)->pMidiParser->initTrack(); } } else { _pMidiDrv->send(_queuedCommands[curCommand]._dataVal); } curCommand++; } _queuedCommands.clear(); } void SciMusic::clearPlayList() { // we must NOT lock our mutex here. Playlist is modified inside soundKill() which will lock the mutex // during deletion. If we lock it here, a deadlock may occur within soundStop() because that one // calls the mixer, which will also lock the mixer mutex and if the mixer thread is active during // that time, we will get a deadlock. while (!_playList.empty()) { soundStop(_playList[0]); soundKill(_playList[0]); } } void SciMusic::pauseAll(bool pause) { bool alreadyUnpaused = (_globalPause <= 0); if (pause) _globalPause++; else _globalPause--; bool stillUnpaused = (_globalPause <= 0); // This check is for a specific situation (the ScummVM autosave) which will try to unpause the music, // although it is already unpaused, and after the save it will then pause it again. We allow the // _globalPause counter to go into the negative, so that the final outcome of both calls is a _globalPause // counter of 0 (First: 0, then -1, then 0 again). However, the pause counters of the individual sounds // do not support negatives. And it would be somewhat more likely to cause regressions to add that // support than to just contain it here... // So, for cases where the status of the _globalPause counter only changes in the range below or equal 0 // we return here. The individual sounds only need to get targeted if they ACTUALLY get paused or // unpaused (_globalPause counter changes from 0 to 1 or from 1 to 0) or if the pause counter is // increased above 1 (since positive counters are supported and required for the individual sounds). if (alreadyUnpaused && stillUnpaused) return; for (auto &music : _playList) { soundToggle(music, pause); } } void SciMusic::resetGlobalPauseCounter() { // This is an adjustment for our savegame loading process, // ONLY when done from kRestoreGame(). // The enginge will call SciMusic::pauseAll() before loading. // So the _globalPause will be increased and the individual // sounds will be paused, too. However, the sounds will // then be restored to the playing status that is stored in // the savegame. The _globalPause stays, however. There may // be no unpausing after the loading, since the playing status // in the savegames is the correct one. So, the essence is: // the _globalPause counter needs to go down without anything // else happening. // The loading from GMM has been implemented differently. It // will remove the paused state before loading (and doesn't // do anything unpleasant afterwards, either). So this is not // needed there. // I have added an assert, since it is such a special case, // people need to know what they're doing if they call this. // The value can be greater than 1, since the scripts may // already have increased it, before the kRestoreGame() call // happens. assert(_globalPause >= 1); _globalPause = 0; } void SciMusic::stopAll() { for (auto &sound: _playList) { soundStop(sound); } } void SciMusic::stopAllSamples() { for (auto &sound: _playList) { if (sound->isSample) { soundStop(sound); } } } void SciMusic::soundSetSoundOn(bool soundOnFlag) { Common::StackLock lock(_mutex); _soundOn = soundOnFlag; _pMidiDrv->playSwitch(soundOnFlag); } uint16 SciMusic::soundGetVoices() { Common::StackLock lock(_mutex); return _pMidiDrv->getPolyphony(); } MusicEntry *SciMusic::getSlot(uint32 obj) { Common::StackLock lock(_mutex); for (auto &sound: _playList) { if (sound->soundObj == obj) return sound; } return nullptr; } MusicEntry *SciMusic::getFirstSlotWithStatus(SoundStatus status) { for (auto &sound: _playList) { if (sound->status == status) return sound; } return nullptr; } void SciMusic::stopMusic() { for (auto &sound: _playList) { if (sound->soundType == Audio::Mixer::kMusicSoundType) soundStop(sound); } } void SciMusic::pauseMusic() { for (auto &sound: _playList) { if (sound->soundType == Audio::Mixer::kMusicSoundType) soundPause(sound); } } void SciMusic::resumeMusic() { for (auto &sound: _playList) { if (sound->soundType == Audio::Mixer::kMusicSoundType) soundResume(sound); } } void SciMusic::stopSFX() { for (auto &sound: _playList) { if (sound->soundType == Audio::Mixer::kSFXSoundType) soundStop(sound); } } void SciMusic::setGlobalReverb(int8 reverb) { Common::StackLock lock(_mutex); if (reverb != 127) { // Set global reverb normally _globalReverb = reverb; // Check the reverb of the active song... for (const auto &sound: _playList) { if (sound->status == kSoundPlaying) { if (sound->reverb == 127) // Active song has no reverb _pMidiDrv->setReverb(reverb); // Set the global reverb break; } } } else { // Set reverb of the active song for (const auto &sound: _playList) { if (sound->status == kSoundPlaying) { _pMidiDrv->setReverb(sound->reverb); // Set the song's reverb break; } } } } byte SciMusic::getCurrentReverb() { Common::StackLock lock(_mutex); return _pMidiDrv->getReverb(); } // A larger priority value has higher priority. For equal priority values, // songs that have been added later have higher priority. static bool musicEntryCompare(const MusicEntry *l, const MusicEntry *r) { return (l->priority > r->priority) || (l->priority == r->priority && l->time > r->time); } void SciMusic::sortPlayList() { // Sort the play list in descending priority order Common::sort(_playList.begin(), _playList.end(), musicEntryCompare); } void SciMusic::soundInitSnd(MusicEntry *pSnd) { // Remove all currently mapped channels of this MusicEntry first, // since they will no longer be valid. for (int i = 0; i < 16; ++i) { if (_channelMap[i]._song == pSnd) { _channelMap[i]._song = nullptr; _channelMap[i]._channel = -1; } } SoundResource::Track *track = pSnd->soundRes->getTrackByType(_pMidiDrv->getPlayId()); // If MIDI device is selected but there is no digital track in sound // resource try to use Adlib's digital sample if possible. Also, if the // track couldn't be found, load the digital track, as some games depend on // this (e.g. the Longbow demo). if (!track || (_useDigitalSFX && track->digitalChannelNr == -1)) { SoundResource::Track *digital = pSnd->soundRes->getDigitalTrack(); if (digital) track = digital; } pSnd->time = ++_timeCounter; if (track) { bool playSample = (track->digitalChannelNr != -1 && (_useDigitalSFX || track->channelCount == 1)); // Play digital sample if (playSample) { const SciSpan &channelData = track->channels[track->digitalChannelNr].data; delete pSnd->pStreamAud; byte flags = Audio::FLAG_UNSIGNED; // Amiga SCI1 games had signed sound data if (DgdsEngine::getInstance()->getPlatform() == Common::kPlatformAmiga) flags = 0; int endPart = track->digitalSampleEnd > 0 ? (track->digitalSampleSize - track->digitalSampleEnd) : 0; const uint size = track->digitalSampleSize - track->digitalSampleStart - endPart; pSnd->pStreamAud = Audio::makeRawStream(channelData.getUnsafeDataAt(track->digitalSampleStart), size, track->digitalSampleRate, flags, DisposeAfterUse::NO); assert(pSnd->pStreamAud); delete pSnd->pLoopStream; pSnd->pLoopStream = nullptr; pSnd->soundType = Audio::Mixer::kSFXSoundType; pSnd->hCurrentAud = Audio::SoundHandle(); pSnd->playBed = false; pSnd->overridePriority = false; pSnd->isSample = true; } else { // play MIDI track Common::StackLock lock(_mutex); //pSnd->soundType = Audio::Mixer::kMusicSoundType; // set on init for DGDS if (pSnd->pMidiParser == nullptr) { pSnd->pMidiParser = new MidiParser_SCI(this); pSnd->pMidiParser->setMidiDriver(_pMidiDrv); pSnd->pMidiParser->setTimerRate(_dwTempo); pSnd->pMidiParser->setMasterVolume(_masterVolume); } pSnd->pauseCounter = 0; for (int i = 0; i < 16; ++i) { pSnd->_usedChannels[i] = 0xFF; pSnd->_chan[i]._dontMap = false; pSnd->_chan[i]._dontRemap = false; pSnd->_chan[i]._prio = -1; pSnd->_chan[i]._voices = -1; pSnd->_chan[i]._mute = 0; } for (int i = 0; i < track->channelCount; ++i) { // skip digital channel if (i == track->digitalChannelNr) { continue; } SoundResource::Channel &chan = track->channels[i]; assert(chan.number < ARRAYSIZE(pSnd->_chan)); pSnd->_usedChannels[i] = chan.number; // Flag 1 is exclusive towards the other flags. When it is // set the others won't even get evaluated. And it wouldn't // matter, since channels flagged with 1 operate completely // independent of the channel mapping. // For more info on the flags see the comment in // SoundResource::SoundResource(). pSnd->_chan[chan.number]._dontMap |= (bool)(chan.flags & 1); // Flag 2 prevents the channel number from being remapped // to a different free channel on the MIDI device. // It's possible for a MIDI track to define the same channel // multiple times with different values for dontRemap. // This can be a problem if it is a dedicated percussion // channel, so always err on the side of caution. pSnd->_chan[chan.number]._dontRemap |= (bool)(chan.flags & 2); // HACK for DGDS - don't remap rhythm channel. if (chan.number == MIDI_RHYTHM_CHANNEL) pSnd->_chan[chan.number]._dontRemap = true; if (pSnd->_chan[chan.number]._prio == -1) pSnd->_chan[chan.number]._prio = chan.prio; if (pSnd->_chan[chan.number]._voices == -1) pSnd->_chan[chan.number]._voices = chan.poly; pSnd->_chan[chan.number]._mute |= ((chan.flags & 4) ? 1 : 0); // FIXME: Most MIDI tracks use the first 10 bytes for // fixed MIDI commands. SSCI skips those the first iteration, // but _does_ update channel state (including volume) with // them. Specifically, prio/voices, patch, volume, pan. // This should probably be implemented in MidiParser_SCI::loadMusic. // // UPDATE: While we could change how we handle it, we DO // read the commands into the channel data arrays when we call // trackState(). So, I think what we do has the same result... } pSnd->pMidiParser->mainThreadBegin(); // loadMusic() below calls jumpToTick. // Disable sound looping and hold before jumpToTick is called, // otherwise the song may keep looping forever when it ends in // jumpToTick (e.g. LSL3, when going left from room 210). uint16 prevLoop = pSnd->loop; int16 prevHold = pSnd->hold; pSnd->loop = 0; pSnd->hold = -1; pSnd->playBed = false; pSnd->overridePriority = false; pSnd->pMidiParser->loadMusic(track, pSnd, 0); pSnd->reverb = pSnd->pMidiParser->getSongReverb(); // Restore looping and hold pSnd->loop = prevLoop; pSnd->hold = prevHold; pSnd->pMidiParser->mainThreadEnd(); } } } void SciMusic::soundPlay(MusicEntry *pSnd, bool restoring) { _mutex.lock(); if (DgdsEngine::getInstance()->getGameId() == GID_DRAGON && pSnd->playBed) { // If pSnd->playBed, and version <= SCI1_EARLY, then kill // existing sounds with playBed enabled. uint playListCount = _playList.size(); for (uint i = 0; i < playListCount; i++) { if (_playList[i] != pSnd && _playList[i]->playBed) { debugC(2, kDebugLevelSound, "Automatically stopping old playBed song from soundPlay"); MusicEntry *old = _playList[i]; _mutex.unlock(); soundStop(old); _mutex.lock(); break; } } } uint playListCount = _playList.size(); uint playListNo = playListCount; //MusicEntry *alreadyPlaying = nullptr; // searching if sound is already in _playList for (uint i = 0; i < playListCount; i++) { if (_playList[i] == pSnd) playListNo = i; //if ((_playList[i]->status == kSoundPlaying) && (_playList[i]->pMidiParser)) // alreadyPlaying = _playList[i]; } if (playListNo == playListCount) { // not found _playList.push_back(pSnd); } pSnd->time = ++_timeCounter; sortPlayList(); _mutex.unlock(); // unlock to perform mixer-related calls if (pSnd->isSample) { if (isDigitalSamplePlaying()) { // Another sample is already playing, we have to stop that one // SSCI is only able to play 1 sample at a time // In Space Quest 5 room 250 the player is able to open the air-hatch and kill himself. // In that situation the scripts are playing 2 samples at the same time and the first sample // is not supposed to play. // TODO: SSCI actually calls kDoAudio(play) internally, which stops other samples from being played // but such a change isn't trivial, because we also handle Sound resources in here, that contain samples _pMixer->stopHandle(_currentlyPlayingSample->hCurrentAud); warning("kDoSound: sample already playing, old resource %d, new resource %d", _currentlyPlayingSample->resourceId, pSnd->resourceId); } // Sierra SCI ignores volume set when playing samples via kDoSound // At least freddy pharkas/CD has a script bug that sets volume to 0 // when playing the "score" sample if (pSnd->loop > 1) { pSnd->pLoopStream = new Audio::LoopingAudioStream(pSnd->pStreamAud, pSnd->loop, DisposeAfterUse::NO); _pMixer->playStream(pSnd->soundType, &pSnd->hCurrentAud, pSnd->pLoopStream, -1, _pMixer->kMaxChannelVolume, 0, DisposeAfterUse::NO); } else { // Rewind in case we play the same sample multiple times // (non-looped) like in pharkas right at the start pSnd->pStreamAud->rewind(); _pMixer->playStream(pSnd->soundType, &pSnd->hCurrentAud, pSnd->pStreamAud, -1, _pMixer->kMaxChannelVolume, 0, DisposeAfterUse::NO); } // Remember the sample, that is now playing _currentlyPlayingSample = pSnd; } else { if (pSnd->pMidiParser) { Common::StackLock lock(_mutex); pSnd->pMidiParser->mainThreadBegin(); // Call this before initTrack(), since several sound drivers have custom channel volumes that get set in // initTrack() and we don't want to overwrite those with the generic values from sendInitCommands(). if (pSnd->status != kSoundPaused) pSnd->pMidiParser->sendInitCommands(); // The track init always needs to be done. Otherwise some sounds will not be properly set up (bug #11476). // It is also safe to do this for paused tracks, since the jumpToTick() command further down will parse through // the song from the beginning up to the resume position and ensure that the actual current voice mapping, // instrument and volume settings etc. are correct. // First glance at disasm might suggest that it has to be called only once per sound. But the truth is that // when calling the sound driver opcode for sound restoring (opcode no. 9, we don't have that) it will // internally also call initTrack(). And it wouldn't make sense otherwise, since without that the channel setup // from the last sound would still be active. pSnd->pMidiParser->initTrack(); pSnd->pMidiParser->setVolume(pSnd->volume); // Disable sound looping and hold before jumpToTick is called, // otherwise the song may keep looping forever when it ends in jumpToTick. // This is needed when loading saved games, or when a game // stops the same sound twice (e.g. LSL3 Amiga, going left from // room 210 to talk with Kalalau). Fixes bugs #5404 and #5503. uint16 prevLoop = pSnd->loop; int16 prevHold = pSnd->hold; pSnd->loop = 0; pSnd->hold = -1; bool fastForward = (pSnd->status == kSoundPaused) || (pSnd->status == kSoundPlaying && restoring); if (!fastForward) { pSnd->pMidiParser->jumpToTick(0); } else { // Fast forward to the last position and perform associated events when loading pSnd->pMidiParser->jumpToTick(pSnd->ticker, true, true, true); } // Restore looping and hold pSnd->loop = prevLoop; pSnd->hold = prevHold; pSnd->pMidiParser->mainThreadEnd(); } } pSnd->status = kSoundPlaying; _mutex.lock(); remapChannels(); _mutex.unlock(); } void SciMusic::soundStop(MusicEntry *pSnd) { SoundStatus previousStatus = pSnd->status; pSnd->status = kSoundStopped; if (pSnd->isSample) { if (_currentlyPlayingSample == pSnd) _currentlyPlayingSample = nullptr; _pMixer->stopHandle(pSnd->hCurrentAud); } if (pSnd->pMidiParser) { Common::StackLock lock(_mutex); pSnd->pMidiParser->mainThreadBegin(); // We shouldn't call stop in case it's paused, otherwise we would send // allNotesOff() again if (previousStatus == kSoundPlaying) pSnd->pMidiParser->stop(); pSnd->pMidiParser->mainThreadEnd(); remapChannels(); } pSnd->fadeStep = 0; // end fading, if fading was in progress } void SciMusic::soundSetVolume(MusicEntry *pSnd, byte volume) { assert(volume <= MUSIC_VOLUME_MAX); if (!pSnd->isSample && pSnd->pMidiParser) { Common::StackLock lock(_mutex); pSnd->pMidiParser->mainThreadBegin(); pSnd->pMidiParser->setVolume(volume); pSnd->pMidiParser->mainThreadEnd(); } } // this is used to set volume of the sample, used for fading only! void SciMusic::soundSetSampleVolume(MusicEntry *pSnd, byte volume) { assert(volume <= MUSIC_VOLUME_MAX); assert(pSnd->pStreamAud); _pMixer->setChannelVolume(pSnd->hCurrentAud, volume * 2); // Mixer is 0-255, SCI is 0-127 } void SciMusic::soundSetPriority(MusicEntry *pSnd, byte prio) { Common::StackLock lock(_mutex); pSnd->priority = prio; pSnd->time = ++_timeCounter; sortPlayList(); } void SciMusic::soundKill(MusicEntry *pSnd) { pSnd->status = kSoundStopped; _mutex.lock(); remapChannels(); if (pSnd->pMidiParser) { pSnd->pMidiParser->mainThreadBegin(); pSnd->pMidiParser->unloadMusic(); pSnd->pMidiParser->mainThreadEnd(); delete pSnd->pMidiParser; pSnd->pMidiParser = nullptr; } _mutex.unlock(); if (pSnd->isSample) { if (_currentlyPlayingSample == pSnd) { // Forget about this sound, in case it was currently playing _currentlyPlayingSample = nullptr; } _pMixer->stopHandle(pSnd->hCurrentAud); delete pSnd->pStreamAud; pSnd->pStreamAud = nullptr; delete pSnd->pLoopStream; pSnd->pLoopStream = nullptr; pSnd->isSample = false; } _mutex.lock(); uint sz = _playList.size(), i; // Remove sound from playlist for (i = 0; i < sz; i++) { if (_playList[i] == pSnd) { delete _playList[i]->soundRes; delete _playList[i]; _playList.remove_at(i); break; } } _mutex.unlock(); } void SciMusic::soundPause(MusicEntry *pSnd) { // SCI seems not to be pausing samples played back by kDoSound at all // It only stops looping samples (actually doesn't loop them again before they are unpaused) // Examples: Space Quest 1 death by acid drops (pause is called even specifically for the sample, see bug #5097) // Eco Quest 1 during the intro when going to the abort-menu // In both cases sierra sci keeps playing // Leisure Suit Larry 1 doll scene - it seems that pausing here actually just stops // further looping from happening // This is a somewhat bigger change, I'm leaving in the old code in here just in case // I'm currently pausing looped sounds directly, non-looped sounds won't get paused if ((pSnd->pStreamAud) && (!pSnd->pLoopStream)) return; pSnd->pauseCounter++; if (pSnd->status != kSoundPlaying) return; _needsResume = true; pSnd->status = kSoundPaused; if (pSnd->pStreamAud) { _pMixer->pauseHandle(pSnd->hCurrentAud, true); } else { if (pSnd->pMidiParser) { Common::StackLock lock(_mutex); pSnd->pMidiParser->mainThreadBegin(); pSnd->pMidiParser->pause(); pSnd->pMidiParser->mainThreadEnd(); remapChannels(); } } } void SciMusic::soundResume(MusicEntry *pSnd) { if (pSnd->pauseCounter > 0) pSnd->pauseCounter--; if (pSnd->pauseCounter != 0) return; if (pSnd->status != kSoundPaused || (_globalPause > 0 && !_needsResume)) return; _needsResume = true; if (pSnd->pStreamAud) { _pMixer->pauseHandle(pSnd->hCurrentAud, false); pSnd->status = kSoundPlaying; } else { soundPlay(pSnd, true); } } void SciMusic::soundToggle(MusicEntry *pSnd, bool pause) { if (pause) soundPause(pSnd); else soundResume(pSnd); } uint16 SciMusic::soundGetMasterVolume() { if (ConfMan.getBool("mute")) { // When a game is muted, the master volume is set to zero so that // mute applies to external MIDI devices, but this should not be // communicated to the game as it will cause the UI to be drawn with // the wrong (zero) volume for music return (ConfMan.getInt("music_volume") + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume; } return _masterVolume; } void SciMusic::soundSetMasterVolume(uint16 vol) { _masterVolume = vol; Common::StackLock lock(_mutex); for (auto &sound : _playList) { if (sound->pMidiParser) sound->pMidiParser->setMasterVolume(vol); } } /* These direct midi command sends are not needed in dgds void SciMusic::sendMidiCommand(uint32 cmd) { Common::StackLock lock(_mutex); _pMidiDrv->send(cmd); } void SciMusic::sendMidiCommand(MusicEntry *pSnd, uint32 cmd) { Common::StackLock lock(_mutex); if (!pSnd->pMidiParser) { // FPFP calls kDoSound SendMidi to mute and unmute its gameMusic2 sound // object but some scenes set this to an audio sample. In Act 2, room // 660 sets this to audio of restaurant customers talking. Walking up // the hotel stairs from room 500 to 235 calls gameMusic2:mute and // triggers this if gameMusic2 hasn't changed. Bug #10952 warning("tried to cmdSendMidi on non midi slot (%08x)", pSnd->soundObj); return; } pSnd->pMidiParser->mainThreadBegin(); pSnd->pMidiParser->sendFromScriptToDriver(cmd); pSnd->pMidiParser->mainThreadEnd(); } */ void SciMusic::printPlayList(Console *con) { Common::StackLock lock(_mutex); const char *musicStatus[] = { "Stopped", "Initialized", "Paused", "Playing" }; for (uint32 i = 0; i < _playList.size(); i++) { MusicEntry *song = _playList[i]; debug(1, "%d: %08x (%s), resource id: %d, status: %s, %s type\n", i, song->soundObj, "",//g_sci->getEngineState()->_segMan->getObjectName(song->soundObj), song->resourceId, musicStatus[song->status], song->pMidiParser ? "MIDI" : "digital audio"); } } void SciMusic::printSongInfo(uint32 obj, Console *con) { Common::StackLock lock(_mutex); const char *musicStatus[] = { "Stopped", "Initialized", "Paused", "Playing" }; for (auto &song : _playList) { if (song->soundObj == obj) { debug(1, "Resource id: %d, status: %s\n", song->resourceId, musicStatus[song->status]); debug(1, "dataInc: %d, hold: %d, loop: %d\n", song->dataInc, song->hold, song->loop); debug(1, "signal: %d, priority: %d\n", song->signal, song->priority); debug(1, "ticker: %d, volume: %d\n", song->ticker, song->volume); if (song->pMidiParser) { debug(1, "Type: MIDI\n"); if (song->soundRes) { SoundResource::Track *track = song->soundRes->getTrackByType(_pMidiDrv->getPlayId()); if (track) { debug(1, "Channels: %d\n", track->channelCount); } } } else if (song->pStreamAud || song->pLoopStream) { debug(1, "Type: digital audio (%s), sound active: %s\n", song->pStreamAud ? "non looping" : "looping", _pMixer->isSoundHandleActive(song->hCurrentAud) ? "yes" : "no"); if (song->soundRes) { debug(1, "Sound resource information:\n"); SoundResource::Track *track = song->soundRes->getTrackByType(_pMidiDrv->getPlayId()); if (track && track->digitalChannelNr != -1) { debug(1, "Sample size: %d, sample rate: %d, channels: %d, digital channel number: %d\n", track->digitalSampleSize, track->digitalSampleRate, track->channelCount, track->digitalChannelNr); } } } return; } } debug(1, "Song object not found in playlist"); } MusicEntry::MusicEntry() { soundObj = 0; soundRes = nullptr; resourceId = 0; time = 0; dataInc = 0; ticker = 0; signal = 0; priority = 0; loop = 0; volume = MUSIC_VOLUME_DEFAULT; hold = -1; reverb = -1; playBed = false; overridePriority = false; pauseCounter = 0; sampleLoopCounter = 0; fadeTo = 0; fadeStep = 0; fadeTicker = 0; fadeTickerStep = 0; fadeSetVolume = false; fadeCompleted = false; stopAfterFading = false; status = kSoundStopped; soundType = Audio::Mixer::kMusicSoundType; pStreamAud = nullptr; pLoopStream = nullptr; pMidiParser = nullptr; isSample = false; for (int i = 0; i < 16; ++i) { _usedChannels[i] = 0xFF; _chan[i]._prio = 127; _chan[i]._voices = 0; _chan[i]._dontRemap = false; _chan[i]._dontMap = false; _chan[i]._mute = false; } } MusicEntry::~MusicEntry() { } void MusicEntry::onTimer() { if (!signal) { if (!signalQueue.empty()) { // no signal set, but signal in queue, set that one signal = signalQueue[0]; signalQueue.remove_at(0); } } if (status != kSoundPlaying) return; // Fade MIDI and digital sound effects if (fadeStep) doFade(); // Only process MIDI streams in this thread, not digital sound effects if (pMidiParser) { pMidiParser->onTimer(); ticker = (uint16)pMidiParser->getTick(); } } void MusicEntry::doFade() { if (fadeTicker) fadeTicker--; else { fadeTicker = fadeTickerStep; volume += fadeStep; if (((fadeStep > 0) && (volume >= fadeTo)) || ((fadeStep < 0) && (volume <= fadeTo))) { volume = fadeTo; fadeStep = 0; fadeCompleted = true; } // Only process MIDI streams in this thread, not digital sound effects if (pMidiParser) { pMidiParser->setVolume(volume); } fadeSetVolume = true; // set flag so that SoundCommandParser::cmdUpdateCues will set the volume of the stream } } void MusicEntry::setSignal(int newSignal) { // Set the signal directly for newer games, otherwise the sound // object might be deleted already later on (refer to bug #5243) signal = newSignal; } void ChannelRemapping::swap(int i, int j) { DeviceChannelUsage t1; int t2; bool t3; t1 = _map[i]; _map[i] = _map[j]; _map[j] = t1; t2 = _prio[i]; _prio[i] = _prio[j]; _prio[j] = t2; t2 = _voices[i]; _voices[i] = _voices[j]; _voices[j] = t2; t3 = _dontRemap[i]; _dontRemap[i] = _dontRemap[j]; _dontRemap[j] = t3; } void ChannelRemapping::evict(int i) { _freeVoices += _voices[i]; _map[i]._song = nullptr; _map[i]._channel = -1; _prio[i] = 0; _voices[i] = 0; _dontRemap[i] = false; } void ChannelRemapping::clear() { for (int i = 0; i < 16; ++i) { _map[i]._song = nullptr; _map[i]._channel = -1; _prio[i] = 0; _voices[i] = 0; _dontRemap[i] = false; } } ChannelRemapping& ChannelRemapping::operator=(ChannelRemapping& other) { for (int i = 0; i < 16; ++i) { _map[i] = other._map[i]; _prio[i] = other._prio[i]; _voices[i] = other._voices[i]; _dontRemap[i] = other._dontRemap[i]; } _freeVoices = other._freeVoices; return *this; } int ChannelRemapping::lowestPrio() const { int max = 0; int channel = -1; for (int i = 0; i < 16; ++i) { if (_prio[i] > max) { max = _prio[i]; channel = i; } } return channel; } void SciMusic::remapChannels(bool mainThread) { // NB: This function should only be called with _mutex locked // Make sure to set the mainThread argument correctly. ChannelRemapping *map = determineChannelMap(); DeviceChannelUsage currentMap[16]; #ifdef DEBUG_REMAP debug(1, "Remap results:"); #endif // Save current map, and then start from an empty map for (int i = 0; i < 16; ++i) { currentMap[i] = _channelMap[i]; _channelMap[i]._song = nullptr; _channelMap[i]._channel = -1; } // Inform MidiParsers of any unmapped channels int songIndex = -1; for (auto &song : _playList) { songIndex++; if (!song || !song->pMidiParser) continue; bool channelMapped[16]; #ifdef DEBUG_REMAP bool channelUsed[16]; #endif for (int j = 0; j < 16; ++j) { channelMapped[j] = false; #ifdef DEBUG_REMAP channelUsed[j] = false; #endif } for (int j = 0; j < 16; ++j) { if (map->_map[j]._song == song) { int channel = map->_map[j]._channel; assert(channel >= 0 && channel <= 0x0F); channelMapped[channel] = true; } #ifdef DEBUG_REMAP if (song->_usedChannels[j] <= 0x0F) channelUsed[song->_usedChannels[j]] = true; #endif } for (int j = 0; j < 16; ++j) { if (!channelMapped[j]) { if (mainThread) song->pMidiParser->mainThreadBegin(); song->pMidiParser->remapChannel(j, -1); if (mainThread) song->pMidiParser->mainThreadEnd(); #ifdef DEBUG_REMAP if (channelUsed[j]) debug(1, " Unmapping song %d, channel %d", songIndex, j); #endif (void)songIndex; } } } // Now reshuffle the channels on the device. // First, set up any dontRemap channels for (int i = 0; i < 16; ++i) { if (!map->_map[i]._song || !map->_map[i]._song->pMidiParser || !map->_dontRemap[i]) continue; songIndex = -1; for (auto &song : _playList) { songIndex++; if (map->_map[i]._song == song) break; } _channelMap[i] = map->_map[i]; map->_map[i]._song = nullptr; // mark as done // If this channel was not yet mapped to the device, reset it if (currentMap[i] != _channelMap[i]) { #ifdef DEBUG_REMAP debug(1, " Mapping (dontRemap) song %d, channel %d to device channel %d", songIndex, _channelMap[i]._channel, i); #endif resetDeviceChannel(i, mainThread); if (mainThread) _channelMap[i]._song->pMidiParser->mainThreadBegin(); _channelMap[i]._song->pMidiParser->remapChannel(_channelMap[i]._channel, i); if (mainThread) _channelMap[i]._song->pMidiParser->mainThreadEnd(); } } // Next, we look for channels which were already playing. // We keep those on the same device channel as before. for (int i = 0; i < 16; ++i) { if (!map->_map[i]._song) continue; songIndex = -1; for (auto &song : _playList) { songIndex++; if (map->_map[i]._song == song) break; } for (int j = 0; j < 16; ++j) { if (map->_map[i] == currentMap[j]) { // found it _channelMap[j] = map->_map[i]; map->_map[i]._song = nullptr; // mark as done #ifdef DEBUG_REMAP debug(1, " Keeping song %d, channel %d on device channel %d", songIndex, _channelMap[j]._channel, j); #endif break; } } } // Then, remap the rest. for (int i = 0; i < 16; ++i) { if (!map->_map[i]._song || !map->_map[i]._song->pMidiParser) continue; songIndex = -1; for (auto &song : _playList) { songIndex++; if (map->_map[i]._song == song) break; } for (int j = _driverLastChannel; j >= _driverFirstChannel; --j) { if (_channelMap[j]._song == nullptr) { _channelMap[j] = map->_map[i]; map->_map[i]._song = nullptr; #ifdef DEBUG_REMAP debug(1, " Mapping song %d, channel %d to device channel %d", songIndex, _channelMap[j]._channel, j); #endif if (mainThread) _channelMap[j]._song->pMidiParser->mainThreadBegin(); _channelMap[j]._song->pMidiParser->remapChannel(_channelMap[j]._channel, j); if (mainThread) _channelMap[j]._song->pMidiParser->mainThreadEnd(); break; } } } // And finally, stop any empty channels for (int i = _driverLastChannel; i >= _driverFirstChannel; --i) { if (!_channelMap[i]._song && currentMap[i]._song) resetDeviceChannel(i, mainThread); } delete map; } ChannelRemapping *SciMusic::determineChannelMap() { #ifdef DEBUG_REMAP debug(1, "Remap: avail chans: %d-%d", _driverFirstChannel, _driverLastChannel); #endif ChannelRemapping *map = new ChannelRemapping; ChannelRemapping backupMap; map->clear(); map->_freeVoices = _pMidiDrv->getPolyphony(); if (_playList.empty()) return map; // Set reverb, either from first song, or from global (verified with KQ5 floppy // and LSL6 interpreters, fixes bug # 11683 ("QFG2 - Heavy reverb from city street sounds..."). int8 reverb = _playList.front()->reverb; _pMidiDrv->setReverb(reverb == 127 ? _globalReverb : reverb); int songIndex = -1; for (auto &song : _playList) { songIndex++; if (song->status != kSoundPlaying) continue; // If song is digital, skip. // CHECKME: Is this condition correct? if (!song->pMidiParser) { #ifdef DEBUG_REMAP debug(1, " Song %d (%p), digital?", songIndex, (void*)song); #endif continue; } #ifdef DEBUG_REMAP const char* name = g_sci->getEngineState()->_segMan->getObjectName(song->soundObj); debug(1, " Song %d (%p) [%s], prio %d%s", songIndex, (void*)song, name, song->priority, song->playBed ? ", bed" : ""); #endif // Store backup. If we fail to map this song, we will revert to this. backupMap = *map; bool songMapped = true; for (int i = 0; i < 16; ++i) { int c = song->_usedChannels[i]; if (c == 0xFF || c == 0xFE || c == 0x0F) continue; const MusicEntryChannel &channel = song->_chan[c]; if (channel._dontMap) { #ifdef DEBUG_REMAP debug(1, " Channel %d dontMap, skipping", c); #endif continue; } if (channel._mute) { #ifdef DEBUG_REMAP debug(1, " Channel %d muted, skipping", c); #endif continue; } bool dontRemap = channel._dontRemap || song->playBed; #ifdef DEBUG_REMAP debug(1, " Channel %d: prio %d, %d voice%s%s", c, channel._prio, channel._voices, channel._voices == 1 ? "" : "s", dontRemap ? ", dontRemap" : "" ); #endif DeviceChannelUsage dc = { song, c }; // our target int devChannel = -1; if (dontRemap && map->_map[c]._song == nullptr) { // unremappable channel, with channel still free devChannel = c; } // try to find a free channel if (devChannel == -1) { for (int j = 0; j < 16; ++j) { if (map->_map[j] == dc) { // already mapped?! (Can this happen?) devChannel = j; break; } if (map->_map[j]._song) continue; if (j >= _driverFirstChannel && j <= _driverLastChannel) devChannel = j; } } int prio = channel._prio; if (prio > 0) { // prio > 0 means non-essential prio = (16 - prio) + 16*songIndex; } if (devChannel == -1 && prio > 0) { // no empty channel, but this isn't an essential channel, // so we just skip it. #ifdef DEBUG_REMAP debug(1, " skipping non-essential"); #endif continue; } // try to empty a previous channel if this is an essential channel if (devChannel == -1) { devChannel = map->lowestPrio(); if (devChannel != -1) map->evict(devChannel); } if (devChannel == -1) { // failed to map this song. #ifdef DEBUG_REMAP debug(1, " no free (or lower priority) channel found"); #endif songMapped = false; break; } if (map->_map[devChannel] == dc) { // already mapped?! (Can this happen?) continue; } int neededVoices = channel._voices; // do we have enough free voices? if (map->_freeVoices < neededVoices) { // We only care for essential channels. // Note: In early SCI1 interpreters, a song started by 'playBed' // would not be skipped even if some channels couldn't be // mapped due to voice limits. So, we treat all channels as // non-essential here for playBed songs. if (prio > 0 || (song->playBed && DgdsEngine::getInstance()->getGameId() == GID_DRAGON)) { #ifdef DEBUG_REMAP debug(1, " not enough voices; need %d, have %d. Skipping this channel.", neededVoices, map->_freeVoices); #endif continue; } do { int j = map->lowestPrio(); if (j == -1) { #ifdef DEBUG_REMAP debug(1, " not enough voices; need %d, have %d", neededVoices, map->_freeVoices); #endif // failed to free enough voices. songMapped = false; break; } #ifdef DEBUG_REMAP debug(1, " creating room for voices; evict %d", j); #endif map->evict(j); } while (map->_freeVoices < neededVoices); if (!songMapped) { // failed to map this song. break; } } // We have a channel and enough free voices now. #ifdef DEBUG_REMAP debug(1, " trying to map to %d", devChannel); #endif map->_map[devChannel] = dc; map->_voices[devChannel] = neededVoices; map->_prio[devChannel] = prio; map->_dontRemap[devChannel] = dontRemap; map->_freeVoices -= neededVoices; if (!dontRemap || devChannel == c) { // If this channel fits here, we're done. #ifdef DEBUG_REMAP debug(1, " OK"); #endif continue; } // If this channel can't be remapped, we need to move it or fail. if (!map->_dontRemap[c]) { // Target channel can be remapped, so just swap map->swap(devChannel, c); continue; } #ifdef DEBUG_REMAP debug(1, " but %d is already dontRemap", c); #endif if (prio > 0) { // Channel collision, but this channel is non-essential, // so drop it. // TODO: Maybe we should have checked this before making room? map->evict(devChannel); continue; } if (map->_prio[c] > 0) { // Channel collision, but the other channel is non-essential, // so we take its place. map->evict(c); map->swap(devChannel, c); continue; } // Otherwise, we have two essential channels claiming the same // device channel. songMapped = false; break; } if (!songMapped) { // We failed to map this song, so unmap all its channels. #ifdef DEBUG_REMAP debug(1, " Failed song"); #endif *map = backupMap; } } return map; } bool SciMusic::isDeviceChannelMapped(int devChannel) const { return _channelMap[devChannel]._song; } void SciMusic::resetDeviceChannel(int devChannel, bool mainThread) { assert(devChannel >= 0 && devChannel <= 0x0F); if (mainThread) { putMidiCommandInQueue(0x0040B0 | devChannel); // sustain off putMidiCommandInQueue(0x007BB0 | devChannel); // notes off putMidiCommandInQueue(0x004BB0 | devChannel); // release voices } else { _pMidiDrv->send(0x0040B0 | devChannel); // sustain off _pMidiDrv->send(0x007BB0 | devChannel); // notes off _pMidiDrv->send(0x004BB0 | devChannel); // release voices } } bool SciMusic::isDigitalSamplePlaying() const { return _currentlyPlayingSample != nullptr && _pMixer->isSoundHandleActive(_currentlyPlayingSample->hCurrentAud); } } // End of namespace Dgds