diff options
-rw-r--r-- | engines/sci/sound/drivers/cms.cpp | 1396 | ||||
-rw-r--r-- | engines/sci/sound/music.cpp | 5 |
2 files changed, 960 insertions, 441 deletions
diff --git a/engines/sci/sound/drivers/cms.cpp b/engines/sci/sound/drivers/cms.cpp index 8b92432cb9..750de85229 100644 --- a/engines/sci/sound/drivers/cms.cpp +++ b/engines/sci/sound/drivers/cms.cpp @@ -33,14 +33,154 @@ 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 recalculateEnvelopeLevels(); + 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; + + 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 _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 +188,98 @@ 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; - Voice _voice[12]; + bool _playSwitch; + uint16 _masterVolume; + + const int _actualTimerInterval; + const int _reqTimerInterval; + int _updateTimer; + int _rate; - void voiceOn(int voice, int note, int velocity); - void voiceOff(int voice); + SciVersion _version; +}; - void noteSend(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 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 CMSVoice::sendFrequency() { + uint8 frequency = 0; + uint8 octave = 0; - void voiceMapping(int channel, int value); - void bindVoices(int channel, int voices); - void unbindVoices(int channel, int voices); - void donateVoices(); - int findVoice(int channel); + recalculateFrequency(frequency, octave); - int findVoiceBasic(int channel); + uint8 octaveData = _octaveRegs[_id >> 1]; + octaveData = (_id & 1) ? (octaveData & 0x0F) | (octave << 4) : (octaveData & 0xF0) | octave; - void updateVoiceAmplitude(int voice); - void setupVoiceAmplitude(int voice); + cmsWrite(8 + _regOffset, frequency); + cmsWrite(0x10 + (_regOffset >> 1), octaveData); +} - uint8 _octaveRegs[2][3]; +void CMSVoice::cmsWrite(uint8 reg, uint8 val) { + _cms->portWrite(0x221 + _portOffset, reg); + _cms->portWrite(0x220 + _portOffset, val); - static const int _timerFreq = 60; + if (reg >= 16 && reg <= 18) + _octaveRegs[_id >> 1] = val; +} - static const int _frequencyTable[]; - static const int _velocityTable[]; +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 +294,438 @@ 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) { +} + +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; + + if (_secondaryVoice) + _secondaryVoice->noteOn(note, 127); +} + +void CMSVoice_V0::noteOff() { + if (!_driver->property(MidiDriver_CMS::MIDI_PROP_PLAYSWITCH, 0xFFFF) || !_envTL) + return; + + _note = 0xFF; + _envState = kRelease; + if (_secondaryVoice) + _secondaryVoice->noteOff(); +} + +void CMSVoice_V0::stop() { + _note = 0xFF; + if (_envState != kReady) + _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; + } + + recalculateEnvelopeLevels(); + + switch (_envState) { + case kReady: + _envNote = 0xFF; + return; + + case kRestart: + if (_envPAC) { + --_envPAC; + break; + } else { + _currentLevel = ((_currentLevel >> 1) > _envAR) ? ((_currentLevel >> 1) - _envAR) : 0; + //_currentLevel = ((_currentLevel >> 1) > (int8)_envAR) ? ((_currentLevel >> 1) - _envAR1) & 0xFF : (_envAR - _envAR1) & 0xFF; + _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 pbVal = (_driver->property(MidiDriver_CMS::MIDI_PROP_CHANNEL_PITCHWHEEL, _assign) & 0x7FFF) - 0x2000; + int16 pbEff = _pitchWheelTable[ABS(pbVal) >> 7] * ((pbVal < 0) ? -1 : 1); + int frequency = note * 4 + pbEff; + + 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::recalculateEnvelopeLevels() { + 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]; +} + +void CMSVoice_V0::selectEnvelope(int id) { + SciSpan<const uint8> in = _patchData.subspan(512 + ((id & 0x1F) << 3)); + _envAR = *in++; + _envTL = *in++; + _envDR = *in++; + _envSLI = *in++; + _envRR = *in++; + /*unused*/in++; + _vbrMod = *in++; + _vbrSteps = *in++; + _vbrOn = _vbrMod; + _vbrCur = _vbrMod; + _vbrState = _vbrSteps & 0x0F; + _vbrPhase = 0; +} + +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) { +} + +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(1000000 / _baseFreq), _reqTimerInterval(1000000/60), _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 +734,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 +793,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 +811,7 @@ void MidiDriver_CMS::send(uint32 b) { break; case 0xC0: - _channel[channel].patch = op1; + programChange(channel, op1); break; case 0xE0: @@ -261,215 +829,140 @@ 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) and it + // is controlled through the primary voice. It will not receive its own separate commands. The + // main purpose seems providing 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() { + for (_updateTimer -= _actualTimerInterval; _updateTimer <= 0; _updateTimer += _reqTimerInterval) { + for (uint i = 0; i < ARRAYSIZE(_voice); ++i) + _voice[i]->update(); } - - if (chipNumber == 1) - writeToChip2(0x10 + (voiceNr >> 1), octaveData); - else - writeToChip1(0x10 + (voiceNr >> 1), octaveData); } -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 (id != -1) { + if (_version > SCI_VERSION_0_LATE) + _voice[id]->programChange(_channel[channelNr].program); + _voice[id]->noteOn(note, velocity); } - - if (voice == -1) { - if (oldestVoice >= 0) { - voiceOff(oldestVoice); - voice = oldestVoice; - } else { - return -1; - } - } - - _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 +970,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 +986,103 @@ 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; - - if (curVoices == value) { - return; - } else if (curVoices < value) { - bindVoices(channelNr, value - curVoices); - } else { - unbindVoices(channelNr, curVoices - value); - donateVoices(); - } + curVoices += _channel[channelNr].missingVoices; + + 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); + }/*else if (_version < SCI_VERSION_1_EARLY && value == 1) { + // The purpose of these lines would be to fill up missing secondary voices. + // I have commented them out, since the original driver doesn't do that either. + unbindVoices(channelNr, 1, true); + bindVoices(channelNr, 1, true, true); + }*/ } -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; + _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; - if (voice.note != 0xFF) - voiceOff(i); + _voice[ii]->_assign = channelNr; + _voice[i]->_secondaryVoice = _voice[ii]; + + break; + } + + // 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 +1093,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 +1109,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 +1223,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]; + channel.lastVoiceUsed = _numVoicesPrimary - 1; - if (voice.amplitudeTimer != 0 && voice.amplitudeTimer != 254) { - --voice.amplitudeTimer; - return; - } else if (voice.amplitudeTimer == 254) { - if (!voice.turnOff) - return; - - voice.amplitudeTimer = 0; - } - - 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 initTrack(SciSpan<const byte>& header); 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>& header) { + if (_driver) + static_cast<MidiDriver_CMS*>(_driver)->initTrack(header); +} + 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) { |