From 857d2e7beff46fcd5d26e8590c9891a4959b8d1f Mon Sep 17 00:00:00 2001 From: Willem Jan Palenstijn Date: Sun, 14 Apr 2013 01:32:37 +0200 Subject: SCI: Rewrite MIDI channel remapping This adds MIDI state tracking to allow channels to be temporarily unmapped and later re-mapped when there are free device channels available again. --- engines/sci/sound/midiparser_sci.cpp | 208 ++++++++++--- engines/sci/sound/midiparser_sci.h | 20 +- engines/sci/sound/music.cpp | 561 +++++++++++++++++++++++++++++++---- engines/sci/sound/music.h | 44 ++- 4 files changed, 718 insertions(+), 115 deletions(-) (limited to 'engines/sci/sound') diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp index 186fc18a5c..6f250e0a3a 100644 --- a/engines/sci/sound/midiparser_sci.cpp +++ b/engines/sci/sound/midiparser_sci.cpp @@ -58,6 +58,10 @@ MidiParser_SCI::MidiParser_SCI(SciVersion soundVersion, SciMusic *music) : _resetOnPause = false; _pSnd = 0; + + _mainThreadCalled = false; + + resetStateTracking(); } MidiParser_SCI::~MidiParser_SCI() { @@ -68,10 +72,12 @@ MidiParser_SCI::~MidiParser_SCI() { } void MidiParser_SCI::mainThreadBegin() { + assert(!_mainThreadCalled); _mainThreadCalled = true; } void MidiParser_SCI::mainThreadEnd() { + assert(_mainThreadCalled); _mainThreadCalled = false; } @@ -83,12 +89,21 @@ bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, in for (int i = 0; i < 16; i++) { _channelUsed[i] = false; - _channelRemap[i] = -1; _channelMuted[i] = false; _channelVolume[i] = 127; + + if (_soundVersion <= SCI_VERSION_0_LATE) + _channelRemap[i] = i; + else + _channelRemap[i] = -1; } - _channelRemap[9] = 9; // never map channel 9, because that's used for percussion - _channelRemap[15] = 15; // never map channel 15, because thats used by sierra internally + + // FIXME: SSCI does not always start playing a track at the first byte. + // By default it skips 10 (or 13?) bytes containing prio/voices, patch, + // volume, pan commands in fixed locations, and possibly a signal + // in channel 15. We should initialize state tracking to those values + // so that they automatically get set up properly when the channels get + // mapped. See also the related FIXME in MidiParser_SCI::processEvent. if (channelFilterMask) { // SCI0 only has 1 data stream, but we need to filter out channels depending on music hardware selection @@ -314,31 +329,26 @@ byte *MidiParser_SCI::midiFilterChannels(int channelMask) { return _mixedData; } -// This will get called right before actual playing and will try to own the used channels -void MidiParser_SCI::tryToOwnChannels() { - // We don't have SciMusic in case debug command show_instruments is used - if (!_music) - return; - for (int curChannel = 0; curChannel < 15; curChannel++) { - if (_channelUsed[curChannel]) { - if (_channelRemap[curChannel] == -1) { - _channelRemap[curChannel] = _music->tryToOwnChannel(_pSnd, curChannel); - } - } - } -} +void MidiParser_SCI::resetStateTracking() { + for (int i = 0; i < 16; ++i) { + ChannelState &s = _channelState[i]; + s._modWheel = 0; + s._pan = 64; + s._patch = 0; // TODO: Initialize properly (from data in LoadMusic?) + s._note = -1; + s._sustain = false; + s._pitchWheel = 0x2000; + s._voices = 0; -void MidiParser_SCI::lostChannels() { - for (int curChannel = 0; curChannel < 15; curChannel++) - if ((_channelUsed[curChannel]) && (curChannel != 9)) - _channelRemap[curChannel] = -1; + _channelVolume[i] = 127; + } } void MidiParser_SCI::sendInitCommands() { - // reset our "global" volume and channel volumes + resetStateTracking(); + + // reset our "global" volume _volume = 127; - for (int i = 0; i < 16; i++) - _channelVolume[i] = 127; // Set initial voice count if (_pSnd) { @@ -390,53 +400,119 @@ void MidiParser_SCI::sendFromScriptToDriver(uint32 midi) { // this happens for cmdSendMidi at least in sq1vga right at the start, it's a script issue return; } - if (_channelRemap[midiChannel] == -1) { - // trying to send to an unmapped channel - // this happens for cmdSendMidi at least in sq1vga right at the start, scripts are pausing the sound - // and then sending manually. it's a script issue - return; - } sendToDriver(midi); } void MidiParser_SCI::sendToDriver(uint32 midi) { - byte midiChannel = midi & 0xf; - - if ((midi & 0xFFF0) == 0x4EB0) { - // this is channel mute only for sci1 - // it's velocity control for sci0 - if (_soundVersion >= SCI_VERSION_1_EARLY) { - _channelMuted[midiChannel] = midi & 0xFF0000 ? true : false; - return; // don't send this to driver at all - } - } + // State tracking + trackState(midi); - // Is channel muted? if so, don't send command - if (_channelMuted[midiChannel]) + if ((midi & 0xFFF0) == 0x4EB0 && _soundVersion >= SCI_VERSION_1_EARLY) { + // Mute. Handled in trackState(). + // CHECKME: Should we send this on to the driver? return; + } if ((midi & 0xFFF0) == 0x07B0) { // someone trying to set channel volume? int channelVolume = (midi >> 16) & 0xFF; - // Remember, if we need to set it ourselves - _channelVolume[midiChannel] = channelVolume; // Adjust volume accordingly to current local volume channelVolume = channelVolume * _volume / 127; - midi = (midi & 0xFFF0) | ((channelVolume & 0xFF) << 16); + midi = (midi & 0xFFFF) | ((channelVolume & 0xFF) << 16); } + // Channel remapping + byte midiChannel = midi & 0xf; int16 realChannel = _channelRemap[midiChannel]; if (realChannel == -1) return; midi = (midi & 0xFFFFFFF0) | realChannel; + sendToDriver_raw(midi); +} + +void MidiParser_SCI::sendToDriver_raw(uint32 midi) { if (_mainThreadCalled) _music->putMidiCommandInQueue(midi); else _driver->send(midi); } +void MidiParser_SCI::trackState(uint32 b) { + // We keep track of most of the state of a midi channel, so we can + // at any time reset the device to the current state, even if the + // channel has been temporarily disabled due to remapping. + + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0x7f; + byte op2 = (b >> 16) & 0x7f; + + ChannelState &s = _channelState[channel]; + + switch (command) { + case 0x90: + if (op2 != 0) { + // note on + s._note = op1; + break; + } + // else, fall-through + case 0x80: + // note off + if (s._note == op1) + s._note = -1; + break; + case 0xB0: + // control change + switch (op1) { + case 0x01: // mod wheel + s._modWheel = op2; + break; + case 0x07: // channel volume + _channelVolume[channel] = op2; + break; + case 0x0A: // pan + s._pan = op2; + break; + case 0x40: // sustain + s._sustain = (op2 != 0); + break; + case 0x4B: // voices + s._voices = op2; + _pSnd->_chan[channel]._voices = op2; // Also sync our MusicEntry + break; + case 0x4E: // mute + // This is channel mute only for sci1. + // (It's velocity control for sci0, but we don't need state in sci0) + if (_soundVersion >= SCI_VERSION_1_EARLY) { + // FIXME: mute is a level, not a bool, in some SCI versions + bool m = op2; + if (_pSnd->_chan[channel]._mute != m) { + _pSnd->_chan[channel]._mute = m; + // TODO: If muting/unmuting a channel, remap channels. + warning("Mute change without immediate remapping (mainThread = %d)", _mainThreadCalled); + } + } + break; + default: + break; + } + break; + case 0xC0: + // program change + s._patch = op1; + break; + case 0xE0: + // pitchwheel + s._pitchWheel = (op2 << 7) | op1; + break; + default: + break; + } +} + void MidiParser_SCI::parseNextEvent(EventInfo &info) { info.start = _position._playPos; info.delta = 0; @@ -477,8 +553,10 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) { case 0xE: info.basic.param1 = *(_position._playPos++); info.basic.param2 = *(_position._playPos++); - if (info.command() == 0x9 && info.basic.param2 == 0) + if (info.command() == 0x9 && info.basic.param2 == 0) { + // NoteOn with param2==0 is a NoteOff info.event = info.channel() | 0x80; + } info.length = 0; break; @@ -809,4 +887,44 @@ void MidiParser_SCI::setVolume(byte volume) { } } +void MidiParser_SCI::remapChannel(int channel, int devChannel) { + if (_channelRemap[channel] == devChannel) + return; + + _channelRemap[channel] = devChannel; + + if (devChannel == -1) + return; + +// debug(" restoring state: channel %d on devChannel %d", channel, devChannel); + + // restore state + ChannelState &s = _channelState[channel]; + + int channelVolume = _channelVolume[channel]; + channelVolume = (channelVolume * _volume / 127) & 0xFF; + byte pitch1 = s._pitchWheel & 0x7F; + byte pitch2 = (s._pitchWheel >> 7) & 0x7F; + + sendToDriver_raw(0x0040B0 | devChannel); // sustain off + sendToDriver_raw(0x004BB0 | devChannel | (s._voices << 16)); + sendToDriver_raw(0x0000C0 | devChannel | (s._patch << 8)); + sendToDriver_raw(0x0007B0 | devChannel | (channelVolume << 16)); + sendToDriver_raw(0x000AB0 | devChannel | (s._pan << 16)); + sendToDriver_raw(0x0001B0 | devChannel | (s._modWheel << 16)); + sendToDriver_raw(0x0040B0 | devChannel | (s._sustain ? 0x7F0000 : 0)); + sendToDriver_raw(0x0000E0 | devChannel | (pitch1 << 8) | (pitch2 << 16)); + + // CHECKME: Some SSCI version send a control change 0x4E with s._note as + // parameter. + // We need to investigate how (and if) drivers should act on this. + // Related: controller 0x4E is used for 'mute' in the midiparser. + // This could be a bug in SSCI that went unnoticed because few (or no?) + // drivers implement controller 0x4E + + // NB: The line below is _not_ valid since s._note can be 0xFF. + // SSCI handles this out of band in the driver interface. + // sendToDriver_raw(0x004EB0 | devChannel | (s._note << 16); +} + } // End of namespace Sci diff --git a/engines/sci/sound/midiparser_sci.h b/engines/sci/sound/midiparser_sci.h index 5784dca1ab..7e24c34144 100644 --- a/engines/sci/sound/midiparser_sci.h +++ b/engines/sci/sound/midiparser_sci.h @@ -79,20 +79,23 @@ public: const byte *getMixedData() const { return _mixedData; } byte getSongReverb(); - void tryToOwnChannels(); - void lostChannels(); void sendFromScriptToDriver(uint32 midi); void sendToDriver(uint32 midi); void sendToDriver(byte status, byte firstOp, byte secondOp) { sendToDriver(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16)); } + void remapChannel(int channel, int devChannel); + protected: void parseNextEvent(EventInfo &info); void processEvent(const EventInfo &info, bool fireEvents = true); byte *midiMixChannels(); byte *midiFilterChannels(int channelMask); byte midiGetNextChannel(long ticker); + void resetStateTracking(); + void trackState(uint32 midi); + void sendToDriver_raw(uint32 midi); SciMusic *_music; @@ -113,6 +116,19 @@ protected: int16 _channelRemap[16]; bool _channelMuted[16]; byte _channelVolume[16]; + + struct ChannelState { + int8 _modWheel; + int8 _pan; + int8 _patch; + int8 _note; + bool _sustain; + int16 _pitchWheel; + int8 _voices; + }; + + ChannelState _channelState[16]; + }; } // End of namespace Sci diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index 8c6d0d6431..e29afe706b 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -33,7 +33,7 @@ #include "sci/sound/midiparser_sci.h" #include "sci/sound/music.h" -//#define DISABLE_REMAPPING +//#define DEBUG_REMAP namespace Sci { @@ -47,6 +47,8 @@ SciMusic::SciMusic(SciVersion soundVersion, bool useDigitalSFX) for (int i = 0; i < 16; i++) { _usedChannel[i] = 0; _channelRemap[i] = -1; + _channelMap[i]._song = 0; + _channelMap[i]._channel = -1; } _queuedCommands.reserve(1000); @@ -291,6 +293,15 @@ void SciMusic::sortPlayList() { } 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 = 0; + _channelMap[i]._channel = -1; + } + } + int channelFilterMask = 0; SoundResource::Track *track = pSnd->soundRes->getTrackByType(_pMidiDrv->getPlayId()); @@ -337,6 +348,27 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) { // 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) { + SoundResource::Channel &chan = track->channels[i]; + + 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; + + // 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, @@ -358,64 +390,6 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) { } } -// This one checks, if requested channel is available -> in that case give -// caller that channel. Otherwise look for an unused one -int16 SciMusic::tryToOwnChannel(MusicEntry *caller, int16 bestChannel) { -#ifdef DISABLE_REMAPPING - return bestChannel; -#endif - - // Don't even try this for SCI0 - if (_soundVersion <= SCI_VERSION_0_LATE) - return bestChannel; - if (!_usedChannel[bestChannel]) { - // currently unused, so give it to caller directly - _usedChannel[bestChannel] = caller; - _channelRemap[bestChannel] = bestChannel; - return bestChannel; - } - // otherwise look for unused channel - for (int channelNr = _driverFirstChannel; channelNr < 15; channelNr++) { - if (channelNr == 9) // never map to channel 9 (percussion) - continue; - if (!_usedChannel[channelNr]) { - _usedChannel[channelNr] = caller; - _channelRemap[bestChannel] = channelNr; - return channelNr; - } - } - // nothing found, don't map channel at all - // sierra did this as well, although i'm not sure if we act exactly the same way - // maybe they removed channels from previous playing music - return -1; -} - -void SciMusic::freeChannels(MusicEntry *caller) { - // Remove used channels - for (int i = 0; i < 15; i++) { - if (_usedChannel[i] == caller) { - if (_channelRemap[i] != -1) { - // athrxx: The original handles this differently. It seems to be checking for (and effecting) necessary - // remaps / resets etc. more or less all the time. There are several more tables to keep track of everything. - // I don't know whether all of that is needed and to which SCI versions it applies, though. - // At least it is necessary to release the allocated channels inside the driver. Otherwise these channels - // won't be available any more (e.g. after half of the KQ5 FM-Towns intro there will be no more music - // since the driver can't pick up any more channels). The channels also have to be reset to - // default values, since the original does the same (although in a different manny) and the music will be wrong - // otherwise (at least KQ5 FM-Towns). - - sendMidiCommand(0x4000e0 | _channelRemap[i]); // Reset pitch wheel - sendMidiCommand(0x0040b0 | _channelRemap[i]); // Release pedal - sendMidiCommand(0x004bb0 | _channelRemap[i]); // Release assigned driver channels - } - _usedChannel[i] = 0; - _channelRemap[i] = -1; - } - } - // Also tell midiparser, that he lost ownership - caller->pMidiParser->lostChannels(); -} - void SciMusic::soundPlay(MusicEntry *pSnd) { _mutex.lock(); @@ -495,14 +469,13 @@ void SciMusic::soundPlay(MusicEntry *pSnd) { if (_soundVersion <= SCI_VERSION_0_LATE) _playList[i]->isQueued = false; _playList[i]->pMidiParser->stop(); - freeChannels(_playList[i]); + remapChannels(); _playList[i]->fadeStep = 0; _playList[i]->fadeCompleted = true; } } } - pSnd->pMidiParser->tryToOwnChannels(); if (pSnd->status != kSoundPaused) pSnd->pMidiParser->sendInitCommands(); pSnd->pMidiParser->setVolume(pSnd->volume); @@ -532,6 +505,10 @@ void SciMusic::soundPlay(MusicEntry *pSnd) { } pSnd->status = kSoundPlaying; + + _mutex.lock(); + remapChannels(); + _mutex.unlock(); } void SciMusic::soundStop(MusicEntry *pSnd) { @@ -549,8 +526,8 @@ void SciMusic::soundStop(MusicEntry *pSnd) { // allNotesOff() again if (previousStatus == kSoundPlaying) pSnd->pMidiParser->stop(); - freeChannels(pSnd); pSnd->pMidiParser->mainThreadEnd(); + remapChannels(); } pSnd->fadeStep = 0; // end fading, if fading was in progress @@ -586,8 +563,10 @@ void SciMusic::soundSetPriority(MusicEntry *pSnd, byte prio) { void SciMusic::soundKill(MusicEntry *pSnd) { pSnd->status = kSoundStopped; + _mutex.lock(); + remapChannels(); + if (pSnd->pMidiParser) { - Common::StackLock lock(_mutex); pSnd->pMidiParser->mainThreadBegin(); pSnd->pMidiParser->unloadMusic(); pSnd->pMidiParser->mainThreadEnd(); @@ -595,6 +574,8 @@ void SciMusic::soundKill(MusicEntry *pSnd) { pSnd->pMidiParser = NULL; } + _mutex.unlock(); + if (pSnd->pStreamAud) { _pMixer->stopHandle(pSnd->hCurrentAud); delete pSnd->pStreamAud; @@ -603,7 +584,7 @@ void SciMusic::soundKill(MusicEntry *pSnd) { pSnd->pLoopStream = 0; } - Common::StackLock lock(_mutex); + _mutex.lock(); uint sz = _playList.size(), i; // Remove sound from playlist for (i = 0; i < sz; i++) { @@ -614,6 +595,7 @@ void SciMusic::soundKill(MusicEntry *pSnd) { break; } } + _mutex.unlock(); } void SciMusic::soundPause(MusicEntry *pSnd) { @@ -639,8 +621,8 @@ void SciMusic::soundPause(MusicEntry *pSnd) { Common::StackLock lock(_mutex); pSnd->pMidiParser->mainThreadBegin(); pSnd->pMidiParser->pause(); - freeChannels(pSnd); pSnd->pMidiParser->mainThreadEnd(); + remapChannels(); } } } @@ -789,6 +771,14 @@ MusicEntry::MusicEntry() { pStreamAud = 0; pLoopStream = 0; pMidiParser = 0; + + 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() { @@ -857,4 +847,445 @@ void MusicEntry::setSignal(int 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 = 0; + _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 = 0; + _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() { + if (_soundVersion <= SCI_VERSION_0_LATE) + return; + + // NB: This function should only be called from the main thread, + // with _mutex locked + + + 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 = 0; + _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]) { + song->pMidiParser->mainThreadBegin(); + song->pMidiParser->remapChannel(j, -1); + 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 = 0; // 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 + _channelMap[i]._song->pMidiParser->mainThreadBegin(); + _channelMap[i]._song->pMidiParser->remapChannel(_channelMap[i]._channel, i); + _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 = 0; // 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 == 0) { + _channelMap[j] = map->_map[i]; + map->_map[i]._song = 0; +#ifdef DEBUG_REMAP + debug(" Mapping song %d, channel %d to device channel %d", songIndex, _channelMap[j]._channel, j); +#endif + _channelMap[j]._song->pMidiParser->mainThreadBegin(); + _channelMap[j]._song->pMidiParser->remapChannel(_channelMap[j]._channel, j); + _channelMap[j]._song->pMidiParser->mainThreadEnd(); + break; + } + } + + } + + // And finally, stop any empty channels + for (int i = _driverFirstChannel; i <= _driverLastChannel; ++i) { + if (!_channelMap[i]._song) + resetDeviceChannel(i); + } +} + + +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 + debug(" Song %d (%p), prio %d", songIndex, (void*)song, song->priority); +#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) + continue; + if (channel._mute) + continue; + +#ifdef DEBUG_REMAP + debug(" Channel %d: prio %d, %d voice%s%s", c, channel._prio, channel._voices, channel._voices == 1 ? "" : "s", channel._dontRemap ? ", dontRemap" : "" ); +#endif + + DeviceChannelUsage dc = { song, c }; + + // our target + int devChannel = -1; + + if (channel._dontRemap && map->_map[c]._song == 0) { + // 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? + // We only care for essential channels + if (map->_freeVoices < neededVoices && prio > 0) { + 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] = channel._dontRemap; + map->_freeVoices -= neededVoices; + + if (!channel._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) { + // NB: This function should only be called from the main thread + + assert(devChannel >= 0 && devChannel <= 0x0F); + + putMidiCommandInQueue(0x0040B0 | devChannel); // sustain off + putMidiCommandInQueue(0x007BB0 | devChannel); // notes off + putMidiCommandInQueue(0x004BB0 | devChannel); // release voices +} + + + } // End of namespace Sci diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h index 40236c8445..23a072cb26 100644 --- a/engines/sci/sound/music.h +++ b/engines/sci/sound/music.h @@ -52,6 +52,17 @@ class SegManager; typedef Common::Array SignalQueue; + +struct MusicEntryChannel { + // Channel info + int8 _prio; // 0 = essential; lower is higher priority + int8 _voices; + bool _dontRemap; + bool _dontMap; + bool _mute; +}; + + class MusicEntry : public Common::Serializable { public: // Do not get these directly for the sound objects! @@ -90,6 +101,8 @@ public: Audio::Mixer::SoundType soundType; + int _usedChannels[16]; + MusicEntryChannel _chan[16]; MidiParser_SCI *pMidiParser; // this is used for storing signals, when the current signal is not yet @@ -114,6 +127,27 @@ public: virtual void saveLoadWithSerializer(Common::Serializer &ser); }; +struct DeviceChannelUsage { + MusicEntry *_song; + int _channel; + bool operator==(const DeviceChannelUsage& other) const { return _song == other._song && _channel == other._channel; } + bool operator!=(const DeviceChannelUsage& other) const { return !(*this == other); } +}; + +struct ChannelRemapping { + DeviceChannelUsage _map[16]; + int _prio[16]; + int _voices[16]; + bool _dontRemap[16]; + int _freeVoices; + + void clear(); + void swap(int i, int j); + void evict(int i); + ChannelRemapping& operator=(ChannelRemapping& other); + int lowestPrio() const; +}; + typedef Common::Array MusicList; typedef Common::Array MidiCommandQueue; @@ -198,9 +232,6 @@ public: // where a deadlock can occur Common::Mutex _mutex; - int16 tryToOwnChannel(MusicEntry *caller, int16 bestChannel); - void freeChannels(MusicEntry *caller); - protected: void sortPlayList(); @@ -213,6 +244,11 @@ protected: // If true and a sound has a digital track, the sound from the AdLib track is played bool _useDigitalSFX; + // remapping: + void remapChannels(); + ChannelRemapping *determineChannelMap(); + void resetDeviceChannel(int devChannel); + private: MusicList _playList; bool _soundOn; @@ -221,6 +257,8 @@ private: int8 _channelRemap[16]; int8 _globalReverb; + DeviceChannelUsage _channelMap[16]; + MidiCommandQueue _queuedCommands; MusicType _musicType; -- cgit v1.2.3