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) { | 
