[Scummvm-git-logs] scummvm master -> c9096b0ad822b89f3fef6f0917983cc57f1027b8

bluegr noreply at scummvm.org
Wed Sep 11 06:56:54 UTC 2024


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
c9096b0ad8 SCI: fix PCJr sound driver volumes


Commit: c9096b0ad822b89f3fef6f0917983cc57f1027b8
    https://github.com/scummvm/scummvm/commit/c9096b0ad822b89f3fef6f0917983cc57f1027b8
Author: athrxx (athrxx at scummvm.org)
Date: 2024-09-11T09:56:51+03:00

Commit Message:
SCI: fix PCJr sound driver volumes

Turns out that the volume consists of many components,
none of which were currently being implemented properly.
I have added the necessary code for SCI0 and SCI1.

The driver still isn't 100% complete, e. g. I haven't added
pitch bend support yet. Also, it might require a couple
more accuracy fixes.

And while I did avoid a rewrite and just added some code,
it might still be a good idea to clean this up a bit.

Changed paths:
    engines/sci/sound/drivers/pcjr.cpp


diff --git a/engines/sci/sound/drivers/pcjr.cpp b/engines/sci/sound/drivers/pcjr.cpp
index c1fd4c1de56..1bb304fca61 100644
--- a/engines/sci/sound/drivers/pcjr.cpp
+++ b/engines/sci/sound/drivers/pcjr.cpp
@@ -20,6 +20,7 @@
  */
 
 #include "sci/sound/drivers/mididriver.h"
+#include "sci/resource/resource.h"
 
 #include "audio/softsynth/emumidi.h"
 
@@ -65,16 +66,23 @@ public:
 		kMaxChannels = 3
 	};
 
-	MidiDriver_PCJr(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) { }
-	~MidiDriver_PCJr() override { }
+	enum Properties {
+		kPropNone = 0,
+		kPropVolume
+	};
+
+	MidiDriver_PCJr(Audio::Mixer *mixer, SciVersion version, bool pcsMode);
+	~MidiDriver_PCJr() override;
 
 	// MidiDriver
-	int open() override { return open(kMaxChannels); }
+	int open() override;
 	void close() override;
 	void send(uint32 b) override;
 	MidiChannel *allocateChannel() override { return nullptr; }
 	MidiChannel *getPercussionChannel() override { return nullptr; }
-
+	uint32 property(int prop, uint32 value) override;
+	void initTrack(SciSpan<const byte> &header);
+	
 	// AudioStream
 	bool isStereo() const override { return false; }
 	int getRate() const override { return _mixer->getOutputRate(); }
@@ -82,160 +90,723 @@ public:
 	// MidiDriver_Emulated
 	void generateSamples(int16 *buf, int len) override;
 
-	int open(int channels);
 private:
-	int _channels_nr;
-	int _global_volume; // Base volume
-	int _volumes[kMaxChannels];
-	int _notes[kMaxChannels]; // Current halftone, or 0 if off
+	bool loadInstruments(Resource &resource);
+	void updateChannelVolume(int chan);
+	void updateSounds();
+
+	void envAT(byte chan);
+	void envST(byte chan);
+	void envRL(byte chan);
+	void envINST(byte chan);
+	void chanNoteOn(byte chan, byte note, byte velocity);
+	void chanOff(byte chan);
+
+	void noteOn(byte part, byte note, byte velocity);
+	void noteOff(byte part, byte note);
+	void controlChange(byte part, byte controller, byte value);
+	void programChange(byte part, byte program);
+	void pitchBend(byte part, uint16 value);
+
+	void controlChangeSustain(byte part, byte sus);
+	void controlChangePolyphony(byte part, byte numChan);
+	void addChannels(byte part, byte num);
+	void dropChannels(byte part, byte num);
+	void assignFreeChannels(byte part);
+	byte allocateChannel(byte part);
+
+	byte _globalVolume; // Base volume
+	byte _chanVolume[kMaxChannels];
+	byte _chanVelocity[kMaxChannels];
+	int8 _chanEnvVolume[kMaxChannels];
+	uint16 _smpVolume[kMaxChannels];
+	byte _notes[kMaxChannels]; // Current halftone, or 0 if off
+	byte _envState[kMaxChannels];
+	byte _envCount[kMaxChannels];
+	byte _envCount2[kMaxChannels];
+	const byte *_envData[kMaxChannels];
+	int8 _envAttn[kMaxChannels];
 	int _freq_count[kMaxChannels];
