From 09f007fa5ed7c85eb69f40f768b06962e4e03ac8 Mon Sep 17 00:00:00 2001 From: athrxx Date: Fri, 14 Dec 2018 23:04:40 +0100 Subject: SCI: (ADL driver) - implement/fix voice mapping - Backport some code of waltervn's fork at his recommendation (taken from 97604200 and 4c3bfee5). That code has caught some bitrot and I were too lazy to manually resolve the merge conflicts. All changes have been re-checked with disasm. - Add initTrack() implementation for SCI0 EARLY/LATE - Minor cleanup - Please note that this commit does not fix any shortcomings of the SCI sound engine --- engines/sci/sound/drivers/adlib.cpp | 255 ++++++++++++++++++++++++------------ 1 file changed, 171 insertions(+), 84 deletions(-) diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp index 472f3df074..265ad71858 100644 --- a/engines/sci/sound/drivers/adlib.cpp +++ b/engines/sci/sound/drivers/adlib.cpp @@ -41,9 +41,6 @@ namespace Sci { #define STEREO true #endif -// FIXME: We don't seem to be sending the polyphony init data, so disable this for now -#define ADLIB_DISABLE_VOICE_MAPPING - class MidiDriver_AdLib : public MidiDriver { public: enum { @@ -51,14 +48,17 @@ public: kRhythmKeys = 62 }; - MidiDriver_AdLib(Audio::Mixer *mixer) :_playSwitch(true), _masterVolume(15), _rhythmKeyMap(), _opl(0), _isOpen(false) { } + MidiDriver_AdLib(SciVersion version) : _version(version), _isSCI0(version < SCI_VERSION_1_EARLY), _playSwitch(true), _masterVolume(15), + _numVoiceMax(version == SCI_VERSION_0_EARLY ? 8 : kVoices), _rhythmKeyMap(), _opl(0), _adlibTimerParam(0), _adlibTimerProc(0), _stereo(false), _isOpen(false) { } virtual ~MidiDriver_AdLib() { } // MidiDriver int open() { return -1; } // Dummy implementation (use openAdLib) - int openAdLib(bool isSCI0); + int openAdLib(); void close(); void send(uint32 b); + void initTrack(SciSpan &header); + MidiChannel *allocateChannel() { return NULL; } MidiChannel *getPercussionChannel() { return NULL; } bool isOpen() const { return _isOpen; } @@ -116,32 +116,39 @@ private: uint16 pitchWheel; // Pitch wheel setting (0-16383, 8192 is center) uint8 lastVoice; // Last voice used for this MIDI channel bool enableVelocity; // Enable velocity control (SCI0) + uint8 voices; // Number of voices currently used by this channel + uint8 mappedVoices; // Number of voices currently mapped to this channel Channel() : patch(0), volume(63), pan(64), holdPedal(0), extraVoices(0), - pitchWheel(8192), lastVoice(0), enableVelocity(false) { } + pitchWheel(8192), lastVoice(0), enableVelocity(false), voices(0), + mappedVoices(0) { } }; struct AdLibVoice { - int8 channel; // MIDI channel that this voice is assigned to or -1 + int8 channel; // MIDI channel that is currently using this voice, or -1 + int8 mappedChannel; // MIDI channel that this voice is mapped to, or -1 int8 note; // Currently playing MIDI note or -1 int patch; // Currently playing patch or -1 uint8 velocity; // Note velocity bool isSustained; // Flag indicating a note that is being sustained by the hold pedal uint16 age; // Age of the current note - AdLibVoice() : channel(-1), note(-1), patch(-1), velocity(0), isSustained(false), age(0) { } + AdLibVoice() : channel(-1), mappedChannel(-1), note(-1), patch(-1), velocity(0), isSustained(false), age(0) { } }; bool _stereo; bool _isSCI0; + SciVersion _version; OPL::OPL *_opl; bool _isOpen; bool _playSwitch; int _masterVolume; + const uint8 _numVoiceMax; Channel _channels[MIDI_CHANNELS]; AdLibVoice _voices[kVoices]; Common::SpanOwner > _rhythmKeyMap; Common::Array _patches; + Common::List _voiceQueue; Common::TimerManager::TimerProc _adlibTimerProc; void *_adlibTimerParam; @@ -158,18 +165,19 @@ private: void noteOn(int channel, int note, int velocity); void noteOff(int channel, int note); int findVoice(int channel); + int findVoiceLateSci11(int channel); void voiceMapping(int channel, int voices); void assignVoices(int channel, int voices); void releaseVoices(int channel, int voices); void donateVoices(); - int findVoiceBasic(int channel); + void queueMoveToBack(int voice); void setVelocityReg(int regOffset, int velocity, int kbScaleLevel, int pan); int calcVelocity(int voice, int op); }; class MidiPlayer_AdLib : public MidiPlayer { public: - MidiPlayer_AdLib(SciVersion soundVersion) : MidiPlayer(soundVersion) { _driver = new MidiDriver_AdLib(g_system->getMixer()); } + MidiPlayer_AdLib(SciVersion soundVersion) : MidiPlayer(soundVersion) { _driver = new MidiDriver_AdLib(soundVersion); } ~MidiPlayer_AdLib() { delete _driver; _driver = 0; @@ -183,7 +191,7 @@ public: bool hasRhythmChannel() const { return false; } void setVolume(byte volume) { static_cast(_driver)->setVolume(volume); } void playSwitch(bool play) { static_cast(_driver)->playSwitch(play); } - + void initTrack(SciSpan &header) { static_cast(_driver)->initTrack(header); } int getLastChannel() const { return (static_cast(_driver)->useRhythmChannel() ? 8 : 15); } }; @@ -219,15 +227,18 @@ static const int ym3812_note[13] = { 0x2ae }; -int MidiDriver_AdLib::openAdLib(bool isSCI0) { +int MidiDriver_AdLib::openAdLib() { _stereo = STEREO; - debug(3, "ADLIB: Starting driver in %s mode", (isSCI0 ? "SCI0" : "SCI1")); - _isSCI0 = isSCI0; + debug(3, "ADLIB: Starting driver in %s mode", (_isSCI0 ? "SCI0" : "SCI1")); + + // Fill in the voice queue + for (int i = 0; i < kVoices; ++i) + _voiceQueue.push_back(i); _opl = OPL::Config::create(_stereo ? OPL::Config::kDualOpl2 : OPL::Config::kOpl2); - // Try falling back to mono, thus plain OPL2 emualtor, when no Dual OPL2 is available. + // Try falling back to mono, thus plain OPL2 emulator, when no Dual OPL2 is available. if (!_opl && _stereo) { _stereo = false; _opl = OPL::Config::create(OPL::Config::kOpl2); @@ -330,6 +341,53 @@ void MidiDriver_AdLib::send(uint32 b) { } } +void MidiDriver_AdLib::initTrack(SciSpan &header) { + if (!_isOpen || !_isSCI0) + return; + + uint8 readPos = 0; + uint8 caps = header.getInt8At(readPos++); + if (caps != 0 && (_version == SCI_VERSION_0_EARLY || caps != 2)) + return; + + for (int i = 0; i < kVoices; ++i) { + _voices[i].channel = _voices[i].mappedChannel = _voices[i].note = -1; + _voices[i].isSustained = false; + _voices[i].patch = 13; + _voices[i].velocity = 0; + _voices[i].age = 0; + } + + int numVoices = 0; + for (int i = 0; i < 16; ++i) { + _channels[i].patch = 13; + _channels[i].extraVoices = 0; + _channels[i].mappedVoices = 0; + + if (_version == SCI_VERSION_0_LATE) { + uint8 num = header.getInt8At(readPos++) & 0x7F; + uint8 flags = header.getInt8At(readPos++); + if ((flags & 0x04) && num) + assignVoices(i, num); + } else { + uint8 val = header.getInt8At(readPos++); + if (val & 0x01) { + uint8 num = val >> 4; + if (!(val & 0x08) && num && num != 0x0F) { + while (num--) { + if (numVoices >= _numVoiceMax) + continue; + _voices[numVoices++].mappedChannel = i; + _channels[i].mappedVoices++; + } + } + } else if (val & 0x08) { + debugC(9, kDebugLevelSound, "MidiDriver_AdLib::initTrack(): Control channel found: 0x%.02x", i); + } + } + } +} + void MidiDriver_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { _adlibTimerProc = timerProc; _adlibTimerParam = timerParam; @@ -377,8 +435,8 @@ void MidiDriver_AdLib::loadInstrument(const SciSpan &ins) { void MidiDriver_AdLib::voiceMapping(int channel, int voices) { int curVoices = 0; - for (int i = 0; i < kVoices; i++) - if (_voices[i].channel == channel) + for (int i = 0; i < _numVoiceMax; i++) + if (_voices[i].mappedChannel == channel) curVoices++; curVoices += _channels[channel].extraVoices; @@ -396,14 +454,19 @@ void MidiDriver_AdLib::voiceMapping(int channel, int voices) { void MidiDriver_AdLib::assignVoices(int channel, int voices) { assert(voices > 0); - for (int i = 0; i < kVoices; i++) - if (_voices[i].channel == -1) { - _voices[i].channel = channel; + for (int i = 0; i < _numVoiceMax; i++) + if (_voices[i].mappedChannel == -1) { + if (_voices[i].note != -1) // Late SCI1.1, stop note on unmapped channel + voiceOff(i); + _voices[i].mappedChannel = channel; + ++_channels[channel].mappedVoices; if (--voices == 0) return; } - _channels[channel].extraVoices += voices; + // This is already too advanced for SCI0... + if (!_isSCI0) + _channels[channel].extraVoices += voices; } void MidiDriver_AdLib::releaseVoices(int channel, int voices) { @@ -415,18 +478,20 @@ void MidiDriver_AdLib::releaseVoices(int channel, int voices) { voices -= _channels[channel].extraVoices; _channels[channel].extraVoices = 0; - for (int i = 0; i < kVoices; i++) { - if ((_voices[i].channel == channel) && (_voices[i].note == -1)) { - _voices[i].channel = -1; + for (int i = 0; i < _numVoiceMax; i++) { + if ((_voices[i].mappedChannel == channel) && (_voices[i].note == -1)) { + _voices[i].mappedChannel = -1; + --_channels[channel].mappedVoices; if (--voices == 0) return; } } - for (int i = 0; i < kVoices; i++) { - if (_voices[i].channel == channel) { + for (int i = 0; i < _numVoiceMax; i++) { + if (_voices[i].mappedChannel == channel) { voiceOff(i); - _voices[i].channel = -1; + _voices[i].mappedChannel = -1; + --_channels[channel].mappedVoices; if (--voices == 0) return; } @@ -434,10 +499,13 @@ void MidiDriver_AdLib::releaseVoices(int channel, int voices) { } void MidiDriver_AdLib::donateVoices() { + if (_isSCI0) + return; + int freeVoices = 0; for (int i = 0; i < kVoices; i++) - if (_voices[i].channel == -1) + if (_voices[i].mappedChannel == -1) freeVoices++; if (freeVoices == 0) @@ -484,11 +552,7 @@ void MidiDriver_AdLib::noteOn(int channel, int note, int velocity) { } } -#ifdef ADLIB_DISABLE_VOICE_MAPPING - int voice = findVoiceBasic(channel); -#else - int voice = findVoice(channel); -#endif + int voice = _rhythmKeyMap ? findVoiceLateSci11(channel) : findVoice(channel); if (voice == -1) { debug(3, "ADLIB: failed to find free voice assigned to channel %i", channel); @@ -498,77 +562,97 @@ void MidiDriver_AdLib::noteOn(int channel, int note, int velocity) { voiceOn(voice, note, velocity); } -// FIXME: Temporary, see comment at top of file regarding ADLIB_DISABLE_VOICE_MAPPING -int MidiDriver_AdLib::findVoiceBasic(int channel) { +int MidiDriver_AdLib::findVoice(int channel) { int voice = -1; int oldestVoice = -1; - int oldestAge = -1; + uint32 oldestAge = 0; // Try to find a voice assigned to this channel that is free (round-robin) for (int i = 0; i < kVoices; i++) { int v = (_channels[channel].lastVoice + i + 1) % kVoices; - if (_voices[v].note == -1) { - voice = v; - break; - } + if (_voices[v].mappedChannel == channel) { + if (_voices[v].note == -1) { + voice = v; + _voices[voice].channel = channel; + break; + } - // We also keep track of the oldest note in case the search fails - if (_voices[v].age > oldestAge) { - oldestAge = _voices[v].age; - oldestVoice = v; + // We also keep track of the oldest note in case the search fails + // Notes started in the current time slice will not be selected + if (_voices[v].age >= oldestAge) { + oldestAge = _voices[v].age; + oldestVoice = v; + } } } if (voice == -1) { - if (oldestVoice >= 0) { - voiceOff(oldestVoice); - voice = oldestVoice; - } else { + if (!oldestAge) return -1; - } + voiceOff(oldestVoice); + voice = oldestVoice; + _voices[voice].channel = channel; } - _voices[voice].channel = channel; _channels[channel].lastVoice = voice; + return voice; } -int MidiDriver_AdLib::findVoice(int channel) { - int voice = -1; - int oldestVoice = -1; - uint32 oldestAge = 0; +int MidiDriver_AdLib::findVoiceLateSci11(int channel) { + Common::List::const_iterator it; - // Try to find a voice assigned to this channel that is free (round-robin) - for (int i = 0; i < kVoices; i++) { - int v = (_channels[channel].lastVoice + i + 1) % kVoices; + // Search for unused voice + for (it = _voiceQueue.begin(); it != _voiceQueue.end(); ++it) { + int voice = *it; + if (_voices[voice].note == -1 && _voices[voice].patch == _channels[channel].patch) { + _voices[voice].channel = channel; + return voice; + } + } - if (_voices[v].channel == channel) { - if (_voices[v].note == -1) { - voice = v; - break; - } + // Same as before, minus the program check + for (it = _voiceQueue.begin(); it != _voiceQueue.end(); ++it) { + int voice = *it; + if (_voices[voice].note == -1) { + _voices[voice].channel = channel; + return voice; + } + } - // We also keep track of the oldest note in case the search fails - // Notes started in the current time slice will not be selected - if (_voices[v].age > oldestAge) { - oldestAge = _voices[v].age; - oldestVoice = v; + // Search for channel with highest excess of voices + int maxExceed = 0; + int maxExceedChan = 0; + for (uint i = 0; i < MIDI_CHANNELS; ++i) { + if (_channels[i].voices > _channels[i].mappedVoices) { + int exceed = _channels[i].voices - _channels[i].mappedVoices; + if (exceed > maxExceed) { + maxExceed = exceed; + maxExceedChan = i; } } } - if (voice == -1) { - if (oldestVoice >= 0) { - voiceOff(oldestVoice); - voice = oldestVoice; - } else { - return -1; + // Stop voice on channel with highest excess if possible, otherwise stop + // note on this channel. + int stopChan = (maxExceed > 0) ? maxExceedChan : channel; + + for (it = _voiceQueue.begin(); it != _voiceQueue.end(); ++it) { + int voice = *it; + if (_voices[voice].channel == stopChan) { + voiceOff(voice); + _voices[voice].channel = channel; + return voice; } } - _channels[channel].lastVoice = voice; - return voice; + return -1; +} + +void MidiDriver_AdLib::queueMoveToBack(int voice) { + _voiceQueue.remove(voice); + _voiceQueue.push_back(voice); } void MidiDriver_AdLib::noteOff(int channel, int note) { @@ -585,18 +669,18 @@ void MidiDriver_AdLib::noteOff(int channel, int note) { void MidiDriver_AdLib::voiceOn(int voice, int note, int velocity) { int channel = _voices[voice].channel; - int patch; + int patch = _channels[channel].patch; _voices[voice].age = 0; + ++_channels[channel].voices; + queueMoveToBack(voice); - if (channel == 9 && _rhythmKeyMap) { + if ((channel == 9) && _rhythmKeyMap) { patch = CLIP(note, 27, 88) + 101; - } else { - patch = _channels[channel].patch; } // Set patch if different from current patch - if (patch != _voices[voice].patch) + if (patch != _voices[voice].patch && _playSwitch) setPatch(voice, patch); _voices[voice].velocity = velocity; @@ -604,10 +688,14 @@ void MidiDriver_AdLib::voiceOn(int voice, int note, int velocity) { } void MidiDriver_AdLib::voiceOff(int voice) { + int channel = _voices[voice].channel; + _voices[voice].isSustained = false; setNote(voice, _voices[voice].note, 0); _voices[voice].note = -1; _voices[voice].age = 0; + queueMoveToBack(voice); + --_channels[channel].voices; } void MidiDriver_AdLib::setNote(int voice, int note, bool key) { @@ -616,9 +704,8 @@ void MidiDriver_AdLib::setNote(int voice, int note, bool key) { float delta; int bend = _channels[channel].pitchWheel; - if (channel == 9 && _rhythmKeyMap) { + if ((channel == 9) && _rhythmKeyMap) note = _rhythmKeyMap[CLIP(note, 27, 88) - 27]; - } _voices[voice].note = note; @@ -837,7 +924,7 @@ int MidiPlayer_AdLib::open(ResourceManager *resMan) { return -1; } - return static_cast(_driver)->openAdLib(_version <= SCI_VERSION_0_LATE); + return static_cast(_driver)->openAdLib(); } void MidiPlayer_AdLib::close() { @@ -849,7 +936,7 @@ void MidiPlayer_AdLib::close() { byte MidiPlayer_AdLib::getPlayId() const { switch (_version) { case SCI_VERSION_0_EARLY: - return 0x01; + return 0x09; case SCI_VERSION_0_LATE: return 0x04; default: -- cgit v1.2.3