[Scummvm-cvs-logs] scummvm master -> b7bfa2281869df97ace11e551126b8e8ae179f78

wjp wjp at usecode.org
Tue Dec 31 15:36:48 CET 2013


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

Summary:
f777e54df2 SCI: Reduce indentation depth
857d2e7bef SCI: Rewrite MIDI channel remapping
0dfd742b21 SCI: Remove no longer necessary hack
b7bfa22818 Merge pull request #419 from wjp/sci_remap


Commit: f777e54df291d1f39aefdc40d1d966e50d261efb
    https://github.com/scummvm/scummvm/commit/f777e54df291d1f39aefdc40d1d966e50d261efb
Author: Willem Jan Palenstijn (wjp at usecode.org)
Date: 2013-12-31T04:42:58-08:00

Commit Message:
SCI: Reduce indentation depth

Changed paths:
    engines/sci/resource_audio.cpp



diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index 268180b..16622e5 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -678,31 +678,34 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
 					channel = &_tracks[trackNr].channels[channelNr];
 					channel->prio = READ_LE_UINT16(data);
 					uint dataOffset = READ_LE_UINT16(data + 2);
-					if (dataOffset < resource->size) {
-						channel->data = resource->data + dataOffset;
-						channel->size = READ_LE_UINT16(data + 4);
-						channel->curPos = 0;
-						// FIXME: number contains (low nibble) channel and (high nibble) flags
-						// 0x20 is set on rhythm channels to prevent remapping
-						channel->number = *channel->data;
-						channel->poly = *(channel->data + 1);
-						channel->time = channel->prev = 0;
-						channel->data += 2; // skip over header
-						channel->size -= 2; // remove header size
-						if (channel->number == 0xFE) { // Digital channel
-							_tracks[trackNr].digitalChannelNr = channelNr;
-							_tracks[trackNr].digitalSampleRate = READ_LE_UINT16(channel->data);
-							_tracks[trackNr].digitalSampleSize = READ_LE_UINT16(channel->data + 2);
-							_tracks[trackNr].digitalSampleStart = READ_LE_UINT16(channel->data + 4);
-							_tracks[trackNr].digitalSampleEnd = READ_LE_UINT16(channel->data + 6);
-							channel->data += 8; // Skip over header
-							channel->size -= 8;
-						}
-						_tracks[trackNr].channelCount++;
-						channelNr++;
-					} else {
+
+					if (dataOffset >= resource->size) {
 						warning("Invalid offset inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr);
+						data += 6;
+						continue;
+					}
+
+					channel->data = resource->data + dataOffset;
+					channel->size = READ_LE_UINT16(data + 4);
+					channel->curPos = 0;
+					// FIXME: number contains (low nibble) channel and (high nibble) flags
+					// 0x20 is set on rhythm channels to prevent remapping
+					channel->number = *channel->data;
+					channel->poly = *(channel->data + 1);
+					channel->time = channel->prev = 0;
+					channel->data += 2; // skip over header
+					channel->size -= 2; // remove header size
+					if (channel->number == 0xFE) { // Digital channel
+						_tracks[trackNr].digitalChannelNr = channelNr;
+						_tracks[trackNr].digitalSampleRate = READ_LE_UINT16(channel->data);
+						_tracks[trackNr].digitalSampleSize = READ_LE_UINT16(channel->data + 2);
+						_tracks[trackNr].digitalSampleStart = READ_LE_UINT16(channel->data + 4);
+						_tracks[trackNr].digitalSampleEnd = READ_LE_UINT16(channel->data + 6);
+						channel->data += 8; // Skip over header
+						channel->size -= 8;
 					}
+					_tracks[trackNr].channelCount++;
+					channelNr++;
 					data += 6;
 				}
 			} else {


Commit: 857d2e7beff46fcd5d26e8590c9891a4959b8d1f
    https://github.com/scummvm/scummvm/commit/857d2e7beff46fcd5d26e8590c9891a4959b8d1f
Author: Willem Jan Palenstijn (wjp at usecode.org)
Date: 2013-12-31T04:52:15-08:00

Commit Message:
SCI: Rewrite MIDI channel remapping

This adds MIDI state tracking to allow channels to be temporarily
unmapped and later re-mapped when there are free device channels
available again.

Changed paths:
    engines/sci/resource.h
    engines/sci/resource_audio.cpp
    engines/sci/sound/midiparser_sci.cpp
    engines/sci/sound/midiparser_sci.h
    engines/sci/sound/music.cpp
    engines/sci/sound/music.h



diff --git a/engines/sci/resource.h b/engines/sci/resource.h
index c4c8e54..6a22f48 100644
--- a/engines/sci/resource.h
+++ b/engines/sci/resource.h
@@ -554,6 +554,7 @@ class SoundResource {
 public:
 	struct Channel {
 		byte number;
+		byte flags;
 		byte poly;
 		uint16 prio;
 		uint16 size;
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index 16622e5..8e1568f 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -599,6 +599,7 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
 		_tracks->channels = new Channel[_tracks->channelCount];
 		memset(_tracks->channels, 0, sizeof(Channel) * _tracks->channelCount);
 		channel = &_tracks->channels[0];
+		channel->flags |= 2; // don't remap (SCI0 doesn't have remapping)
 		if (_soundVersion == SCI_VERSION_0_EARLY) {
 			channel->data = resource->data + 0x11;
 			channel->size = resource->size - 0x11;
@@ -676,7 +677,6 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
 				channelNr = 0;
 				while (channelCount--) {
 					channel = &_tracks[trackNr].channels[channelNr];
-					channel->prio = READ_LE_UINT16(data);
 					uint dataOffset = READ_LE_UINT16(data + 2);
 
 					if (dataOffset >= resource->size) {
@@ -688,10 +688,10 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
 					channel->data = resource->data + dataOffset;
 					channel->size = READ_LE_UINT16(data + 4);
 					channel->curPos = 0;
-					// FIXME: number contains (low nibble) channel and (high nibble) flags
-					// 0x20 is set on rhythm channels to prevent remapping
 					channel->number = *channel->data;
-					channel->poly = *(channel->data + 1);
+
+					channel->poly = *(channel->data + 1) & 0x0F;
+					channel->prio = *(channel->data + 1) >> 4;
 					channel->time = channel->prev = 0;
 					channel->data += 2; // skip over header
 					channel->size -= 2; // remove header size
@@ -703,6 +703,23 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers
 						_tracks[trackNr].digitalSampleEnd = READ_LE_UINT16(channel->data + 6);
 						channel->data += 8; // Skip over header
 						channel->size -= 8;
+						channel->flags = 0;
+					} else {
+						channel->flags = channel->number >> 4;
+						channel->number = channel->number & 0x0F;
+
+						// 0x20 is set on rhythm channels to prevent remapping
+						// CHECKME: Which SCI versions need that set manually?
+						channel->flags = (*channel->data) >> 4;
+						if (channel->number == 9)
+							channel->flags |= 2;
+						// Note: flag 1: channel start offset is 0 instead of 10
+						//               (currently: everything 0)
+						//               also: don't map the channel to device
+						//       flag 2: don't remap
+						//       flag 4: start muted
+						// QfG2 lacks flags 2 and 4, and uses (flags >= 1) as
+						// the condition for starting offset 0, without the "don't map"
 					}
 					_tracks[trackNr].channelCount++;
 					channelNr++;
diff --git a/engines/sci/sound/midiparser_sci.cpp b/engines/sci/sound/midiparser_sci.cpp
index 186fc18..6f250e0 100644
--- a/engines/sci/sound/midiparser_sci.cpp
+++ b/engines/sci/sound/midiparser_sci.cpp
@@ -58,6 +58,10 @@ MidiParser_SCI::MidiParser_SCI(SciVersion soundVersion, SciMusic *music) :
 
 	_resetOnPause = false;
 	_pSnd = 0;
+
+	_mainThreadCalled = false;
+
+	resetStateTracking();
 }
 
 MidiParser_SCI::~MidiParser_SCI() {
@@ -68,10 +72,12 @@ MidiParser_SCI::~MidiParser_SCI() {
 }
 
 void MidiParser_SCI::mainThreadBegin() {
+	assert(!_mainThreadCalled);
 	_mainThreadCalled = true;
 }
 
 void MidiParser_SCI::mainThreadEnd() {
+	assert(_mainThreadCalled);
 	_mainThreadCalled = false;
 }
 
@@ -83,12 +89,21 @@ bool MidiParser_SCI::loadMusic(SoundResource::Track *track, MusicEntry *psnd, in
 
 	for (int i = 0; i < 16; i++) {
 		_channelUsed[i] = false;
-		_channelRemap[i] = -1;
 		_channelMuted[i] = false;
 		_channelVolume[i] = 127;
+
+		if (_soundVersion <= SCI_VERSION_0_LATE)
+			_channelRemap[i] = i;
+		else
+			_channelRemap[i] = -1;
 	}
-	_channelRemap[9] = 9; // never map channel 9, because that's used for percussion
-	_channelRemap[15] = 15; // never map channel 15, because thats used by sierra internally
+
+	// FIXME: SSCI does not always start playing a track at the first byte.
+	// By default it skips 10 (or 13?) bytes containing prio/voices, patch,
+	// volume, pan commands in fixed locations, and possibly a signal
+	// in channel 15. We should initialize state tracking to those values
+	// so that they automatically get set up properly when the channels get
+	// mapped. See also the related FIXME in MidiParser_SCI::processEvent.
 
 	if (channelFilterMask) {
 		// SCI0 only has 1 data stream, but we need to filter out channels depending on music hardware selection
@@ -314,31 +329,26 @@ byte *MidiParser_SCI::midiFilterChannels(int channelMask) {
 	return _mixedData;
 }
 
-// This will get called right before actual playing and will try to own the used channels
-void MidiParser_SCI::tryToOwnChannels() {
-	// We don't have SciMusic in case debug command show_instruments is used
-	if (!_music)
-		return;
-	for (int curChannel = 0; curChannel < 15; curChannel++) {
-		if (_channelUsed[curChannel]) {
-			if (_channelRemap[curChannel] == -1) {
-				_channelRemap[curChannel] = _music->tryToOwnChannel(_pSnd, curChannel);
-			}
-		}
-	}
-}
+void MidiParser_SCI::resetStateTracking() {
+	for (int i = 0; i < 16; ++i) {
+		ChannelState &s = _channelState[i];
+		s._modWheel = 0;
+		s._pan = 64;
+		s._patch = 0; // TODO: Initialize properly (from data in LoadMusic?)
+		s._note = -1;
+		s._sustain = false;
+		s._pitchWheel = 0x2000;
+		s._voices = 0;
 
-void MidiParser_SCI::lostChannels() {
-	for (int curChannel = 0; curChannel < 15; curChannel++)
-		if ((_channelUsed[curChannel]) && (curChannel != 9))
-			_channelRemap[curChannel] = -1;
+		_channelVolume[i] = 127;
+	}
 }
 
 void MidiParser_SCI::sendInitCommands() {
-	// reset our "global" volume and channel volumes
+	resetStateTracking();
+
+	// reset our "global" volume
 	_volume = 127;
-	for (int i = 0; i < 16; i++)
-		_channelVolume[i] = 127;
 
 	// Set initial voice count
 	if (_pSnd) {
@@ -390,53 +400,119 @@ void MidiParser_SCI::sendFromScriptToDriver(uint32 midi) {
 		//  this happens for cmdSendMidi at least in sq1vga right at the start, it's a script issue
 		return;
 	}
-	if (_channelRemap[midiChannel] == -1) {
-		// trying to send to an unmapped channel
-		//  this happens for cmdSendMidi at least in sq1vga right at the start, scripts are pausing the sound
-		//  and then sending manually. it's a script issue
-		return;
-	}
 	sendToDriver(midi);
 }
 
 void MidiParser_SCI::sendToDriver(uint32 midi) {
-	byte midiChannel = midi & 0xf;
-
-	if ((midi & 0xFFF0) == 0x4EB0) {
-		// this is channel mute only for sci1
-		// it's velocity control for sci0
-		if (_soundVersion >= SCI_VERSION_1_EARLY) {
-			_channelMuted[midiChannel] = midi & 0xFF0000 ? true : false;
-			return; // don't send this to driver at all
-		}
-	}
+	// State tracking
+	trackState(midi);
 
-	// Is channel muted? if so, don't send command
-	if (_channelMuted[midiChannel])
+	if ((midi & 0xFFF0) == 0x4EB0 && _soundVersion >= SCI_VERSION_1_EARLY) {
+		// Mute. Handled in trackState().
+		// CHECKME: Should we send this on to the driver?
 		return;
+	}
 
 	if ((midi & 0xFFF0) == 0x07B0) {
 		// someone trying to set channel volume?
 		int channelVolume = (midi >> 16) & 0xFF;
-		// Remember, if we need to set it ourselves
-		_channelVolume[midiChannel] = channelVolume;
 		// Adjust volume accordingly to current local volume
 		channelVolume = channelVolume * _volume / 127;
-		midi = (midi & 0xFFF0) | ((channelVolume & 0xFF) << 16);
+		midi = (midi & 0xFFFF) | ((channelVolume & 0xFF) << 16);
 	}
 
+
 	// Channel remapping
+	byte midiChannel = midi & 0xf;
 	int16 realChannel = _channelRemap[midiChannel];
 	if (realChannel == -1)
 		return;
 
 	midi = (midi & 0xFFFFFFF0) | realChannel;
+	sendToDriver_raw(midi);
+}
+
+void MidiParser_SCI::sendToDriver_raw(uint32 midi) {
 	if (_mainThreadCalled)
 		_music->putMidiCommandInQueue(midi);
 	else
 		_driver->send(midi);
 }
 
+void MidiParser_SCI::trackState(uint32 b) {
+	// We keep track of most of the state of a midi channel, so we can
+	// at any time reset the device to the current state, even if the
+	// channel has been temporarily disabled due to remapping.
+
+	byte command = b & 0xf0;
+	byte channel = b & 0xf;
+	byte op1 = (b >> 8) & 0x7f;
+	byte op2 = (b >> 16) & 0x7f;
+
+	ChannelState &s = _channelState[channel];
+
+	switch (command) {
+	case 0x90:
+		if (op2 != 0) {
+			// note on
+			s._note = op1;
+			break;
+		}
+		// else, fall-through
+	case 0x80:
+		// note off
+		if (s._note == op1)
+			s._note = -1;
+		break;
+	case 0xB0:
+		// control change
+		switch (op1) {
+		case 0x01: // mod wheel
+			s._modWheel = op2;
+			break;
+		case 0x07: // channel volume
+			_channelVolume[channel] = op2;
+			break;
+		case 0x0A: // pan
+			s._pan = op2;
+			break;
+		case 0x40: // sustain
+			s._sustain = (op2 != 0);
+			break;
+		case 0x4B: // voices
+			s._voices = op2;
+			_pSnd->_chan[channel]._voices = op2; // Also sync our MusicEntry
+			break;
+		case 0x4E: // mute
+			// This is channel mute only for sci1.
+			// (It's velocity control for sci0, but we don't need state in sci0)
+			if (_soundVersion >= SCI_VERSION_1_EARLY) {
+				// FIXME: mute is a level, not a bool, in some SCI versions
+				bool m = op2;
+				if (_pSnd->_chan[channel]._mute != m) {
+					_pSnd->_chan[channel]._mute = m;
+					// TODO: If muting/unmuting a channel, remap channels.
+					warning("Mute change without immediate remapping (mainThread = %d)", _mainThreadCalled);
+				}
+			}
+			break;
+		default:
+			break;
+		}
+		break;
+	case 0xC0:
+		// program change
+		s._patch = op1;
+		break;
+	case 0xE0:
+		// pitchwheel
+		s._pitchWheel = (op2 << 7) | op1;
+		break;
+	default:
+		break;
+	}
+}
+
 void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 	info.start = _position._playPos;
 	info.delta = 0;
@@ -477,8 +553,10 @@ void MidiParser_SCI::parseNextEvent(EventInfo &info) {
 	case 0xE:
 		info.basic.param1 = *(_position._playPos++);
 		info.basic.param2 = *(_position._playPos++);
-		if (info.command() == 0x9 && info.basic.param2 == 0)
+		if (info.command() == 0x9 && info.basic.param2 == 0) {
+			// NoteOn with param2==0 is a NoteOff
 			info.event = info.channel() | 0x80;
+		}
 		info.length = 0;
 		break;
 
@@ -809,4 +887,44 @@ void MidiParser_SCI::setVolume(byte volume) {
 	}
 }
 
+void MidiParser_SCI::remapChannel(int channel, int devChannel) {
+	if (_channelRemap[channel] == devChannel)
+		return;
+
+	_channelRemap[channel] = devChannel;
+
+	if (devChannel == -1)
+		return;
+
+//	debug("  restoring state: channel %d on devChannel %d", channel, devChannel);
+
+	// restore state
+	ChannelState &s = _channelState[channel];
+
+	int channelVolume = _channelVolume[channel];
+	channelVolume = (channelVolume * _volume / 127) & 0xFF;
+	byte pitch1 = s._pitchWheel & 0x7F;
+	byte pitch2 = (s._pitchWheel >> 7) & 0x7F;
+
+	sendToDriver_raw(0x0040B0 | devChannel); // sustain off
+	sendToDriver_raw(0x004BB0 | devChannel | (s._voices << 16));
+	sendToDriver_raw(0x0000C0 | devChannel | (s._patch << 8));
+	sendToDriver_raw(0x0007B0 | devChannel | (channelVolume << 16));
+	sendToDriver_raw(0x000AB0 | devChannel | (s._pan << 16));
+	sendToDriver_raw(0x0001B0 | devChannel | (s._modWheel << 16));
+	sendToDriver_raw(0x0040B0 | devChannel | (s._sustain ? 0x7F0000 : 0));
+	sendToDriver_raw(0x0000E0 | devChannel | (pitch1 << 8) | (pitch2 << 16));
+
+	// CHECKME: Some SSCI version send a control change 0x4E with s._note as
+	// parameter.
+	// We need to investigate how (and if) drivers should act on this.
+	// Related: controller 0x4E is used for 'mute' in the midiparser.
+	// This could be a bug in SSCI that went unnoticed because few (or no?)
+	// drivers implement controller 0x4E
+
+	// NB: The line below is _not_ valid since s._note can be 0xFF.
+	// SSCI handles this out of band in the driver interface.
+	// sendToDriver_raw(0x004EB0 | devChannel | (s._note << 16);
+}
+
 } // End of namespace Sci
diff --git a/engines/sci/sound/midiparser_sci.h b/engines/sci/sound/midiparser_sci.h
index 5784dca..7e24c34 100644
--- a/engines/sci/sound/midiparser_sci.h
+++ b/engines/sci/sound/midiparser_sci.h
@@ -79,20 +79,23 @@ public:
 	const byte *getMixedData() const { return _mixedData; }
 	byte getSongReverb();
 
-	void tryToOwnChannels();
-	void lostChannels();
 	void sendFromScriptToDriver(uint32 midi);
 	void sendToDriver(uint32 midi);
 	void sendToDriver(byte status, byte firstOp, byte secondOp) {
 		sendToDriver(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
 	}
 
+	void remapChannel(int channel, int devChannel);
+
 protected:
 	void parseNextEvent(EventInfo &info);
 	void processEvent(const EventInfo &info, bool fireEvents = true);
 	byte *midiMixChannels();
 	byte *midiFilterChannels(int channelMask);
 	byte midiGetNextChannel(long ticker);
+	void resetStateTracking();
+	void trackState(uint32 midi);
+	void sendToDriver_raw(uint32 midi);
 
 	SciMusic *_music;
 
@@ -113,6 +116,19 @@ protected:
 	int16 _channelRemap[16];
 	bool _channelMuted[16];
 	byte _channelVolume[16];
+
+	struct ChannelState {
+		int8 _modWheel;
+		int8 _pan;
+		int8 _patch;
+		int8 _note;
+		bool _sustain;
+		int16 _pitchWheel;
+		int8 _voices;
+	};
+
+	ChannelState _channelState[16];
+
 };
 
 } // End of namespace Sci
diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index 8c6d0d6..e29afe7 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -33,7 +33,7 @@
 #include "sci/sound/midiparser_sci.h"
 #include "sci/sound/music.h"
 
-//#define DISABLE_REMAPPING
+//#define DEBUG_REMAP
 
 namespace Sci {
 
@@ -47,6 +47,8 @@ SciMusic::SciMusic(SciVersion soundVersion, bool useDigitalSFX)
 	for (int i = 0; i < 16; i++) {
 		_usedChannel[i] = 0;
 		_channelRemap[i] = -1;
+		_channelMap[i]._song = 0;
+		_channelMap[i]._channel = -1;
 	}
 
 	_queuedCommands.reserve(1000);
@@ -291,6 +293,15 @@ void SciMusic::sortPlayList() {
 }
 
 void SciMusic::soundInitSnd(MusicEntry *pSnd) {
+	// Remove all currently mapped channels of this MusicEntry first,
+	// since they will no longer be valid.
+	for (int i = 0; i < 16; ++i) {
+		if (_channelMap[i]._song == pSnd) {
+			_channelMap[i]._song = 0;
+			_channelMap[i]._channel = -1;
+		}
+	}
+
 	int channelFilterMask = 0;
 	SoundResource::Track *track = pSnd->soundRes->getTrackByType(_pMidiDrv->getPlayId());
 
@@ -337,6 +348,27 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) {
 			// Find out what channels to filter for SCI0
 			channelFilterMask = pSnd->soundRes->getChannelFilterMask(_pMidiDrv->getPlayId(), _pMidiDrv->hasRhythmChannel());
 
+			for (int i = 0; i < 16; ++i)
+				pSnd->_usedChannels[i] = 0xFF;
+			for (int i = 0; i < track->channelCount; ++i) {
+				SoundResource::Channel &chan = track->channels[i];
+
+				pSnd->_usedChannels[i] = chan.number;
+				pSnd->_chan[chan.number]._dontRemap = (chan.flags & 2);
+				pSnd->_chan[chan.number]._prio = chan.prio;
+				pSnd->_chan[chan.number]._voices = chan.poly;
+
+				// CHECKME: Some SCI versions use chan.flags & 1 for this:
+				pSnd->_chan[chan.number]._dontMap = false;
+
+				// FIXME: Most MIDI tracks use the first 10 bytes for
+				// fixed MIDI commands. SSCI skips those the first iteration,
+				// but _does_ update channel state (including volume) with
+				// them. Specifically, prio/voices, patch, volume, pan.
+				// This should probably be implemented in
+				// MidiParser_SCI::loadMusic.
+			}
+
 			pSnd->pMidiParser->mainThreadBegin();
 			// loadMusic() below calls jumpToTick.
 			// Disable sound looping and hold before jumpToTick is called,
@@ -358,64 +390,6 @@ void SciMusic::soundInitSnd(MusicEntry *pSnd) {
 	}
 }
 
-// This one checks, if requested channel is available -> in that case give
-// caller that channel. Otherwise look for an unused one
-int16 SciMusic::tryToOwnChannel(MusicEntry *caller, int16 bestChannel) {
-#ifdef DISABLE_REMAPPING
-	return bestChannel;
-#endif
-
-	// Don't even try this for SCI0
-	if (_soundVersion <= SCI_VERSION_0_LATE)
-		return bestChannel;
-	if (!_usedChannel[bestChannel]) {
-		// currently unused, so give it to caller directly
-		_usedChannel[bestChannel] = caller;
-		_channelRemap[bestChannel] = bestChannel;
-		return bestChannel;
-	}
-	// otherwise look for unused channel
-	for (int channelNr = _driverFirstChannel; channelNr < 15; channelNr++) {
-		if (channelNr == 9) // never map to channel 9 (percussion)
-			continue;
-		if (!_usedChannel[channelNr]) {
-			_usedChannel[channelNr] = caller;
-			_channelRemap[bestChannel] = channelNr;
-			return channelNr;
-		}
-	}
-	// nothing found, don't map channel at all
-	//  sierra did this as well, although i'm not sure if we act exactly the same way
-	//  maybe they removed channels from previous playing music
-	return -1;
-}
-
-void SciMusic::freeChannels(MusicEntry *caller) {
-	// Remove used channels
-	for (int i = 0; i < 15; i++) {
-		if (_usedChannel[i] == caller) {
-			if (_channelRemap[i] != -1) {
-				// athrxx: The original handles this differently. It seems to be checking for (and effecting) necessary
-				// remaps / resets etc. more or less all the time. There are several more tables to keep track of everything.
-				// I don't know whether all of that is needed and to which SCI versions it applies, though.
-				// At least it is necessary to release the allocated channels inside the driver. Otherwise these channels
-				// won't be available any more (e.g. after half of the KQ5 FM-Towns intro there will be no more music
-				// since the driver can't pick up any more channels). The channels also have to be reset to
-				// default values, since the original does the same (although in a different manny) and the music will be wrong
-				// otherwise (at least KQ5 FM-Towns).
-
-				sendMidiCommand(0x4000e0 | _channelRemap[i]);	// Reset pitch wheel
-				sendMidiCommand(0x0040b0 | _channelRemap[i]);	// Release pedal
-				sendMidiCommand(0x004bb0 | _channelRemap[i]);	// Release assigned driver channels
-			}
-			_usedChannel[i] = 0;
-			_channelRemap[i] = -1;
-		}
-	}
-	// Also tell midiparser, that he lost ownership
-	caller->pMidiParser->lostChannels();
-}
-
 void SciMusic::soundPlay(MusicEntry *pSnd) {
 	_mutex.lock();
 
@@ -495,14 +469,13 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {
 						if (_soundVersion <= SCI_VERSION_0_LATE)
 							_playList[i]->isQueued = false;
 						_playList[i]->pMidiParser->stop();
-						freeChannels(_playList[i]);
+						remapChannels();
 						_playList[i]->fadeStep = 0;
 						_playList[i]->fadeCompleted = true;
 					}
 				}
 			}
 
-			pSnd->pMidiParser->tryToOwnChannels();
 			if (pSnd->status != kSoundPaused)
 				pSnd->pMidiParser->sendInitCommands();
 			pSnd->pMidiParser->setVolume(pSnd->volume);
@@ -532,6 +505,10 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {
 	}
 
 	pSnd->status = kSoundPlaying;
+
+	_mutex.lock();
+	remapChannels();
+	_mutex.unlock();
 }
 
 void SciMusic::soundStop(MusicEntry *pSnd) {
@@ -549,8 +526,8 @@ void SciMusic::soundStop(MusicEntry *pSnd) {
 		// allNotesOff() again
 		if (previousStatus == kSoundPlaying)
 			pSnd->pMidiParser->stop();
-		freeChannels(pSnd);
 		pSnd->pMidiParser->mainThreadEnd();
+		remapChannels();
 	}
 
 	pSnd->fadeStep = 0; // end fading, if fading was in progress
@@ -586,8 +563,10 @@ void SciMusic::soundSetPriority(MusicEntry *pSnd, byte prio) {
 void SciMusic::soundKill(MusicEntry *pSnd) {
 	pSnd->status = kSoundStopped;
 
+	_mutex.lock();
+	remapChannels();
+
 	if (pSnd->pMidiParser) {
-		Common::StackLock lock(_mutex);
 		pSnd->pMidiParser->mainThreadBegin();
 		pSnd->pMidiParser->unloadMusic();
 		pSnd->pMidiParser->mainThreadEnd();
@@ -595,6 +574,8 @@ void SciMusic::soundKill(MusicEntry *pSnd) {
 		pSnd->pMidiParser = NULL;
 	}
 
+	_mutex.unlock();
+
 	if (pSnd->pStreamAud) {
 		_pMixer->stopHandle(pSnd->hCurrentAud);
 		delete pSnd->pStreamAud;
@@ -603,7 +584,7 @@ void SciMusic::soundKill(MusicEntry *pSnd) {
 		pSnd->pLoopStream = 0;
 	}
 
-	Common::StackLock lock(_mutex);
+	_mutex.lock();
 	uint sz = _playList.size(), i;
 	// Remove sound from playlist
 	for (i = 0; i < sz; i++) {
@@ -614,6 +595,7 @@ void SciMusic::soundKill(MusicEntry *pSnd) {
 			break;
 		}
 	}
+	_mutex.unlock();
 }
 
 void SciMusic::soundPause(MusicEntry *pSnd) {
@@ -639,8 +621,8 @@ void SciMusic::soundPause(MusicEntry *pSnd) {
 			Common::StackLock lock(_mutex);
 			pSnd->pMidiParser->mainThreadBegin();
 			pSnd->pMidiParser->pause();
-			freeChannels(pSnd);
 			pSnd->pMidiParser->mainThreadEnd();
+			remapChannels();
 		}
 	}
 }
@@ -789,6 +771,14 @@ MusicEntry::MusicEntry() {
 	pStreamAud = 0;
 	pLoopStream = 0;
 	pMidiParser = 0;
+
+	for (int i = 0; i < 16; ++i) {
+		_usedChannels[i] = 0xFF;
+		_chan[i]._prio = 127;
+		_chan[i]._voices = 0;
+		_chan[i]._dontRemap = false;
+		_chan[i]._mute = false;
+	}
 }
 
 MusicEntry::~MusicEntry() {
@@ -857,4 +847,445 @@ void MusicEntry::setSignal(int newSignal) {
 	}
 }
 
+
+void ChannelRemapping::swap(int i, int j) {
+	DeviceChannelUsage t1;
+	int t2;
+	bool t3;
+
+	t1 = _map[i]; _map[i] = _map[j]; _map[j] = t1;
+	t2 = _prio[i]; _prio[i] = _prio[j]; _prio[j] = t2;
+	t2 = _voices[i]; _voices[i] = _voices[j]; _voices[j] = t2;
+	t3 = _dontRemap[i]; _dontRemap[i] = _dontRemap[j]; _dontRemap[j] = t3;
+}
+
+void ChannelRemapping::evict(int i) {
+	_freeVoices += _voices[i];
+
+	_map[i]._song = 0;
+	_map[i]._channel = -1;
+	_prio[i] = 0;
+	_voices[i] = 0;
+	_dontRemap[i] = false;
+}
+
+void ChannelRemapping::clear() {
+	for (int i = 0; i < 16; ++i) {
+		_map[i]._song = 0;
+		_map[i]._channel = -1;
+		_prio[i] = 0;
+		_voices[i] = 0;
+		_dontRemap[i] = false;
+	}
+}
+
+ChannelRemapping& ChannelRemapping::operator=(ChannelRemapping& other) {
+	for (int i = 0; i < 16; ++i) {
+		_map[i] = other._map[i];
+		_prio[i] = other._prio[i];
+		_voices[i] = other._voices[i];
+		_dontRemap[i] = other._dontRemap[i];
+	}
+	_freeVoices = other._freeVoices;
+
+	return *this;
+}
+
+int ChannelRemapping::lowestPrio() const {
+	int max = 0;
+	int channel = -1;
+	for (int i = 0; i < 16; ++i) {
+		if (_prio[i] > max) {
+			max = _prio[i];
+			channel = i;
+		}
+	}
+	return channel;
+}
+
+
+void SciMusic::remapChannels() {
+	if (_soundVersion <= SCI_VERSION_0_LATE)
+		return;
+
+	// NB: This function should only be called from the main thread,
+	// with _mutex locked
+
+
+	ChannelRemapping *map = determineChannelMap();
+
+	DeviceChannelUsage currentMap[16];
+
+#ifdef DEBUG_REMAP
+	debug("Remap results:");
+#endif
+
+	// Save current map, and then start from an empty map
+	for (int i = 0; i < 16; ++i) {
+		currentMap[i] = _channelMap[i];
+		_channelMap[i]._song = 0;
+		_channelMap[i]._channel = -1;
+	}
+
+	// Inform MidiParsers of any unmapped channels
+	const MusicList::iterator end = _playList.end();
+	int songIndex = -1;
+	for (MusicList::iterator i = _playList.begin(); i != end; ++i) {
+		MusicEntry *song = *i;
+		songIndex++;
+
+		if (!song || !song->pMidiParser)
+			continue;
+
+		bool channelMapped[16];
+#ifdef DEBUG_REMAP
+		bool channelUsed[16];
+#endif
+		for (int j = 0; j < 16; ++j) {
+			channelMapped[j] = false;
+#ifdef DEBUG_REMAP
+			channelUsed[j] = false;
+#endif
+		}
+
+		for (int j = 0; j < 16; ++j) {
+			if (map->_map[j]._song == song) {
+				int channel = map->_map[j]._channel;
+				assert(channel >= 0 && channel <= 0x0F);
+				channelMapped[channel] = true;
+			}
+#ifdef DEBUG_REMAP
+			if (song->_usedChannels[j] <= 0x0F)
+				channelUsed[song->_usedChannels[j]] = true;
+#endif
+		}
+
+		for (int j = 0; j < 16; ++j) {
+			if (!channelMapped[j]) {
+				song->pMidiParser->mainThreadBegin();
+				song->pMidiParser->remapChannel(j, -1);
+				song->pMidiParser->mainThreadEnd();
+#ifdef DEBUG_REMAP
+				if (channelUsed[j])
+					debug(" Unmapping song %d, channel %d", songIndex, j);
+#endif
+			}
+		}
+	}
+
+	// Now reshuffle the channels on the device.
+
+	// First, set up any dontRemap channels
+	for (int i = 0; i < 16; ++i) {
+
+		if (!map->_map[i]._song || !map->_map[i]._song->pMidiParser || !map->_dontRemap[i])
+			continue;
+
+		songIndex = -1;
+		for (MusicList::iterator iter = _playList.begin(); iter != end; ++iter) {
+			songIndex++;
+			if (map->_map[i]._song == *iter)
+				break;
+		}
+
+		_channelMap[i] = map->_map[i];
+		map->_map[i]._song = 0; // mark as done
+
+		// If this channel was not yet mapped to the device, reset it
+		if (currentMap[i] != _channelMap[i]) {
+#ifdef DEBUG_REMAP
+			debug(" Mapping (dontRemap) song %d, channel %d to device channel %d", songIndex, _channelMap[i]._channel, i);
+#endif
+			_channelMap[i]._song->pMidiParser->mainThreadBegin();
+			_channelMap[i]._song->pMidiParser->remapChannel(_channelMap[i]._channel, i);
+			_channelMap[i]._song->pMidiParser->mainThreadEnd();
+		}
+
+	}
+
+	// Next, we look for channels which were already playing.
+	// We keep those on the same device channel as before.
+	for (int i = 0; i < 16; ++i) {
+
+		if (!map->_map[i]._song)
+			continue;
+
+		songIndex = -1;
+		for (MusicList::iterator iter = _playList.begin(); iter != end; ++iter) {
+			songIndex++;
+			if (map->_map[i]._song == *iter)
+				break;
+		}
+
+
+		for (int j = 0; j < 16; ++j) {
+			if (map->_map[i] == currentMap[j]) {
+				// found it
+				_channelMap[j] = map->_map[i];
+				map->_map[i]._song = 0; // mark as done
+#ifdef DEBUG_REMAP
+				debug(" Keeping song %d, channel %d on device channel %d", songIndex, _channelMap[j]._channel, j);
+#endif
+				break;
+			}
+		}
+	}
+
+	// Then, remap the rest.
+	for (int i = 0; i < 16; ++i) {
+
+		if (!map->_map[i]._song || !map->_map[i]._song->pMidiParser)
+			continue;
+
+		songIndex = -1;
+		for (MusicList::iterator iter = _playList.begin(); iter != end; ++iter) {
+			songIndex++;
+			if (map->_map[i]._song == *iter)
+				break;
+		}
+
+		for (int j = _driverLastChannel; j >= _driverFirstChannel; --j) {
+			if (_channelMap[j]._song == 0) {
+				_channelMap[j] = map->_map[i];
+				map->_map[i]._song = 0;
+#ifdef DEBUG_REMAP
+				debug(" Mapping song %d, channel %d to device channel %d", songIndex, _channelMap[j]._channel, j);
+#endif
+				_channelMap[j]._song->pMidiParser->mainThreadBegin();
+				_channelMap[j]._song->pMidiParser->remapChannel(_channelMap[j]._channel, j);
+				_channelMap[j]._song->pMidiParser->mainThreadEnd();
+				break;
+			}
+		}
+
+	}
+
+	// And finally, stop any empty channels
+	for (int i = _driverFirstChannel; i <= _driverLastChannel; ++i) {
+		if (!_channelMap[i]._song)
+			resetDeviceChannel(i);
+	}
+}
+
+
+ChannelRemapping *SciMusic::determineChannelMap() {
+#ifdef DEBUG_REMAP
+	debug("Remap: avail chans: %d-%d", _driverFirstChannel, _driverLastChannel);
+#endif
+
+	ChannelRemapping *map = new ChannelRemapping;
+	ChannelRemapping backupMap;
+	map->clear();
+	map->_freeVoices = _pMidiDrv->getPolyphony();
+
+	if (_playList.empty())
+		return map;
+
+	// TODO: set reverb, either from first song, or from global???
+
+	MusicList::iterator songIter;
+	int songIndex = -1;
+	for (songIter = _playList.begin(); songIter != _playList.end(); ++songIter) {
+		songIndex++;
+		MusicEntry *song = *songIter;
+		if (song->status != kSoundPlaying)
+			continue;
+
+		// If song is digital, skip.
+		// CHECKME: Is this condition correct?
+		if (!song->pMidiParser) {
+#ifdef DEBUG_REMAP
+			debug(" Song %d (%p), digital?", songIndex, (void*)song);
+#endif
+			continue;
+		}
+
+
+#ifdef DEBUG_REMAP
+		debug(" Song %d (%p), prio %d", songIndex, (void*)song, song->priority);
+#endif
+
+		// Store backup. If we fail to map this song, we will revert to this.
+		backupMap = *map;
+
+		bool songMapped = true;
+
+		for (int i = 0; i < 16; ++i) {
+			int c = song->_usedChannels[i];
+			if (c == 0xFF || c == 0xFE || c == 0x0F)
+				continue;
+			const MusicEntryChannel &channel = song->_chan[c];
+			if (channel._dontMap)
+				continue;
+			if (channel._mute)
+				continue;
+
+#ifdef DEBUG_REMAP
+			debug("  Channel %d: prio %d, %d voice%s%s", c, channel._prio, channel._voices, channel._voices == 1 ? "" : "s", channel._dontRemap ? ", dontRemap" : "" );
+#endif
+
+			DeviceChannelUsage dc = { song, c };
+
+			// our target
+			int devChannel = -1;
+
+			if (channel._dontRemap && map->_map[c]._song == 0) {
+				// unremappable channel, with channel still free
+				devChannel = c;
+			}
+
+			// try to find a free channel
+			if (devChannel == -1) {
+				for (int j = 0; j < 16; ++j) {
+					if (map->_map[j] == dc) {
+						// already mapped?! (Can this happen?)
+						devChannel = j;
+						break;
+					}
+					if (map->_map[j]._song)
+						continue;
+
+					if (j >= _driverFirstChannel && j <= _driverLastChannel)
+						devChannel = j;
+				}
+			}
+
+			int prio = channel._prio;
+			if (prio > 0) {
+				// prio > 0 means non-essential
+				prio = (16 - prio) + 16*songIndex;
+			}
+
+			if (devChannel == -1 && prio > 0) {
+				// no empty channel, but this isn't an essential channel,
+				// so we just skip it.
+#ifdef DEBUG_REMAP
+				debug("   skipping non-essential");
+#endif
+				continue;
+			}
+
+			// try to empty a previous channel if this is an essential channel
+			if (devChannel == -1) {
+				devChannel = map->lowestPrio();
+				if (devChannel != -1)
+					map->evict(devChannel);
+			}
+
+			if (devChannel == -1) {
+				// failed to map this song.
+#ifdef DEBUG_REMAP
+				debug("   no free (or lower priority) channel found");
+#endif
+				songMapped = false;
+				break;
+			}
+
+			if (map->_map[devChannel] == dc) {
+				// already mapped?! (Can this happen?)
+				continue;
+			}
+
+			int neededVoices = channel._voices;
+			// do we have enough free voices?
+			// We only care for essential channels
+			if (map->_freeVoices < neededVoices && prio > 0) {
+				do {
+					int j = map->lowestPrio();
+					if (j == -1) {
+#ifdef DEBUG_REMAP
+						debug("   not enough voices; need %d, have %d", neededVoices, map->_freeVoices);
+#endif
+						// failed to free enough voices.
+						songMapped = false;
+						break;
+					}
+#ifdef DEBUG_REMAP
+					debug("   creating room for voices; evict %d", j);
+#endif
+					map->evict(j);
+				} while (map->_freeVoices < neededVoices);
+
+				if (!songMapped) {
+					// failed to map this song.
+					break;
+				}
+			}
+
+			// We have a channel and enough free voices now.
+#ifdef DEBUG_REMAP
+			debug("   trying to map to %d", devChannel);
+#endif
+
+			map->_map[devChannel] = dc;
+			map->_voices[devChannel] = neededVoices;
+			map->_prio[devChannel] = prio;
+			map->_dontRemap[devChannel] = channel._dontRemap;
+			map->_freeVoices -= neededVoices;
+
+			if (!channel._dontRemap || devChannel == c) {
+				// If this channel fits here, we're done.
+#ifdef DEBUG_REMAP
+				debug("    OK");
+#endif
+				continue;
+			}
+
+			// If this channel can't be remapped, we need to move it or fail.
+
+			if (!map->_dontRemap[c]) {
+				// Target channel can be remapped, so just swap
+				map->swap(devChannel, c);
+				continue;
+			}
+#ifdef DEBUG_REMAP
+			debug("    but %d is already dontRemap", c);
+#endif
+
+			if (prio > 0) {
+				// Channel collision, but this channel is non-essential,
+				// so drop it.
+				// TODO: Maybe we should have checked this before making room?
+				map->evict(devChannel);
+				continue;
+			}
+
+			if (map->_prio[c] > 0) {
+				// Channel collision, but the other channel is non-essential,
+				// so we take its place.
+				map->evict(c);
+				map->swap(devChannel, c);
+				continue;
+			}
+
+			// Otherwise, we have two essential channels claiming the same
+			// device channel.
+			songMapped = false;
+			break;
+		}
+
+		if (!songMapped) {
+			// We failed to map this song, so unmap all its channels.
+#ifdef DEBUG_REMAP
+			debug(" Failed song");
+#endif
+			*map = backupMap;
+		}
+	}
+
+	return map;
+}
+
+void SciMusic::resetDeviceChannel(int devChannel) {
+	// NB: This function should only be called from the main thread
+
+	assert(devChannel >= 0 && devChannel <= 0x0F);
+
+	putMidiCommandInQueue(0x0040B0 | devChannel); // sustain off
+	putMidiCommandInQueue(0x007BB0 | devChannel); // notes off
+	putMidiCommandInQueue(0x004BB0 | devChannel); // release voices
+}
+
+
+
 } // End of namespace Sci
diff --git a/engines/sci/sound/music.h b/engines/sci/sound/music.h
index 40236c8..23a072c 100644
--- a/engines/sci/sound/music.h
+++ b/engines/sci/sound/music.h
@@ -52,6 +52,17 @@ class SegManager;
 
 typedef Common::Array<uint16> SignalQueue;
 
+
+struct MusicEntryChannel {
+	// Channel info
+	int8 _prio; // 0 = essential; lower is higher priority
+	int8 _voices;
+	bool _dontRemap;
+	bool _dontMap;
+	bool _mute;
+};
+
+
 class MusicEntry : public Common::Serializable {
 public:
 	// Do not get these directly for the sound objects!
@@ -90,6 +101,8 @@ public:
 
 	Audio::Mixer::SoundType soundType;
 
+	int _usedChannels[16];
+	MusicEntryChannel _chan[16];
 	MidiParser_SCI *pMidiParser;
 
 	// this is used for storing signals, when the current signal is not yet
@@ -114,6 +127,27 @@ public:
 	virtual void saveLoadWithSerializer(Common::Serializer &ser);
 };
 
+struct DeviceChannelUsage {
+	MusicEntry *_song;
+	int _channel;
+	bool operator==(const DeviceChannelUsage& other) const { return _song == other._song && _channel == other._channel; }
+	bool operator!=(const DeviceChannelUsage& other) const { return !(*this == other); }
+};
+
+struct ChannelRemapping {
+	DeviceChannelUsage _map[16];
+	int _prio[16];
+	int _voices[16];
+	bool _dontRemap[16];
+	int _freeVoices;
+
+	void clear();
+	void swap(int i, int j);
+	void evict(int i);
+	ChannelRemapping& operator=(ChannelRemapping& other);
+	int lowestPrio() const;
+};
+
 typedef Common::Array<MusicEntry *> MusicList;
 typedef Common::Array<uint32> MidiCommandQueue;
 
@@ -198,9 +232,6 @@ public:
 	// where a deadlock can occur
 	Common::Mutex _mutex;
 
-	int16 tryToOwnChannel(MusicEntry *caller, int16 bestChannel);
-	void freeChannels(MusicEntry *caller);
-
 protected:
 	void sortPlayList();
 
@@ -213,6 +244,11 @@ protected:
 	// If true and a sound has a digital track, the sound from the AdLib track is played
 	bool _useDigitalSFX;
 
+	// remapping:
+	void remapChannels();
+	ChannelRemapping *determineChannelMap();
+	void resetDeviceChannel(int devChannel);
+
 private:
 	MusicList _playList;
 	bool _soundOn;
@@ -221,6 +257,8 @@ private:
 	int8 _channelRemap[16];
 	int8 _globalReverb;
 
+	DeviceChannelUsage _channelMap[16];
+
 	MidiCommandQueue _queuedCommands;
 	MusicType _musicType;
 


Commit: 0dfd742b2183e195b80ec5085ed42fee17a283ee
    https://github.com/scummvm/scummvm/commit/0dfd742b2183e195b80ec5085ed42fee17a283ee
Author: Willem Jan Palenstijn (wjp at usecode.org)
Date: 2013-12-31T04:52:37-08:00

Commit Message:
SCI: Remove no longer necessary hack

Changed paths:
    engines/sci/sound/music.cpp



diff --git a/engines/sci/sound/music.cpp b/engines/sci/sound/music.cpp
index e29afe7..1b8aa55 100644
--- a/engines/sci/sound/music.cpp
+++ b/engines/sci/sound/music.cpp
@@ -455,27 +455,6 @@ void SciMusic::soundPlay(MusicEntry *pSnd) {
 			Common::StackLock lock(_mutex);
 			pSnd->pMidiParser->mainThreadBegin();
 
-			if (pSnd->status != kSoundPaused) {
-				// Stop any in progress music fading, as that will reset the
-				// volume of the sound channels that the faded song occupies..
-				// Fixes bug #3266480 and partially fixes bug #3041738.
-				// CHECKME: Is this the right thing to do? Are these
-				// overlapping channels not a deeper underlying problem?
-				for (uint i = 0; i < playListCount; i++) {
-					// Is another MIDI song being faded down? If yes, stop it
-					// immediately instead
-					if (_playList[i]->fadeStep < 0 && _playList[i]->pMidiParser) {
-						_playList[i]->status = kSoundStopped;
-						if (_soundVersion <= SCI_VERSION_0_LATE)
-							_playList[i]->isQueued = false;
-						_playList[i]->pMidiParser->stop();
-						remapChannels();
-						_playList[i]->fadeStep = 0;
-						_playList[i]->fadeCompleted = true;
-					}
-				}
-			}
-
 			if (pSnd->status != kSoundPaused)
 				pSnd->pMidiParser->sendInitCommands();
 			pSnd->pMidiParser->setVolume(pSnd->volume);


Commit: b7bfa2281869df97ace11e551126b8e8ae179f78
    https://github.com/scummvm/scummvm/commit/b7bfa2281869df97ace11e551126b8e8ae179f78
Author: Willem Jan Palenstijn (wjp at usecode.org)
Date: 2013-12-31T06:22:33-08:00

Commit Message:
Merge pull request #419 from wjp/sci_remap

SCI: Rewrite MIDI channel remapping

Changed paths:
    engines/sci/resource.h
    engines/sci/resource_audio.cpp
    engines/sci/sound/midiparser_sci.cpp
    engines/sci/sound/midiparser_sci.h
    engines/sci/sound/music.cpp
    engines/sci/sound/music.h









More information about the Scummvm-git-logs mailing list