-	int _channel_assigner;
-	int _channels_assigned;
-	int _chan_nrs[kMaxChannels];
+	byte _partMapping[kMaxChannels];
+	uint16 _duration[kMaxChannels];
+	uint16 _releaseDuration[kMaxChannels];
+	byte _chanSustain[kMaxChannels];
+	byte _chanRelease[kMaxChannels];
+	byte _chanMapping[16];
+	byte _chanMissing[16];
+	byte _program[16];
+	byte _partSustain[16];
+	uint32 _sndUpdateSmpQty;
+	uint32 _sndUpdateSmpQtyRem;
+	uint32 _sndUpdateCountDown;
+	uint32 _sndUpdateCountDownRem;
+	const uint16 *_smpVolTable;
+	const uint16 *_instrumentOffsets;
+	const uint8 *_instrumentData;
+	const SciVersion _version;
+	const byte _numChannels;
+	const bool _pcsMode;
 };
 
 void MidiDriver_PCJr::send(uint32 b) {
 	byte command = b & 0xff;
 	byte op1 = (b >> 8) & 0xff;
 	byte op2 = (b >> 16) & 0xff;
-	int mapped_chan = -1;
-	int chan_nr = command & 0xf;
-
-	// First, test for channel having been assigned already
-	if (_channels_assigned & (1 << chan_nr)) {
-		// Already assigned this channel number:
-		for (int i = 0; i < _channels_nr; i++)
-			if (_chan_nrs[i] == chan_nr) {
-				mapped_chan = i;
-				break;
-			}
-	} else if ((command & 0xe0) == 0x80) {
-		// Assign new channel round-robin
-
-		// Mark channel as unused:
-		if (_chan_nrs[_channel_assigner] >= 0)
-			_channels_assigned &= ~(1 << _chan_nrs[_channel_assigner]);
-
-		// Remember channel:
-		_chan_nrs[_channel_assigner] = chan_nr;
-		// Mark channel as used
-		_channels_assigned |= (1 << _chan_nrs[_channel_assigner]);
-
-		// Save channel for use later in this call:
-		mapped_chan = _channel_assigner;
-		// Round-ropin iterate channel assigner:
-		_channel_assigner = (_channel_assigner + 1) % _channels_nr;
-	}
-
-	if (mapped_chan == -1)
-		return;
+	byte part = command & 0x0f;
 
 	switch (command & 0xf0) {
-
 	case 0x80:
-		if (op1 == _notes[mapped_chan])
-			_notes[mapped_chan] = 0;
+		noteOff(part, op1);
 		break;
-
 	case 0x90:
-		if (!op2) {
-			if (op1 == _notes[mapped_chan])
-				_notes[mapped_chan] = 0;
-		} else {
-			_notes[mapped_chan] = op1;
-			_volumes[mapped_chan] = op2;
-		}
+		if (!op2)
+			noteOff(part, op1);
+		else 
+			noteOn(part, op1, op2);	
 		break;
-
 	case 0xb0:
-		if ((op1 == SCI_MIDI_CHANNEL_NOTES_OFF) || (op1 == SCI_MIDI_CHANNEL_SOUND_OFF))
-			_notes[mapped_chan] = 0;
+		controlChange(part, op1, op2);
+		break;
+	case 0xc0:
+		programChange(part, op1);
+		break;
+	case 0xe0:
+		pitchBend(part, op1 | (op2 << 7));
 		break;
-
 	default:
 		debug(2, "Unused MIDI command %02x %02x %02x", command, op1, op2);
-		break; /* ignore */
+		break;
+	}
+}
+
+uint32 MidiDriver_PCJr::property(int prop, uint32 value) {
+	uint32 res = 0;
+	value &= 0xffff;
+
+	switch (prop) {
+	case kPropVolume:
+		res = _globalVolume;
+		if (value != 0xffff) {
+			_globalVolume = value;
+			for (int i = 0; i < _numChannels; ++i)
+				updateChannelVolume(i);
+		}
+		break;
+	default:
+		break;
+	}
+
+	return res;
+}
+
+void MidiDriver_PCJr::initTrack(SciSpan<const byte> &header) {
+	if (!_isOpen || _version > SCI_VERSION_0_LATE)
+		return;
+
+	uint8 readPos = 0;
+	uint8 caps = header.getInt8At(readPos++);
+	if (caps != 0 && caps != 2)
+		return;
+
+	for (int i = 0; i < _numChannels; ++i) {
+		_smpVolume[i] = 0;
+		_chanVolume[i] = 96;
+		_chanVelocity[i] = 0;
+		_notes[i] = 0xFF;
+		_partMapping[i] = 0x10;
+		_envState[i] = 0;
+		_envCount[i] = 0;
+		updateChannelVolume(i);
+	}
+
+	if (_version == SCI_VERSION_0_EARLY) {
+		byte chanFlag = _pcsMode ? 0x04 : 0x02;
+		for (int i = 0, numAssigned = 0; i < 16 && numAssigned < _numChannels; ++i) {
+			uint8 f = header.getInt8At(++readPos);
+			if ((!(f & 8) || (f & 1)) && (f & chanFlag))
+				_partMapping[numAssigned++] = i;
+		}
+	} else {
+		byte chanFlag = _pcsMode ? 0x20 : 0x10;
+		for (int i = 0, numAssigned = 0; i < 16 && numAssigned < _numChannels; ++i) {
+			uint8 f = header.getInt8At(++readPos);
+			readPos++;
+			if (f & chanFlag)
+				_partMapping[numAssigned++] = i;
+		}
 	}
 }
 
 void MidiDriver_PCJr::generateSamples(int16 *data, int len) {
-	int i;
-	int chan;
 	int freq[kMaxChannels];
 	int frequency = getRate();
 
-	for (chan = 0; chan < _channels_nr; chan++)
+	for (int chan = 0; chan < _numChannels; chan++)
 		freq[chan] = get_freq(_notes[chan]);
 
-	for (i = 0; i < len; i++) {
-		int16 result = 0;
+	for (int i = 0; i < len; i++) {
+		if (!--_sndUpdateCountDown) {
+			_sndUpdateCountDown = _sndUpdateSmpQty;
+			_sndUpdateCountDownRem += _sndUpdateSmpQtyRem;
+			while (_sndUpdateCountDownRem >= (_sndUpdateSmpQty << 16)) {
+				_sndUpdateCountDownRem -= (_sndUpdateSmpQty << 16);
+				++_sndUpdateCountDown;
+			}
+			updateSounds();
+		}
 
-		for (chan = 0; chan < _channels_nr; chan++)
-			if (_notes[chan]) {
-				int volume = (_global_volume * _volumes[chan])
-				             >> VOLUME_SHIFT;
+		int16 result = 0;
 
+		for (int chan = 0; chan < _numChannels; chan++)
+			if (_notes[chan] != 0xFF) {
+				uint16 volume = _smpVolume[chan];
 				_freq_count[chan] += freq[chan];
 				while (_freq_count[chan] >= (frequency << 1))
 					_freq_count[chan] -= (frequency << 1);
 
 				if (_freq_count[chan] - freq[chan] < 0) {
-					/* Unclean rising edge */
+					// Unclean rising edge
 					int l = volume << 1;
-					result += -volume + (l * _freq_count[chan]) / freq[chan];
-				} else if (_freq_count[chan] >= frequency
-				           && _freq_count[chan] - freq[chan] < frequency) {
-					/* Unclean falling edge */
+					result += (int16)~volume + (l * _freq_count[chan]) / freq[chan];
+				} else if (_freq_count[chan] >= frequency && _freq_count[chan] - freq[chan] < frequency) {
+					// Unclean falling edge
 					int l = volume << 1;
 					result += volume - (l * (_freq_count[chan] - frequency)) / freq[chan];
 				} else {
 					if (_freq_count[chan] < frequency)
 						result += volume;
 					else
-						result += -volume;
+						result += (int16)~volume;
 				}
 			}
 		data[i] = result;
 	}
 }
 
-int MidiDriver_PCJr::open(int channels) {
+MidiDriver_PCJr::MidiDriver_PCJr(Audio::Mixer *mixer, SciVersion version, bool pcsMode) : MidiDriver_Emulated(mixer), _version(version), _pcsMode(pcsMode),
+	_numChannels(pcsMode ? 1 : 3), _globalVolume(0), _instrumentOffsets(nullptr), _instrumentData(nullptr), _smpVolTable(nullptr) {
+	for (int i = 0; i < kMaxChannels; ++i) {
+		_chanVolume[i] = 0;
+		_chanVelocity[i] = 0;
+		_chanEnvVolume[i] = 0;
+		_smpVolume[i] = 0;
+		_notes[i] = 0;
+		_envState[i] = 0;
+		_envCount[i] = 0;
+		_envCount2[i] = 0;
+		_envAttn[i] = 0;
+		_freq_count[i] = 0;
+		_partMapping[i] = 0;
+		_duration[i] = 0;
+		_releaseDuration[i] = 0;
+		_chanSustain[i] = 0;
+		_chanRelease[i] = 0;
+		_envData[i] = nullptr;
+	}
+
+	for (int i = 0; i < 16; ++i) {
+		_chanMapping[i] = 0;
+		_chanMissing[i] = 0;
+		_program[i] = 0;
+		_partSustain[i] = 0;
+	}
+
+	uint16 *smpVolTable = new uint16[16]();
+	for (int i = 0; i < 15; ++i) // The last entry is left at zero.
+		smpVolTable[i] = (double)((32767 & ~_numChannels) / _numChannels) / pow(10.0, (double)i / 10.0);
+	_smpVolTable = smpVolTable;
+
+	_sndUpdateSmpQty = (mixer->getOutputRate() << 16) / 0x3C0000;
+	_sndUpdateSmpQtyRem = (mixer->getOutputRate() << 16) % 0x3C0000;
+	_sndUpdateCountDown = _sndUpdateSmpQty;
+	_sndUpdateCountDownRem = 0;
+};
+
+MidiDriver_PCJr::~MidiDriver_PCJr() {
+	close();
+	delete[] _smpVolTable;
+	delete[] _instrumentOffsets;
+	delete[] _instrumentData;
+}
+
+int MidiDriver_PCJr::open() {
 	if (_isOpen)
 		return MERR_ALREADY_OPEN;
 
-	if (channels > kMaxChannels)
-		return -1;
-
-	_channels_nr = channels;
-	_global_volume = 100;
-	for (int i = 0; i < _channels_nr; i++) {
-		_volumes[i] = 100;
-		_notes[i] = 0;
+	_globalVolume = 15;
+	for (int i = 0; i < _numChannels; i++) {
+		_smpVolume[i] = 0;
+		_chanVolume[i] = 15;
+		_chanVelocity[i] = 0;
+		_chanEnvVolume[i] = 15;
+		_partMapping[i] = 0xFF;
+		_notes[i] = 0xFF;
 		_freq_count[i] = 0;
-		_chan_nrs[i] = -1;
+		_envState[i] = 0;
+		_envCount[i] = 0;
+		updateChannelVolume(i);
 	}
-	_channel_assigner = 0;
-	_channels_assigned = 0;
 
-	MidiDriver_Emulated::open();
+	if (_version > SCI_VERSION_0_LATE && !_pcsMode) {
+		ResourceManager *resMan = g_sci->getResMan();
+		Resource *resource = resMan->findResource(ResourceId(kResourceTypePatch, 101), false);
+		if (resource == nullptr)
+			return MERR_CANNOT_CONNECT;
+		if (!loadInstruments(*resource))
+			return MERR_CANNOT_CONNECT;
+	}
+
+	int res = MidiDriver_Emulated::open();
 
 	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO);
 
-	return 0;
+	return res;
 }
 
 void MidiDriver_PCJr::close() {
 	_mixer->stopHandle(_mixerSoundHandle);
 }
 
+bool MidiDriver_PCJr::loadInstruments(Resource &resource) {
+	uint16 *instrumentOffsets = new uint16[128]();
+	if (!instrumentOffsets)
+		return false;
+
+	uint32 readPos = 0;
+	for (int i = 0; i < 128; ++i) {
+		int in = resource.getUint16LEAt(readPos) - 0x100;
+		if (in < 0) {
+			warning("%s(): Error reading instrument patch resource", __FUNCTION__);
+			delete[] instrumentOffsets;
+			return false;
+		}
+		instrumentOffsets[i] = in;
+		readPos += 2;
+	}
+
+	uint8 *instrumentData = new uint8[770]();
+	if (!instrumentData)
+		return false;
+
+	uint32 writePos = 0;
+	for (int i = 0; i < 40; ++i) {
+		for (uint8 in = 0; writePos < 770 && in != 0xFF; ) {
+			in = resource.getUint8At(readPos++);
+			instrumentData[writePos++] = in;			
+		}
+	}
+
+	_instrumentOffsets = instrumentOffsets;
+	_instrumentData = instrumentData;
+
+	return true;
+}
+
+void MidiDriver_PCJr::updateChannelVolume(int chan) {
+	assert(chan >= 0 && chan < kMaxChannels);
+	int attn = 0;
+
+	if (_pcsMode) {
+		// The PC speaker has a fixed volume level. The original will turn it off if the volume is zero
+		// or otherwise turn it on. We just use the PCJr volume table for the master volume, so that our
+		// volume controls still work.
+		attn = 15 - _globalVolume;
+	} else if (_version <= SCI_VERSION_0_LATE) {
+		int veloAttn = 3 - (_chanVelocity[chan] >> 5);
+		int volAttn = 15 - (_chanVolume[chan] >> 3);
+		attn = volAttn + veloAttn + _chanEnvVolume[chan];
+		if (attn >= 15) {
+			attn = 15;
+			if (_envState[chan] >= 2)
+				chanOff(chan);
+		} else if (attn < 0) {
+			attn = 0;
+		}
+		attn = CLIP<int>(attn + (15 - _globalVolume), volAttn, 15);
+	} else {
+		static const byte veloTable[16] = { 0x01, 0x03, 0x06, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F };
+		int velo = _chanVelocity[chan] ? veloTable[_chanVelocity[chan] >> 3] : 0;
+		int vol = _chanVolume[chan] ? MAX<int>(1, _chanVolume[chan] >> 3) : 0;
+		int tl = ((vol * velo / 15) * _chanEnvVolume[chan] / 15) * _globalVolume;
+		if (tl > 0 && tl < 15)
+			tl = 15;
+		attn = 15 - tl / 15;
+	}
+
+	_smpVolume[chan] = _smpVolTable[attn];
+}
+
+void MidiDriver_PCJr::updateSounds() {
+	if (_pcsMode)
+		return;
+
+	for (int i = 0; i < _numChannels; i++) {
+		if (_notes[i] == 0xFF)
+			continue;
+
+		if (_version > SCI_VERSION_0_LATE) {
+			++_duration[i];
+			if (_chanRelease[i])
+				++_releaseDuration[i];
+			envINST(i);
+			updateChannelVolume(i);
+		} else {
+			switch (_envState[i]) {
+			case 1:
+				envAT(i);
+				break;
+			case 2:
+				envST(i);
+				break;
+			case 3:
+				envRL(i);
+				break;
+			default:
+				break;
+			}
+		}
+	}
+}
+
+void MidiDriver_PCJr::envAT(byte chan) {
+	static const int8 envTable[] = { 0, -2, -3, -2, -1, 0, 127 };
+	byte c = _envCount[chan]++;
+	if (envTable[c] == 127) {
+		_envState[chan]++;
+		_envCount[chan] = 0;
+		_envCount2[chan] = 20;
+		envST(chan);
+	} else {
+		_chanEnvVolume[chan] = _envAttn[chan] = envTable[c];
+		updateChannelVolume(chan);
+	}
+}
+
+void MidiDriver_PCJr::envST(byte chan) {
+	if (!_envCount2[chan] || --_envCount2[chan])
+		return;
+	_chanEnvVolume[chan] = ++_envAttn[chan];
+	_envCount2[chan] = 20;
+	updateChannelVolume(chan);
+}
+
+void MidiDriver_PCJr::envRL(byte chan) {
+	static const byte envTable[] = { 1, 1, 1, 2, 2, 3, 3, 4, 4, 6, 6, 7, 8, 9, 10, 11, 12, 127 };
+	byte c = _envCount[chan]++;
+	_chanEnvVolume[chan] = (envTable[c] == 127) ? 15 : _envAttn[chan] + envTable[c];
+	updateChannelVolume(chan);
+}
+
+void MidiDriver_PCJr::envINST(byte chan) {
+	if (_envCount[chan] == 0xFE) {
+		if (_chanRelease[chan])
+			_envCount[chan] = 0;
+		else
+			return;
+	}
+
+	if (_envCount[chan] == 0) {
+		byte a = _envData[chan][_envState[chan] << 1];
+		if (a == 0xFF) {
+			chanOff(chan);
+			_envCount[chan] = 0;
+		} else {
+			_envCount[chan] = _envData[chan][(_envState[chan] << 1) + 1];
+			_chanEnvVolume[chan] = a;
+			++_envState[chan];
+		}
+	} else {
+		--_envCount[chan];
+	}
+}
+
+void MidiDriver_PCJr::chanNoteOn(byte chan, byte note, byte velocity) {
+	_notes[chan] = note;
+	_chanVelocity[chan] = velocity;
+	_envState[chan] = _version > SCI_VERSION_0_LATE ? 0 : 1;
+	_envCount[chan] = 0;
+	_duration[chan] = 0;
+	_releaseDuration[chan] = 0;
+	_chanRelease[chan] = 0;
+	_chanSustain[chan] = 0;
+
+	if (_pcsMode)
+		return;
+
+	if (_version <= SCI_VERSION_0_LATE) {
+		envAT(chan);
+	} else {
+		assert(_instrumentOffsets);
+		assert(_instrumentData);
+		_envData[chan] = &_instrumentData[_instrumentOffsets[_program[_partMapping[chan]]]];
+	}
+}
+
+void MidiDriver_PCJr::chanOff(byte chan) {
+	_notes[chan] = 0xFF;
+	_duration[chan] = 0;
+	_releaseDuration[chan] = 0;
+	_chanSustain[chan] = 0;
+	_chanRelease[chan] = 0;
+	_envState[chan] = 0;
+	_envAttn[chan] = 0;
+	_chanEnvVolume[chan] = 0;
+	_envCount[chan] = 0;
+}
+
+void MidiDriver_PCJr::noteOn(byte part, byte note, byte velocity) {
+	if (_pcsMode) {
+		if (_partMapping[0] != part)
+			return;
+		chanOff(0);
+		if (note < 24 || note > 119)
+			return;
+		chanNoteOn(0, note, velocity);
+		return;
+	} else if (_version > SCI_VERSION_0_LATE) {
+		if (note < 21 || note > 116)
+			return;
+
+		for (int i = 0; i < _numChannels; ++i) {
+			if (_partMapping[i] != part || _notes[i] != note)
+				continue;
+			chanOff(i);
+			chanNoteOn(i, note, velocity);
+			return;
+		}
+	}
+
+	byte c = allocateChannel(part);
+	if (c != 0xFF)
+		chanNoteOn(c, note, velocity);
+}
+
+void MidiDriver_PCJr::noteOff(byte part, byte note) {
+	for (int i = 0; i < _numChannels; ++i) {
+		if (_partMapping[i] != part || _notes[i] != note)
+			continue;
+
+		if (_pcsMode) {
+			chanOff(i);
+		} else if (_version > SCI_VERSION_0_LATE) {
+			if (_partSustain[part])
+				_chanSustain[i] = 1;
+			else
+				_chanRelease[i] = 1;
+		} else {
+			_envState[i] = 3;
+			_envCount[i] = 0;
+			envRL(i);
+		}
+	}	
+}
+
+void MidiDriver_PCJr::controlChange(byte part, byte controller, byte value) {
+	switch (controller) {
+	case 7:
+		for (int i = 0; i < _numChannels; ++i) {
+			if (_partMapping[i] == part)
+				_chanVolume[i] = value;
+		}
+		break;
+	case 64:
+		controlChangeSustain(part, value);
+		break;
+	case SCI_MIDI_SET_POLYPHONY:
+		controlChangePolyphony(part, value);
+		break;
+	case SCI_MIDI_CHANNEL_NOTES_OFF:
+	case SCI_MIDI_CHANNEL_SOUND_OFF:
+		for (int i = 0; i < _numChannels; ++i) {
+			if (_partMapping[i] == part)
+				chanOff(i);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+void MidiDriver_PCJr::programChange(byte part, byte program) {
+	if (_version > SCI_VERSION_0_LATE && !_pcsMode)
+		_program[part] = program;
+}
+
+void MidiDriver_PCJr::pitchBend(byte part, uint16 value) {
+}
+
+void MidiDriver_PCJr::controlChangeSustain(byte part, byte sus) {
+	if (_version <= SCI_VERSION_0_LATE || _pcsMode)
+		return;
+
+	_partSustain[part] = sus;
+	if (sus)
+		return;
+
+	for (int i = 0; i < _numChannels; ++i) {
+		if (_partMapping[i] != part || !_chanSustain[i])
+			continue;
+		_chanSustain[i] = 0;
+		_chanRelease[i] = 1;
+	}
+}
+
+void MidiDriver_PCJr::controlChangePolyphony(byte part, byte numChan) {
+	if (_version <= SCI_VERSION_0_LATE)
+		return;
+
+	if (_pcsMode) {
+		if (numChan == 0 || _partMapping[0] != part)
+			chanOff(0);
+		_partMapping[0] = numChan ? part : 0xFF;
+		return;
+	}
+
+	uint8 numAssigned = 0;
+	for (int i = 0; i < _numChannels; ++i) {
+		if (_partMapping[i] == part)
+			numAssigned++;
+	}
+
+	numAssigned += _chanMissing[part];
+	if (numAssigned < numChan) {
+		addChannels(part, numChan - numAssigned);
+	} else if (numAssigned > numChan) {
+		dropChannels(part, numAssigned - numChan);
+		assignFreeChannels(part);
+	}
+}
+
+void MidiDriver_PCJr::addChannels(byte part, byte num) {
+	for (int i = 0; i < _numChannels; ++i) {
+		if (_partMapping[i] != 0xFF)
+			continue;
+		_partMapping[i] = part;
+
+		if (_notes[i] != 0xFF)
+			chanOff(i);
+
+		if (!--num)
+			break;
+	}
+	_chanMissing[part] += num;
+}
+
+void MidiDriver_PCJr::dropChannels(byte part, byte num) {
+	if (_chanMissing[part] == num) {
+		_chanMissing[part] = 0;
+		return;
+	} else if (_chanMissing[part] > num) {
+		_chanMissing[part] -= num;
+		return;
+	}
+
+	num -= _chanMissing[part];
+	_chanMissing[part] = 0;
+
+	for (int i = 0; i < _numChannels; i++) {
+		if (_partMapping[i] != part || _notes[i] != 0xFF)
+			continue;
+		_partMapping[i] = 0xFF;
+		if (!--num)
+			return;
+	}
+
+	do {
+		uint16 oldest = 0;
+		byte dropCh = 0;
+		for (int i = 0; i < _numChannels; i++) {
+			if (_partMapping[i] != part)
+				continue;
+
+			uint16 ct = _releaseDuration[i] ? _releaseDuration[i] + 0x8000 : _duration[i];
+
+			if (ct >= oldest) {
+				dropCh = i;
+				oldest = ct;
+			}
+		}
+
+		chanOff(dropCh);
+		_partMapping[dropCh] = 0xFF;
+	} while (--num);
+}
+
+void MidiDriver_PCJr::assignFreeChannels(byte part) {
+	uint8 freeChan = 0;
+	for (int i = 0; i < _numChannels; i++) {
+		if (_partMapping[i] == 0xff)
+			freeChan++;
+	}
+
+	if (!freeChan)
+		return;
+
+	for (int i = 0; i < 16; i++) {
+		if (!_chanMissing[i])
+			continue;
+		if (_chanMissing[i] < freeChan) {
+			freeChan -= _chanMissing[i];
+			addChannels(part, _chanMissing[i]);
+			_chanMissing[i] = 0;
+		} else {
+			_chanMissing[i] -= freeChan;
+			addChannels(part, _chanMissing[i]);
+			return;
+		}
+	}
+}
+
+byte MidiDriver_PCJr::allocateChannel(byte part) {
+	byte res = 0xFF;
+	if (_version <= SCI_VERSION_0_LATE) {
+		for (int i = 0; i < _numChannels; ++i) {
+			if (_partMapping[i] == part)
+				res = i;
+		}
+		return res;
+	}
+
+	uint16 oldest = 0;
+	byte c = _chanMapping[part];
+
+	for (bool loop = true; loop;) {
+		c = (c + 1) % _numChannels;
+		if (c == _chanMapping[part])
+			loop = false;
+
+		if (_partMapping[c] != part)
+			continue;
+
+		if (_notes[c] == 0xFF) {
+			_chanMapping[part] = c;
+			return c;
+		}
+
+		uint16 ct = _releaseDuration[c] ? _releaseDuration[c] + 0x8000 : _duration[c];
+		if (ct < oldest)
+			continue;
+
+		res = c;
+		oldest = ct;		
+	}
+
+	if (oldest != 0) {
+		_chanMapping[part] = res;
+		chanOff(res);
+	}
+
+	return res;
+}
+
 class MidiPlayer_PCJr : public MidiPlayer {
 public:
-	MidiPlayer_PCJr(SciVersion version) : MidiPlayer(version) { _driver = new MidiDriver_PCJr(g_system->getMixer()); }
-	int open(ResourceManager *resMan) override { return static_cast<MidiDriver_PCJr *>(_driver)->open(getPolyphony()); }
+	MidiPlayer_PCJr(SciVersion version, bool pcsMode) : MidiPlayer(version) { _driver = new MidiDriver_PCJr(g_system->getMixer(), version, pcsMode); }
 	byte getPlayId() const override;
 	int getPolyphony() const override { return 3; }
 	bool hasRhythmChannel() const override { return false; }
-	void setVolume(byte volume) override { static_cast<MidiDriver_PCJr *>(_driver)->_global_volume = volume; }
+	void setVolume(byte volume) override { _driver->property(MidiDriver_PCJr::kPropVolume, volume); }
+	void initTrack(SciSpan<const byte> &trackData) override;
 };
 
 byte MidiPlayer_PCJr::getPlayId() const {
@@ -249,14 +820,18 @@ byte MidiPlayer_PCJr::getPlayId() const {
 	}
 }
 
+void MidiPlayer_PCJr::initTrack(SciSpan<const byte> &trackData) {
+	if (_driver)
+		static_cast<MidiDriver_PCJr*>(_driver)->initTrack(trackData);
+};
+
 MidiPlayer *MidiPlayer_PCJr_create(SciVersion version) {
-	return new MidiPlayer_PCJr(version);
+	return new MidiPlayer_PCJr(version, false);
 }
 
 class MidiPlayer_PCSpeaker : public MidiPlayer_PCJr {
 public:
-	MidiPlayer_PCSpeaker(SciVersion version) : MidiPlayer_PCJr(version) { }
-
+	MidiPlayer_PCSpeaker(SciVersion version) : MidiPlayer_PCJr(version, true) { }
 	byte getPlayId() const override;
 	int getPolyphony() const override { return 1; }
 };




More information about the Scummvm-git-logs mailing list