diff options
author | athrxx | 2019-04-28 14:44:22 +0200 |
---|---|---|
committer | athrxx | 2019-06-21 13:35:35 +0200 |
commit | f35eae7287458a89c0ea24f66aa12795c4cb3956 (patch) | |
tree | b85cd1497d12c74f387fae1f0b7f78960399041a | |
parent | 09f3d11cc18cc6c1bdf8a9de178e33de8e03ed96 (diff) | |
download | scummvm-rg350-f35eae7287458a89c0ea24f66aa12795c4cb3956.tar.gz scummvm-rg350-f35eae7287458a89c0ea24f66aa12795c4cb3956.tar.bz2 scummvm-rg350-f35eae7287458a89c0ea24f66aa12795c4cb3956.zip |
SCI: (CMS sound driver) - add support for SCI0
I haven't found an elegant and non-intrusive way to squeeze SCI0 support into LordHoto's existing code. The drivers are too different. So I made some rearrangements. The basic mechanisms of LordHoto's SCI1 code should remain the same as before, though. I only introduced some more classes, moved some code into these classes and renamed some things (mainly for myself, so as not to get confused).
I fixed two voice mapping bugs in the existing driver code. The first bug in bindVocies() effectively hindered the driver from playing anything at all when the CMS_DISABLE_VOICE_MAPPING #define wasn't set (_voice[i].channel == 0xFF instead of _voice[i].channel != 0xFF). The second bug in unbindVoices() was not a complete show stopper, but the function simply did not "unbind the voice". The line which does the actual removal of the channel assignment was missing.
The SCI0 driver portions have been tested with: PQ2, KQ4, LSL3, QFG1, ICE and COC.
SCI_0_EARLY versions apparently don't support the CMS. At least I haven't seen a driver file so far. And there seems to be no no instrument patch resource. Although the latter issue needn't necessarily be one, since the patch data array in the driver is actually preset with data (which gets overwritten as soon as a patch file is loaded). Maybe this would work for SCI_0_EARLY. However, I haven't tested this, since I really would have have a look at a driver file first if one actually exists. For now, I have limited the driver to SCI_0_LATE.
SCI1 has been tested with KQ5 and LSL5 (not extensively, just to see whether anything got broken and whether my voice mapping fixes work).
-rw-r--r-- | engines/sci/sound/drivers/cms.cpp | 1451 | ||||
-rw-r--r-- | engines/sci/sound/music.cpp | 5 |
2 files changed, 1017 insertions, 439 deletions
diff --git a/engines/sci/sound/drivers/cms.cpp b/engines/sci/sound/drivers/cms.cpp index 8b92432cb9..1eee102450 100644 --- a/engines/sci/sound/drivers/cms.cpp +++ b/engines/sci/sound/drivers/cms.cpp @@ -33,14 +33,157 @@ namespace Sci { -// FIXME: We don't seem to be sending the polyphony init data, so disable this for now -#define CMS_DISABLE_VOICE_MAPPING +class MidiDriver_CMS; + +class CMSVoice { +public: + CMSVoice(uint8 id, MidiDriver_CMS *driver, CMSEmulator *cms, SciSpan<const uint8>& patchData); + virtual ~CMSVoice(); + + virtual void noteOn(int note, int velocity) = 0; + virtual void noteOff() = 0; + virtual void stop() = 0; + virtual void programChange(int program) = 0; + virtual void pitchWheel() {} + + virtual void update() = 0; + + virtual void reset() {} + virtual void setPanMask(uint8) {} + + uint8 _assign; + uint8 _note; + bool _sustained; + uint16 _duration; + uint16 _releaseDuration; + CMSVoice *_secondaryVoice; + +protected: + void sendFrequency(); + void cmsWrite(uint8 reg, uint8 val); + + CMSEmulator *_cms; + MidiDriver_CMS *_driver; + SciSpan<const uint8> _patchData; + + const uint8 _id; + const uint8 _regOffset; + const uint8 _portOffset; + + static uint8 _octaveRegs[6]; + static const int _frequencyTable[48]; + +private: + virtual void recalculateFrequency(uint8 &freq, uint8 &octave) = 0; +}; + +class CMSVoice_V0 : public CMSVoice { +public: + CMSVoice_V0(uint8 id, MidiDriver_CMS *driver, CMSEmulator *cms, SciSpan<const uint8>& patchData); + virtual ~CMSVoice_V0(); + + void noteOn(int note, int); + void noteOff(); + void stop(); + void programChange(int program); + + void update(); + + void reset(); + void setPanMask(uint8 mask); + +private: + void recalculateFrequency(uint8 &frequency, uint8 &octave); + void recalculateEvelopeLevels(); + void selectEnvelope(int id); + + enum EnvelopeState { + kReady = 0, + kRestart = 1, + kAttack = 2, + kDecay = 3, + kSustain = 4, + kRelease = 5 + }; + + EnvelopeState _envState; + uint8 _envAR; + uint8 _envTL; + uint8 _envDR; + uint8 _envSL; + uint8 _envRR; + uint8 _envSLI; + uint8 _envPAC; + uint8 _envPA; + + static uint8 _envAR1; + + uint8 _envNote; + uint8 _envSSL; + uint8 _panMask; + uint8 _strMask; + + int8 _transFreq; + int8 _transOct; + + bool _vbrOn; + uint8 _vbrSteps; + uint8 _vbrState; + int8 _vbrMod; + int8 _vbrCur; + int16 _vbrPhase; + + int _currentLevel; + bool _updateCMS; + + const bool _isSecondary; + + static const uint8 _envelopeDataTable[256]; + static const uint8 _volumeTable[176]; + static const uint8 _pitchWheelTable[65]; +}; + +class CMSVoice_V1 : public CMSVoice { +public: + CMSVoice_V1(uint8 id, MidiDriver_CMS *driver, CMSEmulator *cms, SciSpan<const uint8>& patchData); + virtual ~CMSVoice_V1(); + + void noteOn(int note, int velocity); + void noteOff(); + void stop(); + void programChange(int program); + void pitchWheel(); + + void update(); + +private: + void recalculateFrequency(uint8 &frequency, uint8 &octave); + + void updateVoiceAmplitude(); + void setupVoiceAmplitude(); + + SciSpan<const uint8> _patchDataCur; + uint8 _velocity; + uint8 _patchDataIndex; + uint8 _amplitudeTimer; + uint8 _amplitudeModifier; + bool _release; + + static const int _velocityTable[32]; +}; class MidiDriver_CMS : public MidiDriver_Emulated { public: - MidiDriver_CMS(Audio::Mixer *mixer, ResourceManager *resMan) - : MidiDriver_Emulated(mixer), _resMan(resMan), _cms(0), _rate(0), _playSwitch(true), _masterVolume(0) { - } + enum { + MIDI_PROP_CHANNEL_VOLUME = 1, + MIDI_PROP_CHANNEL_PITCHWHEEL = 2, + MIDI_PROP_CHANNEL_PANPOS = 3, + MIDI_PROP_PLAYSWITCH = 4 + }; + +public: + MidiDriver_CMS(Audio::Mixer *mixer, ResourceManager *resMan, SciVersion version); + ~MidiDriver_CMS(); int open(); void close(); @@ -48,105 +191,102 @@ public: void send(uint32 b); uint32 property(int prop, uint32 param); + void initTrack(SciSpan<const byte>& header); + + void onTimer(); + MidiChannel *allocateChannel() { return 0; } MidiChannel *getPercussionChannel() { return 0; } bool isStereo() const { return true; } int getRate() const { return _rate; } - void playSwitch(bool play); private: + void noteOn(int channelNr, int note, int velocity); + void noteOff(int channelNr, int note); + void controlChange(int channelNr, int control, int value); + void programChange(int channelNr, int value); + void pitchWheel(int channelNr, int value); + + void voiceMapping(int channelNr, int value); + void bindVoices(int channelNr, int voices, bool bindSecondary, bool doProgramChange); + void unbindVoices(int channelNr, int voices, bool bindSecondary); + void donateVoices(bool bindSecondary); + int findVoice(int channelNr, int note); + int findVoiceBasic(int channelNr); + + void writeToChip(int chip, int address, int data); void generateSamples(int16 *buffer, int len); - ResourceManager *_resMan; - CMSEmulator *_cms; - - void writeToChip1(int address, int data); - void writeToChip2(int address, int data); - - int32 _samplesPerCallback; - int32 _samplesPerCallbackRemainder; - int32 _samplesTillCallback; - int32 _samplesTillCallbackRemainder; - - int _rate; - bool _playSwitch; - uint16 _masterVolume; - - Common::SpanOwner<SciSpan<uint8> > _patchData; - struct Channel { - Channel() - : patch(0), volume(0), pan(0x40), hold(0), extraVoices(0), - pitchWheel(0x2000), pitchModifier(0), pitchAdditive(false), - lastVoiceUsed(0) { - } - - uint8 patch; + Channel() : program(0), volume(0), pan(0x40), hold(0), missingVoices(0), lastVoiceUsed(0), pitchWheel(0x2000), isValid(true) {} + uint8 program; uint8 volume; uint8 pan; uint8 hold; - uint8 extraVoices; - uint16 pitchWheel; - uint8 pitchModifier; - bool pitchAdditive; + uint8 missingVoices; uint8 lastVoiceUsed; + uint16 pitchWheel; + bool isValid; }; Channel _channel[16]; + CMSVoice *_voice[12]; - struct Voice { - Voice() : channel(0xFF), note(0xFF), sustained(0xFF), ticks(0), - turnOffTicks(0), patchDataPtr(), patchDataIndex(0), - amplitudeTimer(0), amplitudeModifier(0), turnOff(false), - velocity(0) { - } + const int _numVoicesPrimary; + const int _numVoicesSecondary; - uint8 channel; - uint8 note; - uint8 sustained; - uint16 ticks; - uint16 turnOffTicks; - SciSpan<uint8> patchDataPtr; - uint8 patchDataIndex; - uint8 amplitudeTimer; - uint8 amplitudeModifier; - bool turnOff; - uint8 velocity; - }; + CMSEmulator *_cms; + ResourceManager *_resMan; + Common::SpanOwner<SciSpan<const uint8> > _patchData; + + bool _playSwitch; + uint16 _masterVolume; + + const int _actualTimerInterval; + const int _reqTimerInterval; + int _updateTimer; + int _rate; - Voice _voice[12]; + SciVersion _version; +}; - void voiceOn(int voice, int note, int velocity); - void voiceOff(int voice); +CMSVoice::CMSVoice(uint8 id, MidiDriver_CMS* driver, CMSEmulator *cms, SciSpan<const uint8>& patchData) : _id(id), _regOffset(id > 5 ? id - 6 : id), _portOffset(id > 5 ? 2 : 0), + _driver(driver), _cms(cms), _assign(0xFF), _note(0xFF), _sustained(false), _duration(0), _releaseDuration(0), _secondaryVoice(0), _patchData(patchData) { + assert(_id < 12); + _octaveRegs[_id >> 1] = 0; +} - void noteSend(int voice); +CMSVoice::~CMSVoice() { - void noteOn(int channel, int note, int velocity); - void noteOff(int channel, int note); - void controlChange(int channel, int control, int value); - void pitchWheel(int channel, int value); +} - void voiceMapping(int channel, int value); - void bindVoices(int channel, int voices); - void unbindVoices(int channel, int voices); - void donateVoices(); - int findVoice(int channel); +void CMSVoice::sendFrequency() { + uint8 frequency = 0; + uint8 octave = 0; - int findVoiceBasic(int channel); + recalculateFrequency(frequency, octave); - void updateVoiceAmplitude(int voice); - void setupVoiceAmplitude(int voice); + uint8 octaveData = _octaveRegs[_id >> 1]; + octaveData = (_id & 1) ? (octaveData & 0x0F) | (octave << 4) : (octaveData & 0xF0) | octave; - uint8 _octaveRegs[2][3]; + cmsWrite(8 + _regOffset, frequency); + cmsWrite(0x10 + (_regOffset >> 1), octaveData); +} - static const int _timerFreq = 60; +void CMSVoice::cmsWrite(uint8 reg, uint8 val) { + _cms->portWrite(0x221 + _portOffset, reg); + _cms->portWrite(0x220 + _portOffset, val); - static const int _frequencyTable[]; - static const int _velocityTable[]; + if (reg >= 16 && reg <= 18) + _octaveRegs[_id >> 1] = val; +} + +uint8 CMSVoice::_octaveRegs[6] = { + 0, 0, 0, 0, 0, 0 }; -const int MidiDriver_CMS::_frequencyTable[] = { +const int CMSVoice::_frequencyTable[48] = { 3, 10, 17, 24, 31, 38, 46, 51, 58, 64, 71, 77, @@ -161,13 +301,489 @@ const int MidiDriver_CMS::_frequencyTable[] = { 242, 246, 250, 253 }; -const int MidiDriver_CMS::_velocityTable[] = { +CMSVoice_V0::CMSVoice_V0(uint8 id, MidiDriver_CMS* driver, CMSEmulator *cms, SciSpan<const uint8>& patchData) : CMSVoice(id, driver, cms, patchData), _envState(kReady), _currentLevel(0), _strMask(0), + _envAR(0), _envTL(0), _envDR(0), _envSL(0), _envRR(0), _envSLI(0), _vbrOn(false), _vbrSteps(0), _vbrState(0), _vbrMod(0), _vbrCur(0), _isSecondary(id > 7), + _vbrPhase(0), _transOct(0), _transFreq(0), _envPAC(0), _envPA(0), _panMask(_id & 1 ? 0xF0 : 0x0F), _envSSL(0), _envNote(0xFF), _updateCMS(false) { +} + +CMSVoice_V0::~CMSVoice_V0() { +} + +void CMSVoice_V0::noteOn(int note, int) { + if (!_driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, 0xFFFF) || !_envTL) + return; + + _note = note; + _envNote = note + 3; + _envState = kRestart; + _vbrPhase = 0; + _vbrCur = _vbrMod; + _vbrState = _vbrSteps & 0x0F; + _envPAC = _envPA; + + //debug("NOTEON: Voice: %02d, Note: %02d, AR: 0x%02x, TL: 0x%02x, DR: 0x%02x, SLI: 0x%02x, RR: 0x%02x, VBR: 0x%02x", _id, note, _envAR, _envTL, _envDR, _envSLI, _envRR, _vbrMod); + + if (_secondaryVoice) + _secondaryVoice->noteOn(note, 127); +} + +void CMSVoice_V0::noteOff() { + if (!_driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, 0xFFFF) || !_envTL) + return; + + //debug("NOTEOFF: Voice: %02d", _id); + + _note = 0xFF; + _envState = kRelease; + if (_secondaryVoice) + _secondaryVoice->noteOff(); +} + +void CMSVoice_V0::stop() { + _note = 0xFF; + _envState = kRelease; + if (_secondaryVoice) + _secondaryVoice->stop(); +} + +void CMSVoice_V0::programChange(int program) { + assert(program < 128); + if (program == 127) { + // This seems to replace the start of track offset with the current position so that 0xFC (kEndOfTrack) + // midi events would not reset the track to the start, but to the current position instead. This cannot + // be handled here. All versions of the SCI0 driver that I have seen so far do this. Still, I somehow + // doubt that it will ever come up, but let's see... + warning("CMSVoice_V0::programChange(): Unhandled program change 127"); + return; + } + + SciSpan<const uint8> data = _patchData.subspan(128 + (_patchData.getUint8At(program) << 3)); + uint8 pos = _isSecondary ? 3 : 0; + + selectEnvelope(data.getUint8At(pos++)); + + if (_isSecondary) { + _envSSL = data.getUint8At(pos++); + // This decides whether the secondary voice has the same or the opposite pan position as the primary voice. + _panMask = _strMask ^ -(_envSSL & 1); + } + + _transOct = data.getInt8At(pos++); + _transFreq = data.getInt8At(pos++); + + if (_isSecondary) + _envPA = data.getUint8At(pos++); + + if (_secondaryVoice) { + assert(!_isSecondary); + if (data.getUint8At(pos) == 0xFF) { + _secondaryVoice->stop(); + _secondaryVoice->_assign = 0xFF; + _secondaryVoice = 0; + } else { + _secondaryVoice->setPanMask(_panMask); + _secondaryVoice->programChange(program); + } + } +} + +void CMSVoice_V0::update() { + if (_updateCMS) { + sendFrequency(); + cmsWrite(_regOffset, ((_currentLevel & 0xF0) | (_currentLevel >> 4)) & _panMask); + _updateCMS = false; + } + + recalculateEvelopeLevels(); + + switch (_envState) { + case kReady: + _envNote = 0xFF; + return; + + case kRestart: + if (_envPAC) { + --_envPAC; + break; + } else { + //if ((_currentLevel >> 1) > _envAR) + _currentLevel = ((_currentLevel >> 1) > (int8)_envAR) ? ((_currentLevel >> 1) - _envAR1) & 0xFF : (_envAR - _envAR1) & 0xFF; + //_currentLevel = (uint8)MIN<int8>(0, (_currentLevel >> 1) - _envAR); + _envState = kAttack; + } + // fall through + + case kAttack: + _currentLevel = _currentLevel + _envAR; + if (_currentLevel > _envTL || _currentLevel > 0xFF) { + _currentLevel = _envTL; + _envState = kDecay; + } + break; + + case kDecay: + _currentLevel -= _envDR; + if (_currentLevel <= _envSL) { + if (_currentLevel < 0) + _currentLevel = 0; + _envState = kSustain; + } + break; + + case kSustain: + _currentLevel = _envSL; + break; + + case kRelease: + _currentLevel -= _envRR; + if (_currentLevel < 0) { + _currentLevel = 0; + _envState = kReady; + } + break; + + default: + break; + } + + if (_vbrOn && _envState != kRestart) { + _vbrPhase += _vbrCur; + if (!--_vbrState) { + _vbrCur = -_vbrCur; + _vbrState = (_vbrSteps & 0x0F) << 1; + } + } + + _updateCMS = true; + ++_duration; +} + +void CMSVoice_V0::reset() { + _envState = kReady; + _secondaryVoice = 0; + _assign = _note = _envNote = 0xFF; + _panMask = _id & 1 ? 0xF0 : 0x0F; + _envTL = 0; + _currentLevel = 0; + _duration = 0; + _envPA = 0; + _transFreq = _transOct = 0; + selectEnvelope(3); +} + +void CMSVoice_V0::setPanMask(uint8 mask) { + _strMask = mask; +} + +void CMSVoice_V0::recalculateFrequency(uint8 &freq, uint8 &octave) { + if (_assign == 0xFF || _envNote == 0xFF) + return; + + uint8 note = _envNote % 12; + octave = CLIP<int>(_envNote / 12 - 2, 0, 7); + + int16 pw = (_driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_PITCHWHEEL, _assign) & 0x7FFF) - 0x2000; + int16 sg = (pw < 0) ? -1 : 0; + pw = (pw ^ sg) - sg; + pw = ((pw >> 7) & 1) + (pw >> 8); + assert(pw < ARRAYSIZE(_pitchWheelTable)); + pw = (_pitchWheelTable[pw] ^ sg) - sg; + + int frequency = note * 4 + pw; + + if (frequency < 0) { + if (octave) { + frequency += 48; + --octave; + } else { + frequency = 0; + } + } else if (frequency >= 48) { + if (octave < 7) { + frequency -= 48; + ++octave; + } else { + frequency = 47; + } + } + + octave = CLIP<int8>(octave + _transOct, 0, 7); + frequency = _frequencyTable[frequency & 0xFF] + _transFreq + _vbrPhase; + + if (frequency > 255) { + frequency &= 0xFF; + octave++; + } else if (frequency < 0) { + frequency &= 0xFF; + octave--; + } + + octave = CLIP<int8>(octave, 0, 7); + freq = frequency; +} + +void CMSVoice_V0::recalculateEvelopeLevels() { + uint8 chanVol = _driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_VOLUME, _assign); + + if (_envTL && _isSecondary) { + int volIndexTLS = (chanVol >> 4) | (_envSSL & 0xF0); + assert(volIndexTLS < ARRAYSIZE(_volumeTable)); + _envTL = _volumeTable[volIndexTLS]; + } else if (_envTL) { + _envTL = chanVol; + } + + int volIndexSL = (_envSLI << 4) + (_envTL >> 4); + assert(volIndexSL < ARRAYSIZE(_volumeTable)); + _envSL = _volumeTable[volIndexSL]; +} + +uint8 CMSVoice_V0::_envAR1 = 0; + +void CMSVoice_V0::selectEnvelope(int id) { + const uint8 *in = &_envelopeDataTable[(id & 0x1F) << 3]; + _envAR = *in++; + _envTL = *in++; + _envDR = *in++; + _envSLI = *in++; + _envRR = *in++; + /*unused*/in++; + _vbrMod = *in++; + _vbrSteps = *in++; + _vbrOn = _vbrMod; + if (_id == 1) + _envAR1 = _envAR; +} + +const uint8 CMSVoice_V0::_envelopeDataTable[256] = { + 0xff, 0xff, 0x02, 0x05, 0x08, 0x00, 0x01, 0x02, + 0xff, 0xff, 0x40, 0x02, 0x01, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x08, 0x02, 0x02, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x10, 0x02, 0x02, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x18, 0x02, 0x01, 0x00, 0x00, 0x00, + 0x20, 0xff, 0x01, 0x06, 0x08, 0x00, 0x00, 0x00, + 0x20, 0xff, 0x08, 0x02, 0x03, 0x00, 0x00, 0x00, + 0x20, 0xe0, 0x02, 0x05, 0x02, 0x00, 0x00, 0x00, + 0x10, 0xe0, 0x10, 0x03, 0x02, 0x00, 0x00, 0x00, + 0x20, 0xe0, 0x08, 0x03, 0x03, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x04, 0x06, 0x08, 0x00, 0x01, 0x02, + 0xff, 0xff, 0x04, 0x02, 0x04, 0x00, 0x01, 0x02, + 0xff, 0xff, 0x08, 0x02, 0x04, 0x00, 0x01, 0x02, + 0xff, 0xff, 0x10, 0x02, 0x04, 0x00, 0x01, 0x02, + 0xa0, 0xff, 0x0c, 0x03, 0x08, 0x00, 0x03, 0x01, + 0x20, 0xff, 0x01, 0x06, 0x0c, 0x00, 0x01, 0x02, + 0x20, 0xff, 0x08, 0x02, 0x04, 0x00, 0x01, 0x02, + 0x20, 0xd0, 0x02, 0x05, 0x02, 0x00, 0x01, 0x02, + 0x10, 0xff, 0x20, 0x05, 0x04, 0x00, 0x02, 0x02, + 0x08, 0xc0, 0x10, 0x04, 0x04, 0x00, 0x02, 0x02, + 0xc8, 0xff, 0x02, 0x05, 0x10, 0x00, 0x01, 0x02, + 0xff, 0xff, 0x04, 0x04, 0x10, 0x00, 0x01, 0x02, + 0xff, 0xff, 0x08, 0x03, 0x08, 0x00, 0x01, 0x02, + 0xff, 0xff, 0x0c, 0x02, 0x08, 0x00, 0x01, 0x02, + 0x19, 0xc4, 0x04, 0x04, 0x08, 0x00, 0x02, 0x02, + 0x10, 0xff, 0x01, 0x06, 0x08, 0x00, 0x04, 0x02, + 0x0a, 0xff, 0x01, 0x06, 0x08, 0x00, 0x05, 0x01, + 0x10, 0xff, 0x0a, 0x01, 0x02, 0x00, 0x05, 0x02, + 0xff, 0xff, 0x0a, 0x02, 0x08, 0x00, 0x05, 0x01, + 0xff, 0xff, 0x03, 0x03, 0x08, 0x00, 0x02, 0x01, + 0xff, 0xff, 0x08, 0x01, 0x04, 0x00, 0x0c, 0x02, + 0xff, 0xff, 0x10, 0x02, 0x04, 0x00, 0x0f, 0x0a +}; + +const uint8 CMSVoice_V0::_volumeTable[176] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x20, 0x20, 0x20, 0x30, 0x30, 0x30, 0x30, 0x40, 0x40, + 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x20, 0x20, 0x30, 0x30, 0x40, 0x40, 0x40, 0x50, 0x50, 0x60, + 0x00, 0x00, 0x10, 0x10, 0x20, 0x20, 0x30, 0x30, 0x40, 0x40, 0x50, 0x50, 0x60, 0x60, 0x70, 0x70, + 0x00, 0x00, 0x10, 0x10, 0x20, 0x30, 0x30, 0x40, 0x40, 0x50, 0x60, 0x60, 0x70, 0x70, 0x80, 0x90, + 0x00, 0x00, 0x10, 0x20, 0x20, 0x30, 0x40, 0x40, 0x50, 0x60, 0x70, 0x70, 0x80, 0x90, 0x90, 0xa0, + 0x00, 0x00, 0x10, 0x20, 0x30, 0x40, 0x40, 0x50, 0x60, 0x70, 0x80, 0x80, 0x90, 0xa0, 0xb0, 0xc0, + 0x00, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, + 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0 +}; + +const uint8 CMSVoice_V0::_pitchWheelTable[65] = { + 0x00, 0x01, 0x02, 0x02, 0x03, 0x04, 0x05, 0x05, + 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0e, 0x0f, 0x10, 0x11, 0x11, + 0x12, 0x13, 0x14, 0x14, 0x15, 0x16, 0x17, 0x17, + 0x18, 0x19, 0x1a, 0x1a, 0x1b, 0x1c, 0x1d, 0x1d, + 0x1e, 0x1f, 0x20, 0x20, 0x21, 0x22, 0x23, 0x23, + 0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x29, + 0x2a, 0x2b, 0x2c, 0x2c, 0x2d, 0x2e, 0x2f, 0x2f, + 0x30 +}; + +CMSVoice_V1::CMSVoice_V1(uint8 id, MidiDriver_CMS* driver, CMSEmulator *cms, SciSpan<const uint8>& patchData) : CMSVoice(id, driver, cms, patchData), _velocity(0), _patchDataIndex(0), + _amplitudeTimer(0), _amplitudeModifier(0), _release(false) { +} + +CMSVoice_V1::~CMSVoice_V1() { +} + +void CMSVoice_V1::noteOn(int note, int velocity) { + _note = note; + _release = false; + _patchDataIndex = 0; + _amplitudeTimer = 0; + _duration = 0; + _releaseDuration = 0; + _velocity = velocity ? _velocityTable[velocity >> 3] : 0; + sendFrequency(); +} + +void CMSVoice_V1::noteOff() { + _release = true; +} + +void CMSVoice_V1::stop() { + _velocity = 0; + _note = 0xFF; + _sustained = false; + _release = false; + _patchDataIndex = 0; + _amplitudeTimer = 0; + _amplitudeModifier = 0; + _duration = 0; + _releaseDuration = 0; + + setupVoiceAmplitude(); +} + +void CMSVoice_V1::programChange(int program) { + _patchDataCur = _patchData.subspan(_patchData.getUint16LEAt(program << 1)); +} + +void CMSVoice_V1::pitchWheel() { + sendFrequency(); +} + +void CMSVoice_V1::update() { + if (_note == 0xFF) + return; + + if (_release) + ++_releaseDuration; + ++_duration; + + updateVoiceAmplitude(); + setupVoiceAmplitude(); +} + +void CMSVoice_V1::recalculateFrequency(uint8 &freq, uint8 &octave) { + assert(_assign != 0xFF); + + int frequency = (CLIP<int>(_note, 21, 116) - 21) * 4; + int16 pw = _driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_PITCHWHEEL, _assign); + int modifier = (pw < 0x2000) ? (0x2000 - pw) / 170 : ((pw > 0x2000) ? (pw - 0x2000) / 170 : 0); + + if (modifier) { + if (pw < 0x2000) { + if (frequency > modifier) + frequency -= modifier; + else + frequency = 0; + } else { + int tempFrequency = 384 - frequency; + if (modifier < tempFrequency) + frequency += modifier; + else + frequency = 383; + } + } + + octave = 0; + while (frequency >= 48) { + frequency -= 48; + ++octave; + } + + freq = _frequencyTable[frequency & 0xFF]; +} + +void CMSVoice_V1::updateVoiceAmplitude() { + if (_amplitudeTimer != 0 && _amplitudeTimer != 254) { + --_amplitudeTimer; + return; + } else if (_amplitudeTimer == 254) { + if (!_release) + return; + _amplitudeTimer = 0; + } + + int nextDataIndex = _patchDataIndex; + uint8 timerData = 0; + uint8 amplitudeData = _patchDataCur[nextDataIndex]; + + if (amplitudeData == 255) { + timerData = amplitudeData = 0; + stop(); + } else { + timerData = _patchDataCur[nextDataIndex + 1]; + nextDataIndex += 2; + } + + _patchDataIndex = nextDataIndex; + _amplitudeTimer = timerData; + _amplitudeModifier = amplitudeData; +} + +void CMSVoice_V1::setupVoiceAmplitude() { + assert(_assign != 0xFF); + uint amplitude = 0; + uint8 chanVolume = _driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_VOLUME, _assign); + uint8 masterVolume = _driver->property(MIDI_PROP_MASTER_VOLUME, 0xFFFF); + + if (chanVolume && _velocity && _amplitudeModifier && masterVolume) { + amplitude = chanVolume * _velocity; + amplitude /= 0x0F; + amplitude *= _amplitudeModifier; + amplitude /= 0x0F; + amplitude *= masterVolume; + amplitude /= 0x0F; + + if (!amplitude) + ++amplitude; + } + + uint8 amplitudeData = 0; + int pan = _driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_PANPOS, _assign) >> 2; + if (pan >= 16) { + amplitudeData = (amplitude * (31 - pan) / 0x0F) & 0x0F; + amplitudeData |= (amplitude << 4); + } else { + amplitudeData = (amplitude * pan / 0x0F) & 0x0F; + amplitudeData <<= 4; + amplitudeData |= amplitude; + } + + if (!_driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, 0xFFFF)) + amplitudeData = 0; + + cmsWrite(_regOffset, amplitudeData); +} + +const int CMSVoice_V1::_velocityTable[32] = { 1, 3, 6, 8, 9, 10, 11, 12, 12, 13, 13, 14, 14, 14, 15, 15, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 10, 10 }; +MidiDriver_CMS::MidiDriver_CMS(Audio::Mixer* mixer, ResourceManager* resMan, SciVersion version) : MidiDriver_Emulated(mixer), _resMan(resMan), + _version(version), _cms(0), _rate(0), _playSwitch(true), _masterVolume(0), _numVoicesPrimary(version > SCI_VERSION_0_LATE ? 12 : 8), + _actualTimerInterval(1000000000 /(_baseFreq*1000)), _reqTimerInterval(1000000000/60000/*18206*/), _numVoicesSecondary(version > SCI_VERSION_0_LATE ? 0 : 4) { + memset(_voice, 0, sizeof(_voice)); + _updateTimer = _reqTimerInterval; +} + +MidiDriver_CMS::~MidiDriver_CMS() { + for (int i = 0; i < 12; ++i) + delete _voice[i]; +} + int MidiDriver_CMS::open() { if (_cms) return MERR_ALREADY_OPEN; @@ -176,36 +792,42 @@ int MidiDriver_CMS::open() { Resource *res = _resMan->findResource(ResourceId(kResourceTypePatch, 101), false); if (!res) return -1; + + _patchData->allocateFromSpan(_version < SCI_VERSION_1_EARLY ? res->subspan(30) : *res); - _patchData->allocateFromSpan(*res); - + _rate = _mixer->getOutputRate(); + _cms = new CMSEmulator(_rate); + assert(_cms); + for (uint i = 0; i < ARRAYSIZE(_channel); ++i) _channel[i] = Channel(); - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) - _voice[i] = Voice(); - - _rate = _mixer->getOutputRate(); - _cms = new CMSEmulator(_rate); - assert(_cms); + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { + if (_version < SCI_VERSION_1_EARLY) + _voice[i] = new CMSVoice_V0(i, this, _cms, *_patchData); + else + _voice[i] = new CMSVoice_V1(i, this, _cms, *_patchData); + } + _playSwitch = true; _masterVolume = 0; for (int i = 0; i < 31; ++i) { - writeToChip1(i, 0); - writeToChip2(i, 0); + writeToChip(0, i, 0); + writeToChip(1, i, 0); } - writeToChip1(0x14, 0xFF); - writeToChip2(0x14, 0xFF); + // Enable frequency for all channels + writeToChip(0, 0x14, _version < SCI_VERSION_1_EARLY ? 0x3F : 0xFF); + writeToChip(1, 0x14, _version < SCI_VERSION_1_EARLY ? 0x3F : 0xFF); - writeToChip1(0x1C, 1); - writeToChip2(0x1C, 1); + // Sync and reset generators + writeToChip(0, 0x1C, 2); + writeToChip(1, 0x1C, 2); - _samplesPerCallback = getRate() / _timerFreq; - _samplesPerCallbackRemainder = getRate() % _timerFreq; - _samplesTillCallback = 0; - _samplesTillCallbackRemainder = 0; + // Enable all channels + writeToChip(0, 0x1C, 1); + writeToChip(1, 0x1C, 1); int retVal = MidiDriver_Emulated::open(); if (retVal != 0) @@ -229,6 +851,10 @@ void MidiDriver_CMS::send(uint32 b) { const uint8 op1 = (b >> 8) & 0xff; const uint8 op2 = (b >> 16) & 0xff; + // This is a SCI0 only feature. For SCI1 we simply set all channels to valid by default so that this check will always pass. + if (!_channel[channel].isValid) + return; + switch (command) { case 0x80: noteOff(channel, op1); @@ -243,7 +869,7 @@ void MidiDriver_CMS::send(uint32 b) { break; case 0xC0: - _channel[channel].patch = op1; + programChange(channel, op1); break; case 0xE0: @@ -261,215 +887,144 @@ uint32 MidiDriver_CMS::property(int prop, uint32 param) { if (param != 0xffff) _masterVolume = param; return _masterVolume; - + case MIDI_PROP_PLAYSWITCH: + if (param != 0xffff) + _playSwitch = param ? true : false; + return _playSwitch ? 1 : 0; + case MIDI_PROP_CHANNEL_VOLUME: + return (param < 16) ? _channel[param].volume : 0; + case MIDI_PROP_CHANNEL_PITCHWHEEL: + return (param < 16) ? _channel[param].pitchWheel : 0; + case MIDI_PROP_CHANNEL_PANPOS: + return (param < 16) ? _channel[param].pan : 0; default: return MidiDriver_Emulated::property(prop, param); } } -void MidiDriver_CMS::playSwitch(bool play) { - _playSwitch = play; -} - -void MidiDriver_CMS::writeToChip1(int address, int data) { - _cms->portWrite(0x221, address); - _cms->portWrite(0x220, data); - - if (address >= 16 && address <= 18) - _octaveRegs[0][address - 16] = data; -} - -void MidiDriver_CMS::writeToChip2(int address, int data) { - _cms->portWrite(0x223, address); - _cms->portWrite(0x222, data); - - if (address >= 16 && address <= 18) - _octaveRegs[1][address - 16] = data; -} +void MidiDriver_CMS::initTrack(SciSpan<const byte>& header) { + if (!_isOpen || _version > SCI_VERSION_0_LATE) + return; -void MidiDriver_CMS::voiceOn(int voiceNr, int note, int velocity) { - Voice &voice = _voice[voiceNr]; - voice.note = note; - voice.turnOff = false; - voice.patchDataIndex = 0; - voice.amplitudeTimer = 0; - voice.ticks = 0; - voice.turnOffTicks = 0; - voice.patchDataPtr = _patchData->subspan(_patchData->getUint16LEAt(_channel[voice.channel].patch * 2)); - if (velocity) - velocity = _velocityTable[(velocity >> 3)]; - voice.velocity = velocity; - noteSend(voiceNr); -} + uint8 readPos = 0; + uint8 caps = header.getInt8At(readPos++); + int numChan = (caps == 2) ? 15 : 16; + if (caps != 0 && caps != 2) + return; -void MidiDriver_CMS::voiceOff(int voiceNr) { - Voice &voice = _voice[voiceNr]; - voice.velocity = 0; - voice.note = 0xFF; - voice.sustained = 0; - voice.turnOff = false; - voice.patchDataIndex = 0; - voice.amplitudeTimer = 0; - voice.amplitudeModifier = 0; - voice.ticks = 0; - voice.turnOffTicks = 0; + for (int i = 0; i < 12; ++i) + _voice[i]->reset(); - setupVoiceAmplitude(voiceNr); -} + for (int i = 0; i < 16; ++i) { + _channel[i].isValid = false; + _channel[i].volume = 180; + _channel[i].pitchWheel = 0x2000; + _channel[i].pan = 0; -void MidiDriver_CMS::noteSend(int voiceNr) { - Voice &voice = _voice[voiceNr]; + if (i == numChan) + continue; - int frequency = (CLIP<int>(voice.note, 21, 116) - 21) * 4; - if (_channel[voice.channel].pitchModifier) { - int modifier = _channel[voice.channel].pitchModifier; + uint8 num = header.getInt8At(readPos++) & 0x0F; + uint8 flags = header.getInt8At(readPos++); - if (!_channel[voice.channel].pitchAdditive) { - if (frequency > modifier) - frequency -= modifier; - else - frequency = 0; - } else { - int tempFrequency = 384 - frequency; - if (modifier < tempFrequency) - frequency += modifier; - else - frequency = 383; - } - } + if (num == 15 || !(flags & 4)) + continue; - int chipNumber = 0; - if (voiceNr >= 6) { - voiceNr -= 6; - chipNumber = 1; - } + // Another strange thing about this driver... All channels to be used have to be marked as valid here + // or they will be blocked in send(). Even control change voice mapping won't be accessible. This means + // that a num == 0 setting could even make sense here, since it will mark that channel as valid for + // later use (e.g. assigning voices via control change voice mapping). + _channel[i].isValid = true; + if (num == 0) + continue; - int octave = 0; - while (frequency >= 48) { - frequency -= 48; - ++octave; + // This weird driver will assign a second voice if the number of requested voices is exactly 1. + // The secondary voice is configured differently (has its own instrument patch data). The secondary + // voice is controlled through the primary voice. It will not receive its own separate commands. + // The main purpose seems to be to have stereo channels with 2 discrete voices for the left and + // right speaker output. However, the instrument patch can also turn this around so that both voices + // use the same panning. What an awesome concept... + bindVoices(i, num, num == 1, false); } +} - frequency = _frequencyTable[frequency]; - - if (chipNumber == 1) - writeToChip2(8 + voiceNr, frequency); - else - writeToChip1(8 + voiceNr, frequency); - - uint8 octaveData = _octaveRegs[chipNumber][voiceNr >> 1]; - - if (voiceNr & 1) { - octaveData &= 0x0F; - octaveData |= (octave << 4); - } else { - octaveData &= 0xF0; - octaveData |= octave; - } +void MidiDriver_CMS::onTimer() { + _updateTimer -= _actualTimerInterval; + if (_updateTimer > 0) + return; - if (chipNumber == 1) - writeToChip2(0x10 + (voiceNr >> 1), octaveData); - else - writeToChip1(0x10 + (voiceNr >> 1), octaveData); + _updateTimer += _reqTimerInterval; + + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) + _voice[i]->update(); } -void MidiDriver_CMS::noteOn(int channel, int note, int velocity) { +void MidiDriver_CMS::noteOn(int channelNr, int note, int velocity) { if (note < 21 || note > 116) return; if (velocity == 0) { - noteOff(channel, note); + noteOff(channelNr, note); return; } - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel == channel && _voice[i].note == note) { - _voice[i].sustained = 0; - voiceOff(i); - voiceOn(i, note, velocity); + for (int i = 0; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign == channelNr && _voice[i]->_note == note) { + if (_version > SCI_VERSION_0_LATE) { + _voice[i]->stop(); + _voice[i]->programChange(_channel[channelNr].program); + } + _voice[i]->noteOn(note, velocity); return; } } #ifdef CMS_DISABLE_VOICE_MAPPING - int voice = findVoiceBasic(channel); + int id = findVoiceBasic(channelNr); #else - int voice = findVoice(channel); + int id = findVoice(channelNr, note); #endif - if (voice != -1) - voiceOn(voice, note, velocity); -} - -int MidiDriver_CMS::findVoiceBasic(int channel) { - int voice = -1; - int oldestVoice = -1; - int oldestAge = -1; - - // Try to find a voice assigned to this channel that is free (round-robin) - for (int i = 0; i < ARRAYSIZE(_voice); i++) { - int v = (_channel[channel].lastVoiceUsed + i + 1) % ARRAYSIZE(_voice); - - if (_voice[v].note == 0xFF) { - voice = v; - break; - } - - // We also keep track of the oldest note in case the search fails - if (_voice[v].ticks > oldestAge) { - oldestAge = _voice[v].ticks; - oldestVoice = v; - } - } - - if (voice == -1) { - if (oldestVoice >= 0) { - voiceOff(oldestVoice); - voice = oldestVoice; - } else { - return -1; - } + if (id != -1) { + if (_version > SCI_VERSION_0_LATE) + _voice[id]->programChange(_channel[channelNr].program); + _voice[id]->noteOn(note, velocity); } - - _voice[voice].channel = channel; - _channel[channel].lastVoiceUsed = voice; - return voice; } -void MidiDriver_CMS::noteOff(int channel, int note) { +void MidiDriver_CMS::noteOff(int channelNr, int note) { for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel == channel && _voice[i].note == note) { - if (_channel[channel].hold != 0) - _voice[i].sustained = true; + if (_voice[i]->_assign == channelNr && _voice[i]->_note == note) { + if (_channel[channelNr].hold != 0) + _voice[i]->_sustained = true; else - _voice[i].turnOff = true; + _voice[i]->noteOff(); } } } -void MidiDriver_CMS::controlChange(int channel, int control, int value) { +void MidiDriver_CMS::controlChange(int channelNr, int control, int value) { + // The original SCI0 CMS drivers do not have Midi control 123. I support it nonetheless, + // since our current music engine seems to want to have it and it does not cause problems either. + if (_version < SCI_VERSION_1_EARLY && (control == 10 || control == 64)) + return; + switch (control) { case 7: - if (value) { - value >>= 3; - if (!value) - ++value; - } - - _channel[channel].volume = value; + _channel[channelNr].volume = (_version < SCI_VERSION_1_EARLY) ? MAX<uint8>((value & 0x78) << 1, 0x40) : (value ? MAX<uint8>(value >> 3, 1) : 0); break; case 10: - _channel[channel].pan = value; + _channel[channelNr].pan = value; break; case 64: - _channel[channel].hold = value; + _channel[channelNr].hold = value; if (!value) { - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel == channel && _voice[i].sustained) { - _voice[i].sustained = 0; - _voice[i].turnOff = true; + for (int i = 0; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign == channelNr && _voice[i]->_sustained) { + _voice[i]->_sustained = false; + _voice[i]->noteOff(); } } } @@ -477,14 +1032,14 @@ void MidiDriver_CMS::controlChange(int channel, int control, int value) { case 75: #ifndef CMS_DISABLE_VOICE_MAPPING - voiceMapping(channel, value); + voiceMapping(channelNr, value); #endif break; case 123: for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel == channel && _voice[i].note != 0xFF) - voiceOff(i); + if (_voice[i]->_assign == channelNr && _voice[i]->_note != 0xFF) + _voice[i]->stop(); } break; @@ -493,80 +1048,100 @@ void MidiDriver_CMS::controlChange(int channel, int control, int value) { } } -void MidiDriver_CMS::pitchWheel(int channelNr, int value) { - Channel &channel = _channel[channelNr]; - channel.pitchWheel = value; - channel.pitchAdditive = false; - channel.pitchModifier = 0; +void MidiDriver_CMS::programChange(int channelNr, int value) { + _channel[channelNr].program = value; + if (_version > SCI_VERSION_0_LATE) + return; - if (value < 0x2000) { - channel.pitchModifier = (0x2000 - value) / 170; - } else if (value > 0x2000) { - channel.pitchModifier = (value - 0x2000) / 170; - channel.pitchAdditive = true; + for (int i = 0; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign == channelNr) + _voice[i]->programChange(value); } +} - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel == channelNr && _voice[i].note != 0xFF) - noteSend(i); +void MidiDriver_CMS::pitchWheel(int channelNr, int value) { + _channel[channelNr].pitchWheel = value; + for (int i = 0; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign == channelNr && _voice[i]->_note != 0xFF) + _voice[i]->pitchWheel(); } } void MidiDriver_CMS::voiceMapping(int channelNr, int value) { int curVoices = 0; - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel == channelNr) + for (int i = 0; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign == channelNr) ++curVoices; } - curVoices += _channel[channelNr].extraVoices; + curVoices += _channel[channelNr].missingVoices; - if (curVoices == value) { - return; - } else if (curVoices < value) { - bindVoices(channelNr, value - curVoices); - } else { - unbindVoices(channelNr, curVoices - value); - donateVoices(); + if (curVoices < value) { + bindVoices(channelNr, value - curVoices, curVoices == 0 && value == 1, true); + } else if (curVoices > value) { + unbindVoices(channelNr, curVoices - value, value == 1); + donateVoices(value == 1); } } -void MidiDriver_CMS::bindVoices(int channel, int voices) { - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel == 0xFF) +void MidiDriver_CMS::bindVoices(int channelNr, int voices, bool bindSecondary, bool doProgramChange) { + int secondary = bindSecondary ? _numVoicesSecondary : 0; + + for (int i = 0; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign != 0xFF) continue; - Voice &voice = _voice[i]; - voice.channel = channel; + //debug("===== MidiDriver_CMS: hardware channel %02d is assigned to device channel %02d (primary voice) =======", i, channelNr); + _voice[i]->_assign = channelNr; + if (_voice[i]->_note != 0xFF) + _voice[i]->stop(); + + for (int ii = _numVoicesPrimary; ii < _numVoicesPrimary + secondary; ++ii) { + if (_voice[ii]->_assign != 0xFF) + continue; + + _voice[ii]->_assign = channelNr; + _voice[i]->_secondaryVoice = _voice[ii]; + + //debug("===== MidiDriver_CMS: hardware channel %02d is assigned to device channel %02d (secondary voice) =====", ii, channelNr); + break; + } - if (voice.note != 0xFF) - voiceOff(i); + // This will also release the secondary voice binding immediately if the current patch does + // not require such an extra channel. This condition will not be checked when called from initTrack(). + if (doProgramChange) + _voice[i]->programChange(_channel[channelNr].program); --voices; if (voices == 0) break; } - _channel[channel].extraVoices += voices; - - // The original called "PatchChange" here, since this just - // copies the value of _channel[channel].patch to itself - // it is left out here though. + _channel[channelNr].missingVoices += voices; } -void MidiDriver_CMS::unbindVoices(int channelNr, int voices) { +void MidiDriver_CMS::unbindVoices(int channelNr, int voices, bool bindSecondary) { + int secondary = bindSecondary ? _numVoicesSecondary : 0; Channel &channel = _channel[channelNr]; - if (channel.extraVoices >= voices) { - channel.extraVoices -= voices; + if (channel.missingVoices >= voices) { + channel.missingVoices -= voices; } else { - voices -= channel.extraVoices; - channel.extraVoices = 0; + voices -= channel.missingVoices; + channel.missingVoices = 0; + + for (int i = 0; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign == channelNr && _voice[i]->_note == 0xFF) { + _voice[i]->_assign = 0xFF; + + CMSVoice *sec = _voice[i]->_secondaryVoice; + if (sec) { + sec->stop(); + sec->_assign = 0xFF; + _voice[i]->_secondaryVoice = 0; + } - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel == channelNr - && _voice[i].note == 0xFF) { --voices; if (voices == 0) return; @@ -577,15 +1152,15 @@ void MidiDriver_CMS::unbindVoices(int channelNr, int voices) { uint16 voiceTime = 0; uint voiceNr = 0; - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel != channelNr) + for (int i = 0; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign != channelNr) continue; - uint16 curTime = _voice[i].turnOffTicks; + uint16 curTime = _voice[i]->_releaseDuration; if (curTime) curTime += 0x8000; else - curTime = _voice[i].ticks; + curTime = _voice[i]->_duration; if (curTime >= voiceTime) { voiceNr = i; @@ -593,72 +1168,112 @@ void MidiDriver_CMS::unbindVoices(int channelNr, int voices) { } } - _voice[voiceNr].sustained = 0; - voiceOff(voiceNr); - _voice[voiceNr].channel = 0xFF; + _voice[voiceNr]->_sustained = false; + _voice[voiceNr]->stop(); + _voice[voiceNr]->_assign = 0xFF; + + CMSVoice *sec = _voice[voiceNr]->_secondaryVoice; + if (sec) { + sec->stop(); + sec->_assign = 0xFF; + _voice[voiceNr]->_secondaryVoice = 0; + } + --voices; } while (voices != 0); } + + for (int i = _numVoicesPrimary; i < _numVoicesPrimary + secondary; ++i) { + if (_voice[i]->_assign != 0xFF) + continue; + + _voice[i]->_assign = channelNr; + if (_voice[i]->_note != 0xFF) + _voice[i]->stop(); + + for (int ii = 0; ii < _numVoicesPrimary; ++ii) { + if (_voice[ii]->_assign != channelNr) + continue; + _voice[ii]->_secondaryVoice = _voice[i]; + // This will release the secondary binding immediately if the current patch does not require such an extra channel. + _voice[ii]->programChange(_channel[channelNr].program); + break; + } + + if (_voice[i]->_assign == channelNr && _voice[i]->_note != 0xFF) + _voice[i]->stop(); + break; + } } -void MidiDriver_CMS::donateVoices() { +void MidiDriver_CMS::donateVoices(bool bindSecondary) { int freeVoices = 0; - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].channel == 0xFF) + for (int i = 0; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign == 0xFF) ++freeVoices; } if (!freeVoices) return; - for (uint i = 0; i < ARRAYSIZE(_channel); ++i) { + for (int i = 0; i < ARRAYSIZE(_channel); ++i) { Channel &channel = _channel[i]; - if (!channel.extraVoices) { + if (!channel.missingVoices) { continue; - } else if (channel.extraVoices < freeVoices) { - freeVoices -= channel.extraVoices; - channel.extraVoices = 0; - bindVoices(i, channel.extraVoices); + } else if (channel.missingVoices < freeVoices) { + freeVoices -= channel.missingVoices; + int missing = channel.missingVoices; + channel.missingVoices = 0; + bindVoices(i, missing, bindSecondary, true); } else { - channel.extraVoices -= freeVoices; - bindVoices(i, freeVoices); + channel.missingVoices -= freeVoices; + bindVoices(i, freeVoices, bindSecondary, true); return; } } } -int MidiDriver_CMS::findVoice(int channelNr) { +int MidiDriver_CMS::findVoice(int channelNr, int note) { Channel &channel = _channel[channelNr]; int voiceNr = channel.lastVoiceUsed; - int newVoice = 0; + int newVoiceAltSCI0 = (_version > SCI_VERSION_0_LATE) ? -2 : -1; uint16 newVoiceTime = 0; bool loopDone = false; do { ++voiceNr; - if (voiceNr == 12) + if (voiceNr == _numVoicesPrimary) voiceNr = 0; - Voice &voice = _voice[voiceNr]; - if (voiceNr == channel.lastVoiceUsed) loopDone = true; - if (voice.channel == channelNr) { - if (voice.note == 0xFF) { - channel.lastVoiceUsed = voiceNr; + if (_voice[voiceNr]->_assign == channelNr) { + if (_voice[voiceNr]->_note == 0xFF) { + channel.lastVoiceUsed = (_version > SCI_VERSION_0_LATE) ? voiceNr : _numVoicesPrimary - 1; return voiceNr; } - uint16 curTime = voice.turnOffTicks; + int cnt = 1; + for (int i = voiceNr + 1; i < _numVoicesPrimary; ++i) { + if (_voice[i]->_assign == channelNr) + ++cnt; + } + + // The SCI0 driver will (before resorting to the "note age test") simply return the first + // assigned voice as long as there are no other (primary) voices assigned to the midi part. + if (cnt == 1 && newVoiceAltSCI0 == -1) + newVoiceAltSCI0 = voiceNr; + + uint16 curTime = _voice[voiceNr]->_releaseDuration; if (curTime) curTime += 0x8000; else - curTime = voice.ticks; + curTime = _voice[voiceNr]->_duration; if (curTime >= newVoiceTime) { newVoice = voiceNr; @@ -667,147 +1282,109 @@ int MidiDriver_CMS::findVoice(int channelNr) { } } while (!loopDone); + if (newVoiceAltSCI0 >= 0) + return newVoiceAltSCI0; + if (newVoiceTime > 0) { voiceNr = newVoice; - _voice[voiceNr].sustained = 0; - voiceOff(voiceNr); - channel.lastVoiceUsed = voiceNr; - return voiceNr; - } else { - return -1; - } -} - -void MidiDriver_CMS::updateVoiceAmplitude(int voiceNr) { - Voice &voice = _voice[voiceNr]; - - if (voice.amplitudeTimer != 0 && voice.amplitudeTimer != 254) { - --voice.amplitudeTimer; - return; - } else if (voice.amplitudeTimer == 254) { - if (!voice.turnOff) - return; - - voice.amplitudeTimer = 0; - } + channel.lastVoiceUsed = _numVoicesPrimary - 1; - int nextDataIndex = voice.patchDataIndex; - uint8 timerData = 0; - uint8 amplitudeData = voice.patchDataPtr[nextDataIndex]; + if (_version > SCI_VERSION_0_LATE) { + _voice[voiceNr]->stop(); + channel.lastVoiceUsed = voiceNr; + } - if (amplitudeData == 255) { - timerData = amplitudeData = 0; - voiceOff(voiceNr); - } else { - timerData = voice.patchDataPtr[nextDataIndex + 1]; - nextDataIndex += 2; + return voiceNr; } - - voice.patchDataIndex = nextDataIndex; - voice.amplitudeTimer = timerData; - voice.amplitudeModifier = amplitudeData; + + return -1; } -void MidiDriver_CMS::setupVoiceAmplitude(int voiceNr) { - Voice &voice = _voice[voiceNr]; - uint amplitude = 0; +int MidiDriver_CMS::findVoiceBasic(int channelNr) { + int voice = -1; + int oldestVoice = -1; + int oldestAge = -1; - if (_channel[voice.channel].volume && voice.velocity - && voice.amplitudeModifier && _masterVolume) { - amplitude = _channel[voice.channel].volume * voice.velocity; - amplitude /= 0x0F; - amplitude *= voice.amplitudeModifier; - amplitude /= 0x0F; - amplitude *= _masterVolume; - amplitude /= 0x0F; + // Try to find a voice assigned to this channel that is free (round-robin) + for (int i = 0; i < _numVoicesPrimary; i++) { + int v = (_channel[channelNr].lastVoiceUsed + i + 1) % _numVoicesPrimary; - if (!amplitude) - ++amplitude; + if (_voice[v]->_note == 0xFF) { + voice = v; + break; + } + + // We also keep track of the oldest note in case the search fails + if (_voice[v]->_duration > oldestAge) { + oldestAge = _voice[v]->_duration; + oldestVoice = v; + } } - uint8 amplitudeData = 0; - int pan = _channel[voice.channel].pan >> 2; - if (pan >= 16) { - amplitudeData = (amplitude * (31 - pan) / 0x0F) & 0x0F; - amplitudeData |= (amplitude << 4); - } else { - amplitudeData = (amplitude * pan / 0x0F) & 0x0F; - amplitudeData <<= 4; - amplitudeData |= amplitude; + if (voice == -1) { + if (oldestVoice >= 0) { + _voice[oldestVoice]->stop(); + voice = oldestVoice; + } else { + return -1; + } } - if (!_playSwitch) - amplitudeData = 0; + _voice[voice]->_assign = channelNr; + _channel[channelNr].lastVoiceUsed = (_version > SCI_VERSION_0_LATE) ? voice : 0; + return voice; +} - if (voiceNr >= 6) - writeToChip2(voiceNr - 6, amplitudeData); - else - writeToChip1(voiceNr, amplitudeData); +void MidiDriver_CMS::writeToChip(int chip, int address, int data) { + assert(chip == 0 || chip == 1); + _cms->portWrite(0x221 + (chip << 1), address); + _cms->portWrite(0x220 + (chip << 1), data); } void MidiDriver_CMS::generateSamples(int16 *buffer, int len) { - while (len) { - if (!_samplesTillCallback) { - for (uint i = 0; i < ARRAYSIZE(_voice); ++i) { - if (_voice[i].note == 0xFF) - continue; - - ++_voice[i].ticks; - if (_voice[i].turnOff) - ++_voice[i].turnOffTicks; - - updateVoiceAmplitude(i); - setupVoiceAmplitude(i); - } - - _samplesTillCallback = _samplesPerCallback; - _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; - if (_samplesTillCallbackRemainder >= _timerFreq) { - _samplesTillCallback++; - _samplesTillCallbackRemainder -= _timerFreq; - } - } - - int32 render = MIN<int32>(len, _samplesTillCallback); - len -= render; - _samplesTillCallback -= render; - _cms->readBuffer(buffer, render); - buffer += render * 2; - } + _cms->readBuffer(buffer, len); } - class MidiPlayer_CMS : public MidiPlayer { public: - MidiPlayer_CMS(SciVersion version) : MidiPlayer(version) { - } - - int open(ResourceManager *resMan) { - if (_driver) - return MidiDriver::MERR_ALREADY_OPEN; + MidiPlayer_CMS(SciVersion version) : MidiPlayer(version) {} - _driver = new MidiDriver_CMS(g_system->getMixer(), resMan); - int driverRetVal = _driver->open(); - if (driverRetVal != 0) - return driverRetVal; - - return 0; - } + int open(ResourceManager *resMan); + void close(); - void close() { - _driver->setTimerCallback(0, 0); - _driver->close(); - delete _driver; - _driver = nullptr; - } + void MidiPlayer_CMS::initTrack(SciSpan<const byte>& trackData); bool hasRhythmChannel() const { return false; } - byte getPlayId() const { return 9; } + byte getPlayId() const { return _version > SCI_VERSION_0_LATE ? 9 : 4; } int getPolyphony() const { return 12; } - void playSwitch(bool play) { static_cast<MidiDriver_CMS *>(_driver)->playSwitch(play); } + void playSwitch(bool play) { _driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, play ? 1 : 0); } }; +int MidiPlayer_CMS::open(ResourceManager *resMan) { + if (_driver) + return MidiDriver::MERR_ALREADY_OPEN; + + _driver = new MidiDriver_CMS(g_system->getMixer(), resMan, _version); + int driverRetVal = _driver->open(); + if (driverRetVal != 0) + return driverRetVal; + + return 0; +} + +void MidiPlayer_CMS::close() { + _driver->setTimerCallback(0, 0); + _driver->close(); + delete _driver; + _driver = nullptr; +} + +void MidiPlayer_CMS::initTrack(SciSpan<const byte>& trackData) { + if (_driver) + static_cast<MidiDriver_CMS*>(_driver)->initTrack(trackData); +} + MidiPlayer *MidiPlayer_CMS_create(SciVersion version) { return new MidiPlayer_CMS(version); } diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp index 5977cf0177..dc62acb395 100644 --- a/engines/sci/sound/music.cpp +++ b/engines/sci/sound/music.cpp @@ -84,8 +84,9 @@ void SciMusic::init() { if (g_sci->_features->useAltWinGMSound()) deviceFlags |= MDT_PREFER_GM; - // Currently our CMS implementation only supports SCI1(.1) - if (getSciVersion() >= SCI_VERSION_1_EGA_ONLY && getSciVersion() <= SCI_VERSION_1_1) + // 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 (g_sci->getPlatform() == Common::kPlatformFMTowns) { |