scummvm/engines/sci/sound/music.cpp
athrxx 8eba7fb4fb SCI: fix bug no. 13496 (LSL6 music plays all instruments / tracks at …
…once)

I am not sure if this bug ticket is about just one or about several bugs. This is at least something I could reproduce. The reason is that there can be channels that should start up muted. We didn't support that. I also fixed a couple of other things about the mute state that I noticed.
2022-06-05 09:10:59 +03:00

1553 lines
47 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 "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "common/config-manager.h"
#include "common/translation.h"
#include "gui/error.h"
#include "sci/sci.h"
#include "sci/console.h"
#include "sci/resource/resource.h"
#include "sci/engine/features.h"
#include "sci/engine/kernel.h"
#include "sci/engine/state.h"
#include "sci/sound/midiparser_sci.h"
#include "sci/sound/music.h"
//#define DEBUG_REMAP
namespace Sci {
SciMusic::SciMusic(SciVersion soundVersion, bool useDigitalSFX)
: _mutex(g_system->getMixer()->mutex()), _soundVersion(soundVersion), _soundOn(true), _masterVolume(15), _globalReverb(0), _useDigitalSFX(useDigitalSFX), _needsResume(soundVersion > SCI_VERSION_0_LATE), _globalPause(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() {
if (_pMidiDrv) {
_pMidiDrv->close();
delete _pMidiDrv;
}
}
void SciMusic::init() {
// system init
_pMixer = g_system->getMixer();
// SCI sound init
_dwTempo = 0;
const Common::Platform platform = g_sci->getPlatform();
uint32 deviceFlags;
if (g_sci->_features->generalMidiOnly()) {
deviceFlags = MDT_MIDI;
} else {
deviceFlags = MDT_PCSPK | MDT_PCJR | MDT_ADLIB | MDT_MIDI;
}
// Default to MIDI for Windows versions of SCI1.1 games, as their
// soundtrack is written for GM.
if (g_sci->_features->useAltWinGMSound())
deviceFlags |= MDT_PREFER_GM;
// SCI_VERSION_0_EARLY games apparently don't support the CMS. At least there
// is no patch resource 101 and I also haven't seen any CMS driver file so far.
if (getSciVersion() > SCI_VERSION_0_EARLY && getSciVersion() <= SCI_VERSION_1_1)
deviceFlags |= MDT_CMS;
if (platform == Common::kPlatformFMTowns) {
if (getSciVersion() > SCI_VERSION_1_EARLY)
deviceFlags = MDT_TOWNS;
else
deviceFlags |= MDT_TOWNS;
}
if (platform == Common::kPlatformPC98)
deviceFlags |= MDT_PC98;
uint32 dev = MidiDriver::detectDevice(deviceFlags);
_musicType = MidiDriver::getMusicType(dev);
if (g_sci->_features->useAltWinGMSound() && _musicType != MT_GM) {
warning("A Windows CD version with an alternate MIDI soundtrack has been chosen, "
"but no MIDI music device has been selected. Reverting to the DOS soundtrack");
g_sci->_features->forceDOSTracks();
#ifdef ENABLE_SCI32
} else if (g_sci->_features->generalMidiOnly() && _musicType != MT_GM) {
warning("This game only supports General MIDI, but a non-GM device has "
"been selected. Some music may be wrong or missing");
#endif
}
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) {
if (getSciVersion() <= SCI_VERSION_0_LATE)
_pMidiDrv = MidiPlayer_AmigaMac0_create(_soundVersion, platform);
else
_pMidiDrv = MidiPlayer_AmigaMac1_create(_soundVersion, platform);
} else
_pMidiDrv = MidiPlayer_AdLib_create(_soundVersion);
break;
case MT_PCJR:
_pMidiDrv = MidiPlayer_PCJr_create(_soundVersion);
break;
case MT_PCSPK:
_pMidiDrv = MidiPlayer_PCSpeaker_create(_soundVersion);
break;
case MT_CMS:
_pMidiDrv = MidiPlayer_CMS_create(_soundVersion);
break;
case MT_TOWNS:
_pMidiDrv = MidiPlayer_FMTowns_create(_soundVersion);
break;
case MT_PC98:
_pMidiDrv = MidiPlayer_PC9801_create(_soundVersion);
break;
default:
if (ConfMan.getInt("midi_mode") == kMidiModeFB01
|| (ConfMan.hasKey("native_fb01") && ConfMan.getBool("native_fb01")))
_pMidiDrv = MidiPlayer_Fb01_create(_soundVersion);
else
_pMidiDrv = MidiPlayer_Midi_create(_soundVersion);
}
if (_pMidiDrv && !_pMidiDrv->open()) {
_pMidiDrv->setTimerCallback(this, &miditimerCallback);
_dwTempo = _pMidiDrv->getBaseTempo();
} else {
if (g_sci->getGameId() == GID_FUNSEEKER ||
(g_sci->getGameId() == GID_GK2 && g_sci->isDemo())) {
// Disable checks for required audio drivers in certain demos
// which contain no sound - in this case, we can proceed
// without actually initializing the MIDI driver.
} else {
const char *missingFiles = _pMidiDrv->reportMissingFiles();
if (missingFiles) {
Common::U32String message = _(
"The selected audio driver requires the following file(s):\n\n"
);
message += Common::U32String(missingFiles);
message += _("\n\n"
"Some audio drivers (at least for some games) were made\n"
"available by Sierra as aftermarket patches and thus might not\n"
"have been installed as part of the original game setup.\n\n"
"Please copy these file(s) into your game data directory.\n\n"
"However, please note that the file(s) might not be available\n"
"separately but only as content of (patched) resource bundles.\n"
"In that case you may need to apply the original Sierra patch.\n\n"
);
::GUI::displayErrorDialog(message);
}
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();
if (getSciVersion() <= SCI_VERSION_0_LATE)
_globalReverb = _pMidiDrv->getReverb(); // Init global reverb for SCI0
_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() {
const MusicList::iterator end = _playList.end();
// sending out queued commands that were "sent" via main thread
sendMidiCommandsFromQueue();
// remap channels, if requested
if (_needsRemap)
remapChannels(false);
_needsRemap = false;
for (MusicList::iterator i = _playList.begin(); i != end; ++i)
(*i)->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<MusicEntry*>(_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) {
const MusicList::iterator end = _playList.end();
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 (MusicList::iterator i = _playList.begin(); i != end; ++i) {
#ifdef ENABLE_SCI32
// The entire DAC will have been paused by the caller;
// do not pause the individual samples too
if (_soundVersion >= SCI_VERSION_2 && (*i)->isSample) {
continue;
}
#endif
soundToggle(*i, 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...
assert(_globalPause == 1);
_globalPause = 0;
}
void SciMusic::stopAll() {
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
soundStop(*i);
}
}
void SciMusic::stopAllSamples() {
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
if ((*i)->isSample) {
soundStop(*i);
}
}
}
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(reg_t obj) {
Common::StackLock lock(_mutex);
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
if ((*i)->soundObj == obj)
return *i;
}
return nullptr;
}
MusicEntry *SciMusic::getFirstSlotWithStatus(SoundStatus status) {
for (MusicList::iterator i = _playList.begin(); i != _playList.end(); ++i) {
if ((*i)->status == status)
return *i;
}
return nullptr;
}
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...
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
if ((*i)->status == kSoundPlaying) {
if ((*i)->reverb == 127) // Active song has no reverb
_pMidiDrv->setReverb(reverb); // Set the global reverb
break;
}
}
} else {
// Set reverb of the active song
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
if ((*i)->status == kSoundPlaying) {
_pMidiDrv->setReverb((*i)->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;
}
}
int channelFilterMask = 0;
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;
if (_soundVersion <= SCI_VERSION_0_LATE && !_useDigitalSFX) {
// For SCI0 the digital sample is present in the same track as the
// MIDI. If the user specifically requests not to use the digital
// samples, play the MIDI data instead. If the MIDI portion of the
// track is empty however, play the digital sample anyway. This is
// necessary for e.g. the "Where am I?" sample in the SQ3 intro.
playSample = false;
if (track->channelCount == 2) {
SoundResource::Channel &chan = track->channels[0];
if (chan.data.size() < 2 || chan.data[1] == SCI_MIDI_EOT) {
playSample = true;
}
}
} else
playSample = (track->digitalChannelNr != -1 && (_useDigitalSFX || track->channelCount == 1));
// Play digital sample
if (playSample) {
const SciSpan<const byte> &channelData = track->channels[track->digitalChannelNr].data;
delete pSnd->pStreamAud;
byte flags = Audio::FLAG_UNSIGNED;
// Amiga SCI1 games had signed sound data
if (_soundVersion >= SCI_VERSION_1_EARLY && g_sci->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;
if (pSnd->pMidiParser == nullptr) {
pSnd->pMidiParser = new MidiParser_SCI(_soundVersion, this);
pSnd->pMidiParser->setMidiDriver(_pMidiDrv);
pSnd->pMidiParser->setTimerRate(_dwTempo);
pSnd->pMidiParser->setMasterVolume(_masterVolume);
}
pSnd->pauseCounter = 0;
// Find out what channels to filter for SCI0
channelFilterMask = pSnd->soundRes->getChannelFilterMask(_pMidiDrv->getPlayId(), _pMidiDrv->hasRhythmChannel());
for (int i = 0; i < 16; ++i)
pSnd->_usedChannels[i] = 0xFF;
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;
pSnd->_chan[chan.number]._dontRemap = (chan.flags & 2);
pSnd->_chan[chan.number]._prio = chan.prio;
pSnd->_chan[chan.number]._voices = chan.poly;
pSnd->_chan[chan.number]._mute = (chan.flags & 4) ? 1 : 0;
// CHECKME: Some SCI versions use chan.flags & 1 for this:
pSnd->_chan[chan.number]._dontMap = false;
// 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.
}
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, channelFilterMask, _soundVersion);
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 (_soundVersion <= SCI_VERSION_1_EARLY && 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->pMidiParser) {
// Original SCI0 doesn't use this function to restore sound. The function it has
// for that will not check priorities.
if ((_soundVersion <= SCI_VERSION_0_LATE) && alreadyPlaying && !restoring) {
// Music already playing in SCI0?
if (pSnd->priority > alreadyPlaying->priority) {
// And new priority higher? pause previous music and play new one immediately.
// Example of such case: lsl3, when getting points (jingle is played then)
soundPause(alreadyPlaying);
} else {
// And new priority equal or lower? queue up music and play it afterwards done by
// SoundCommandParser::updateSci0Cues()
// Example of such case: iceman room 14
pSnd->status = kSoundPaused;
return;
}
}
}
if (pSnd->isSample) {
#ifdef ENABLE_SCI32
if (_soundVersion >= SCI_VERSION_2) {
// TODO: Sound number, loop state, and volume come from soundObj
// in SSCI. Getting them from MusicEntry could cause a bug if the
// soundObj was updated by a game script and not copied back to
// MusicEntry.
g_sci->_audio32->restart(ResourceId(kResourceTypeAudio, pSnd->resourceId), true, pSnd->loop != 0 && pSnd->loop != 1, pSnd->volume, pSnd->soundObj, false);
return;
}
#endif
if (_currentlyPlayingSample && _pMixer->isSoundHandleActive(_currentlyPlayingSample->hCurrentAud)) {
// 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();
// 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();
if (pSnd->status != kSoundPaused)
pSnd->pMidiParser->sendInitCommands();
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) {
#ifdef ENABLE_SCI32
if (_soundVersion >= SCI_VERSION_2) {
g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
} else {
#endif
if (_currentlyPlayingSample == pSnd)
_currentlyPlayingSample = nullptr;
_pMixer->stopHandle(pSnd->hCurrentAud);
#ifdef ENABLE_SCI32
}
#endif
}
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
// SSCI0 resumes the next available sound from the (priority ordered) list with a paused status.
if (_soundVersion <= SCI_VERSION_0_LATE && previousStatus == kSoundPlaying && (pSnd = getFirstSlotWithStatus(kSoundPaused)))
soundResume(pSnd);
}
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) {
#ifdef ENABLE_SCI32
if (_soundVersion >= SCI_VERSION_2) {
g_sci->_audio32->stop(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
} else {
#endif
if (_currentlyPlayingSample == pSnd) {
// Forget about this sound, in case it was currently playing
_currentlyPlayingSample = nullptr;
}
_pMixer->stopHandle(pSnd->hCurrentAud);
#ifdef ENABLE_SCI32
}
#endif
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 = (_soundVersion > SCI_VERSION_0_LATE);
if (pSnd->pStreamAud) {
_pMixer->pauseHandle(pSnd->hCurrentAud, false);
pSnd->status = kSoundPlaying;
} else {
soundPlay(pSnd, true);
}
}
void SciMusic::soundToggle(MusicEntry *pSnd, bool pause) {
#ifdef ENABLE_SCI32
if (_soundVersion >= SCI_VERSION_2_1_EARLY && pSnd->isSample) {
if (pause) {
g_sci->_audio32->pause(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
} else {
g_sci->_audio32->resume(ResourceId(kResourceTypeAudio, pSnd->resourceId), pSnd->soundObj);
}
return;
}
#endif
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);
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
if ((*i)->pMidiParser)
(*i)->pMidiParser->setMasterVolume(vol);
}
}
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 (%04x:%04x)", PRINT_REG(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];
con->debugPrintf("%d: %04x:%04x (%s), resource id: %d, status: %s, %s type\n",
i, PRINT_REG(song->soundObj),
g_sci->getEngineState()->_segMan->getObjectName(song->soundObj),
song->resourceId, musicStatus[song->status],
song->pMidiParser ? "MIDI" : "digital audio");
}
}
void SciMusic::printSongInfo(reg_t obj, Console *con) {
Common::StackLock lock(_mutex);
const char *musicStatus[] = { "Stopped", "Initialized", "Paused", "Playing" };
const MusicList::iterator end = _playList.end();
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
MusicEntry *song = *i;
if (song->soundObj == obj) {
con->debugPrintf("Resource id: %d, status: %s\n", song->resourceId, musicStatus[song->status]);
con->debugPrintf("dataInc: %d, hold: %d, loop: %d\n", song->dataInc, song->hold, song->loop);
con->debugPrintf("signal: %d, priority: %d\n", song->signal, song->priority);
con->debugPrintf("ticker: %d, volume: %d\n", song->ticker, song->volume);
if (song->pMidiParser) {
con->debugPrintf("Type: MIDI\n");
if (song->soundRes) {
SoundResource::Track *track = song->soundRes->getTrackByType(_pMidiDrv->getPlayId());
con->debugPrintf("Channels: %d\n", track->channelCount);
}
} else if (song->pStreamAud || song->pLoopStream) {
con->debugPrintf("Type: digital audio (%s), sound active: %s\n",
song->pStreamAud ? "non looping" : "looping",
_pMixer->isSoundHandleActive(song->hCurrentAud) ? "yes" : "no");
if (song->soundRes) {
con->debugPrintf("Sound resource information:\n");
SoundResource::Track *track = song->soundRes->getTrackByType(_pMidiDrv->getPlayId());
if (track && track->digitalChannelNr != -1) {
con->debugPrintf("Sample size: %d, sample rate: %d, channels: %d, digital channel number: %d\n",
track->digitalSampleSize, track->digitalSampleRate, track->channelCount, track->digitalChannelNr);
}
}
}
return;
}
}
con->debugPrintf("Song object not found in playlist");
}
MusicEntry::MusicEntry() {
soundObj = NULL_REG;
soundRes = nullptr;
resourceId = 0;
time = 0;
dataInc = 0;
ticker = 0;
signal = 0;
priority = 0;
loop = 0;
volume = MUSIC_VOLUME_DEFAULT;
hold = -1;
reverb = -1;
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]._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 || !loop)
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) {
// For SCI0, we cache the signals to set, as some songs might
// update their signal faster than kGetEvent is called (which is where
// we manually invoke kDoSoundUpdateCues for SCI0 games). SCI01 and
// newer handle signalling inside kDoSoundUpdateCues. Refer to bug #5218
if (g_sci->_features->detectDoSoundType() <= SCI_VERSION_0_LATE) {
if (!signal) {
signal = newSignal;
} else {
// signal already set and waiting for getting to scripts, queue new one
signalQueue.push_back(newSignal);
}
} else {
// 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) {
if (_soundVersion <= SCI_VERSION_0_LATE)
return;
// 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("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
const MusicList::iterator end = _playList.end();
int songIndex = -1;
for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
MusicEntry *song = *i;
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(" Unmapping song %d, channel %d", songIndex, j);
#endif
}
}
}
// 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 (MusicList::iterator iter = _playList.begin(); iter != end; ++iter) {
songIndex++;
if (map->_map[i]._song == *iter)
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(" Mapping (dontRemap) song %d, channel %d to device channel %d", songIndex, _channelMap[i]._channel, i);
#endif
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 (MusicList::iterator iter = _playList.begin(); iter != end; ++iter) {
songIndex++;
if (map->_map[i]._song == *iter)
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(" 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 (MusicList::iterator iter = _playList.begin(); iter != end; ++iter) {
songIndex++;
if (map->_map[i]._song == *iter)
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(" 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("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;
// TODO: set reverb, either from first song, or from global???
MusicList::iterator songIter;
int songIndex = -1;
for (songIter = _playList.begin(); songIter != _playList.end(); ++songIter) {
songIndex++;
MusicEntry *song = *songIter;
if (song->status != kSoundPlaying)
continue;
// If song is digital, skip.
// CHECKME: Is this condition correct?
if (!song->pMidiParser) {
#ifdef DEBUG_REMAP
debug(" Song %d (%p), digital?", songIndex, (void*)song);
#endif
continue;
}
#ifdef DEBUG_REMAP
const char* name = g_sci->getEngineState()->_segMan->getObjectName(song->soundObj);
debug(" 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(" Channel %d dontMap, skipping", c);
#endif
continue;
}
if (channel._mute) {
#ifdef DEBUG_REMAP
debug(" Channel %d muted, skipping", c);
#endif
continue;
}
bool dontRemap = channel._dontRemap || song->playBed;
#ifdef DEBUG_REMAP
debug(" 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(" 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(" 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 && _soundVersion <= SCI_VERSION_1_EARLY)) {
#ifdef DEBUG_REMAP
debug(" 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(" not enough voices; need %d, have %d", neededVoices, map->_freeVoices);
#endif
// failed to free enough voices.
songMapped = false;
break;
}
#ifdef DEBUG_REMAP
debug(" 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(" 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(" 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(" 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(" Failed song");
#endif
*map = backupMap;
}
}
return map;
}
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
}
}
} // End of namespace Sci