[Scummvm-cvs-logs] SF.net SVN: scummvm: [21104] scummvm/trunk/engines/scumm

Jamieson630 at users.sourceforge.net Jamieson630 at users.sourceforge.net
Sun Mar 5 16:59:47 CET 2006


Revision: 21104
Author:   Jamieson630
Date:     2006-03-05 13:30:44 -0800 (Sun, 05 Mar 2006)
ViewCVS:  http://svn.sourceforge.net/scummvm?rev=21104&view=rev

Log Message:
-----------
Restructured IMuse and IMuseInternal.

* IMuse is no longer a concurrency front-end.
* IMuseInternal now derives from IMuse.
* Common::StackLock used to protect thread-sensitive interface methods (same as IMuseDigital).
* clear_queue() included in stopAllSounds() so it can be removed from the public interface.
* Game ID now specified at init using property().
* Timer callbacks receive a struct containing IMuseInternal and MidiDriver refs, instead of just the latter.
* OSystem pointer from init is now cached and used instead of global.

All references to the g_system and g_scumm globals are now gone. BOOYAH!

Tested with MI2, DOTT and S&M, under Windows, in Native MIDI, Adlib, and Mixed modes. No regressions or concurrency issues observed. Manifestations of the latter are the biggest concern at this point.

Modified Paths:
--------------
    scummvm/trunk/engines/scumm/imuse/imuse.cpp
    scummvm/trunk/engines/scumm/imuse/imuse.h
    scummvm/trunk/engines/scumm/imuse/imuse_internal.h
    scummvm/trunk/engines/scumm/imuse/imuse_player.cpp
    scummvm/trunk/engines/scumm/scumm.cpp
    scummvm/trunk/engines/scumm/sound.cpp
Modified: scummvm/trunk/engines/scumm/imuse/imuse.cpp
===================================================================
--- scummvm/trunk/engines/scumm/imuse/imuse.cpp	2006-03-05 20:11:43 UTC (rev 21103)
+++ scummvm/trunk/engines/scumm/imuse/imuse.cpp	2006-03-05 21:30:44 UTC (rev 21104)
@@ -215,84 +215,6 @@
 	return driver;
 }
 
-bool IMuseInternal::startSound(int sound) {
-	Player *player;
-	void *ptr;
-
-	// Do not start a sound if it is already set to start on an ImTrigger
-	// event. This fixes carnival music problems where a sound has been set
-	// to trigger at the right time, but then is started up immediately
-	// anyway, only to be restarted later when the trigger occurs.
-	//
-	// However, we have to make sure the sound with the trigger is actually
-	// playing, otherwise the music may stop when Sam and Max are thrown
-	// out of Bumpusville, because entering the mansion sets up a trigger
-	// for a sound that isn't necessarily playing. This is somewhat related
-	// to bug #780918.
-
-	int i;
-	ImTrigger *trigger = _snm_triggers;
-	for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trigger) {
-		if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus(trigger->sound,true))
-			return false;
-	}
-
-	ptr = findStartOfSound(sound);
-	if (!ptr) {
-		debug(2, "IMuseInternal::startSound(): Couldn't find sound %d!", sound);
-		return false;
-	}
-
-	// Check which MIDI driver this track should use.
-	// If it's NULL, it ain't something we can play.
-	MidiDriver *driver = getBestMidiDriver(sound);
-	if (!driver)
-		return false;
-
-	// If the requested sound is already playing, start it over
-	// from scratch. This was originally a hack to prevent Sam & Max
-	// iMuse messiness while upgrading the iMuse engine, but it
-	// is apparently necessary to deal with fade-and-restart
-	// race conditions that were observed in MI2. Reference
-	// Bug #590511 and Patch #607175 (which was reversed to fix
-	// an FOA regression: Bug #622606).
-	player = findActivePlayer(sound);
-	if (!player)
-		player = allocate_player(128);
-	if (!player)
-		return false;
-
-	// HACK: This is to work around a problem at the Dino Bungie Memorial.
-	// There are three pieces of music involved here:
-	//
-	// 80 - Main theme (looping)
-	// 81 - Music when entering Rex's and Wally's room (not looping)
-	// 82 - Music when listening to Rex or Wally
-	//
-	// When entering, tune 81 starts, tune 80 is faded down (not out) and
-	// a trigger is set in tune 81 to fade tune 80 back up.
-	//
-	// When listening to Rex or Wally, tune 82 is started, tune 81 is faded
-	// out and tune 80 is faded down even further.
-	//
-	// However, when tune 81 is faded out its trigger will cause tune 80 to
-	// fade back up, resulting in two tunes being played simultaneously at
-	// full blast. It's no use trying to keep tune 81 playing at volume 0.
-	// It doesn't loop, so eventually it will terminate on its own.
-	//
-	// I don't know how the original interpreter handled this - or even if
-	// it handled it at all - but it looks like sloppy scripting to me. Our
-	// workaround is to clear the trigger if the player listens to Rex or
-	// Wally before tune 81 has finished on its own.
-
-	if (g_scumm->_game.id == GID_SAMNMAX && sound == 82 && getSoundStatus(81, false))
-		ImClearTrigger(81, 1);
-
-	player->clear();
-	return player->startSound(sound, driver, _direct_passthrough);
-}
-
-
 Player *IMuseInternal::allocate_player(byte priority) {
 	Player *player = _players, *best = NULL;
 	int i;
@@ -335,28 +257,14 @@
 	}
 }
 
-int IMuseInternal::stopSound(int sound) {
-	int r = -1;
-	Player *player = findActivePlayer(sound);
-	if (player) {
-		player->clear();
-		r = 0;
-	}
-	return r;
-}
+////////////////////////////////////////
+//
+// IMuse mixin interface methods
+//
+////////////////////////////////////////
 
-int IMuseInternal::stopAllSounds() {
-	Player *player = _players;
-	int i;
-
-	for (i = ARRAYSIZE(_players); i != 0; i--, player++) {
-		if (player->isActive())
-			player->clear();
-	}
-	return 0;
-}
-
 void IMuseInternal::on_timer(MidiDriver *midi) {
+	Common::StackLock lock(_mutex, "IMuseInternal::on_timer()");
 	if (_paused || !_initialized)
 		return;
 
@@ -365,259 +273,200 @@
 	sequencer_timers(midi);
 }
 
-int IMuseInternal::getMusicTimer() const {
-	int best_time = 0;
-	const Player *player = _players;
-	int i;
+void IMuseInternal::pause(bool paused) {
+	Common::StackLock lock(_mutex, "IMuseInternal::pause()");
+	if (_paused == paused)
+		return;
+	int vol = _music_volume;
+	if (paused)
+		_music_volume = 0;
+	update_volumes();
+	_music_volume = vol;
 
-	for (i = ARRAYSIZE(_players); i != 0; i--, player++) {
-		if (player->isActive()) {
-			int timer = player->getMusicTimer();
-			if (timer > best_time)
-				best_time = timer;
-		}
+	// Fix for Bug #817871. The MT-32 apparently fails
+	// sometimes to respond to a channel volume message
+	// (or only uses it for subsequent note events).
+	// The result is hanging notes on pause. Reportedly
+	// happens in the original distro, too. To fix that,
+	// just send AllNotesOff to the channels.
+	if (_midi_native && _native_mt32) {
+		for (int i = 0; i < 16; ++i)
+			_midi_native->send(123 << 8 | 0xB0 | i);
 	}
-	return best_time;
-}
 
-void IMuseInternal::sequencer_timers(MidiDriver *midi) {
-	Player *player = _players;
-	int i;
-	for (i = ARRAYSIZE(_players); i != 0; i--, player++) {
-		if (player->isActive() && player->getMidiDriver() == midi) {
-			player->onTimer();
-		}
-	}
+	_paused = paused;
 }
 
-void IMuseInternal::handle_marker(uint id, byte data) {
-	uint16 *p = 0;
-	uint pos;
+int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) {
+	Common::StackLock lock(_mutex, "IMuseInternal::save_or_load()");
+	const SaveLoadEntry mainEntries[] = {
+		MKLINE(IMuseInternal, _queue_end, sleUint8, VER(8)),
+		MKLINE(IMuseInternal, _queue_pos, sleUint8, VER(8)),
+		MKLINE(IMuseInternal, _queue_sound, sleUint16, VER(8)),
+		MKLINE(IMuseInternal, _queue_adding, sleByte, VER(8)),
+		MKLINE(IMuseInternal, _queue_marker, sleByte, VER(8)),
+		MKLINE(IMuseInternal, _queue_cleared, sleByte, VER(8)),
+		MKLINE(IMuseInternal, _master_volume, sleByte, VER(8)),
+		MKLINE(IMuseInternal, _trigger_count, sleUint16, VER(8)),
+		MKLINE(IMuseInternal, _snm_trigger_index, sleUint16, VER(54)),
+		MKARRAY(IMuseInternal, _channel_volume[0], sleUint16, 8, VER(8)),
+		MKARRAY(IMuseInternal, _volchan_table[0], sleUint16, 8, VER(8)),
+		MKEND()
+	};
 
-	if (_queue_adding && _queue_sound == id && data == _queue_marker)
-		return;
+	const SaveLoadEntry cmdQueueEntries[] = {
+		MKARRAY(CommandQueue, array[0], sleUint16, 8, VER(23)),
+		MKEND()
+	};
 
-	// Fix for bug #733401, revised for bug #761637:
-	// It would seem that sometimes a marker is in the queue
-	// but not at the head position. In the case of our bug,
-	// this seems to be the result of commands in the queue
-	// for songs that are no longer playing. So we skip
-	// ahead to the appropriate marker, effectively chomping
-	// anything in the queue before it. This fixes the FOA
-	// end credits music, but needs to be tested for inappopriate
-	// behavior elsewhere.
-	pos = _queue_end;
-	while (pos != _queue_pos) {
-		p = _cmd_queue[pos].array;
-		if (p[0] == TRIGGER_ID && p[1] == id && p[2] == data)
-			break;
-		pos = (pos + 1) % ARRAYSIZE(_cmd_queue);
-	}
+	// VolumeFader is obsolete.
+	const SaveLoadEntry volumeFaderEntries[] = {
+		MK_OBSOLETE(VolumeFader, player, sleUint16, VER(8), VER(16)),
+		MK_OBSOLETE(VolumeFader, active, sleUint8, VER(8), VER(16)),
+		MK_OBSOLETE(VolumeFader, curvol, sleUint8, VER(8), VER(16)),
+		MK_OBSOLETE(VolumeFader, speed_lo_max, sleUint16, VER(8), VER(16)),
+		MK_OBSOLETE(VolumeFader, num_steps, sleUint16, VER(8), VER(16)),
+		MK_OBSOLETE(VolumeFader, speed_hi, sleInt8, VER(8), VER(16)),
+		MK_OBSOLETE(VolumeFader, direction, sleInt8, VER(8), VER(16)),
+		MK_OBSOLETE(VolumeFader, speed_lo, sleInt8, VER(8), VER(16)),
+		MK_OBSOLETE(VolumeFader, speed_lo_counter, sleUint16, VER(8), VER(16)),
+		MKEND()
+	};
 
-	if (pos == _queue_pos)
-		return;
+	const SaveLoadEntry snmTriggerEntries[] = {
+		MKLINE(ImTrigger, sound, sleInt16, VER(54)),
+		MKLINE(ImTrigger, id, sleByte, VER(54)),
+		MKLINE(ImTrigger, expire, sleUint16, VER(54)),
+		MKARRAY(ImTrigger, command[0], sleUint16, 8, VER(54)),
+		MKEND()
+	};
 
-	if (pos != _queue_end)
-		debug(0, "Skipping entries in iMuse command queue to reach marker");
-
-	_trigger_count--;
-	_queue_cleared = false;
-	do {
-		pos = (pos + 1) % ARRAYSIZE(_cmd_queue);
-		if (_queue_pos == pos)
-			break;
-		p = _cmd_queue[pos].array;
-		if (*p++ != COMMAND_ID)
-			break;
-		_queue_end = pos;
-
-		doCommand(p[0], p[1], p[2], p[3], p[4], p[5], p[6], 0);
-
-		if (_queue_cleared)
-			return;
-		pos = _queue_end;
-	} while (1);
-
-	_queue_end = pos;
-}
-
-int IMuseInternal::get_channel_volume(uint a) {
-	if (a < 8)
-		return _channel_volume_eff[a];
-	return (_master_volume * _music_volume / 255) / 2;
-}
-
-Part *IMuseInternal::allocate_part(byte pri, MidiDriver *midi) {
-	Part *part, *best = NULL;
 	int i;
 
-	for (i = ARRAYSIZE(_parts), part = _parts; i != 0; i--, part++) {
-		if (!part->_player)
-			return part;
-		if (pri >= part->_pri_eff) {
-			pri = part->_pri_eff;
-			best = part;
-		}
-	}
+	ser->saveLoadEntries(this, mainEntries);
+	ser->saveLoadArrayOf(_cmd_queue, ARRAYSIZE(_cmd_queue), sizeof(_cmd_queue[0]), cmdQueueEntries);
+	ser->saveLoadArrayOf(_snm_triggers, ARRAYSIZE(_snm_triggers), sizeof(_snm_triggers[0]), snmTriggerEntries);
 
-	if (best) {
-		best->uninit();
-		reallocateMidiChannels(midi);
-	} else {
-		debug(1, "Denying part request");
-	}
-	return best;
-}
+	// The players
+	for (i = 0; i < ARRAYSIZE(_players); ++i)
+		_players[i].saveLoadWithSerializer(ser);
 
-int IMuseInternal::getSoundStatus(int sound, bool ignoreFadeouts) const {
-	int i;
-	const Player *player = _players;
+	// The parts
+	for (i = 0; i < ARRAYSIZE(_parts); ++i)
+		_parts[i].saveLoadWithSerializer(ser);
 
-	for (i = ARRAYSIZE(_players); i != 0; i--, player++) {
-		if (player->isActive() && (!ignoreFadeouts || !player->isFadingOut())) {
-			if (sound == -1)
-				return player->getID();
-			else if (player->getID() == (uint16)sound)
-				return 1;
+	{ // Load/save the instrument definitions, which were revamped with V11.
+		Part *part = &_parts[0];
+		if (ser->getVersion() >= VER(11)) {
+			for (i = ARRAYSIZE(_parts); i; --i, ++part) {
+				part->_instrument.saveOrLoad(ser);
+			}
+		} else {
+			for (i = ARRAYSIZE(_parts); i; --i, ++part)
+				part->_instrument.clear();
 		}
 	}
-	return (sound == -1) ? 0 : get_queue_sound_status(sound);
-}
 
-int IMuseInternal::get_queue_sound_status(int sound) const {
-	const uint16 *a;
-	int i, j;
+	// VolumeFader has been replaced with the more generic ParameterFader.
+	// FIXME: replace this loop by something like
+	// if (loading && version <= 16)  ser->skip(XXX bytes);
+	for (i = 0; i < 8; ++i)
+		ser->saveLoadEntries(0, volumeFaderEntries);
 
-	j = _queue_pos;
-	i = _queue_end;
+	if (ser->isLoading()) {
+		// Load all sounds that we need
+		fix_players_after_load(scumm);
+		fix_parts_after_load();
+		setImuseMasterVolume(_master_volume);
 
-	while (i != j) {
-		a = _cmd_queue[i].array;
-		if (a[0] == COMMAND_ID && a[1] == 8 && a[2] == (uint16)sound)
-			return 2;
-		i = (i + 1) % ARRAYSIZE(_cmd_queue);
+		if (_midi_native)
+			reallocateMidiChannels(_midi_native);
+		if (_midi_adlib)
+			reallocateMidiChannels(_midi_adlib);
 	}
 
-	for (i = 0; i < ARRAYSIZE (_deferredCommands); ++i) {
-		if (_deferredCommands[i].time_left && _deferredCommands[i].a == 8 &&
-			_deferredCommands[i].b == sound) {
-			return 2;
-		}
-	}
-
 	return 0;
 }
 
-int IMuseInternal::set_volchan(int sound, int volchan) {
-	int r;
-	int i;
-	int num;
-	Player *player, *best, *sameid;
+bool IMuseInternal::get_sound_active(int sound) const {
+	Common::StackLock lock(_mutex, "IMuseInternal::get_sound_active()");
+	return getSoundStatus_internal (sound, false);
+}
 
-	r = get_volchan_entry(volchan);
-	if (r == -1)
-		return -1;
-
-	if (r >= 8) {
-		player = findActivePlayer(sound);
-		if (player && player->_vol_chan != (uint16)volchan) {
-			player->_vol_chan = volchan;
-			player->setVolume(player->getVolume());
-			return 0;
-		}
-		return -1;
-	} else {
-		best = NULL;
-		num = 0;
-		sameid = NULL;
-		for (i = ARRAYSIZE(_players), player = _players; i != 0; i--, player++) {
-			if (player->isActive()) {
-				if (player->_vol_chan == (uint16)volchan) {
-					num++;
-					if (!best || player->getPriority() <= best->getPriority())
-						best = player;
-				} else if (player->getID() == (uint16)sound) {
-					sameid = player;
-				}
-			}
-		}
-		if (sameid == NULL)
-			return -1;
-		if (num >= r)
-			best->clear();
-		player->_vol_chan = volchan;
-		player->setVolume(player->getVolume());
-		return 0;
-	}
+int32 IMuseInternal::doCommand(int numargs, int a[]) {
+	Common::StackLock lock(_mutex, "IMuseInternal::doCommand()");
+	return doCommand_internal (numargs, a);
 }
 
-int IMuseInternal::clear_queue() {
-	_queue_adding = false;
-	_queue_cleared = true;
-	_queue_pos = 0;
-	_queue_end = 0;
-	_trigger_count = 0;
-	return 0;
+void IMuseInternal::setBase(byte **base) {
+	Common::StackLock lock(_mutex, "IMuseInternal::setBase()");
+	_base_sounds = base;
 }
 
-int IMuseInternal::enqueue_command(int a, int b, int c, int d, int e, int f, int g) {
-	uint16 *p;
-	uint i;
+uint32 IMuseInternal::property(int prop, uint32 value) {
+	Common::StackLock lock(_mutex, "IMuseInternal::property()");
+	switch (prop) {
+	case IMuse::PROP_TEMPO_BASE:
+		// This is a specified as a percentage of normal
+		// music speed. The number must be an integer
+		// ranging from 50 to 200(for 50% to 200% normal speed).
+		if (value >= 50 && value <= 200)
+			_tempoFactor = value;
+		break;
 
-	i = _queue_pos;
+	case IMuse::PROP_NATIVE_MT32:
+		_native_mt32 = (value > 0);
+		Instrument::nativeMT32(_native_mt32);
+		if (_midi_native && _native_mt32)
+			initMT32(_midi_native);
+		break;
 
-	if (i == _queue_end)
-		return -1;
+	case IMuse::PROP_GS:
+		_enable_gs = (value > 0);
 
-	if (a == -1) {
-		_queue_adding = false;
-		_trigger_count++;
-		return 0;
-	}
+		// If True Roland MT-32 is not selected, run in GM or GS mode.
+		// If it is selected, change the Roland GS synth to MT-32 mode.
+		if (_midi_native && !_native_mt32)
+			initGM(_midi_native);
+		else if (_midi_native && _native_mt32 && _enable_gs) {
+			_sc55 = true;
+			initGM(_midi_native);
+		}
+		break;
 
-	p = _cmd_queue[_queue_pos].array;
-	p[0] = COMMAND_ID;
-	p[1] = a;
-	p[2] = b;
-	p[3] = c;
-	p[4] = d;
-	p[5] = e;
-	p[6] = f;
-	p[7] = g;
+	case IMuse::PROP_LIMIT_PLAYERS:
+		if (value > 0 && value <= ARRAYSIZE(_players))
+			_player_limit = (int)value;
+		break;
 
-	i = (i + 1) % ARRAYSIZE(_cmd_queue);
+	case IMuse::PROP_RECYCLE_PLAYERS:
+		_recycle_players = (value != 0);
+		break;
 
-	if (_queue_end != i) {
-		_queue_pos = i;
-		return 0;
-	} else {
-		_queue_pos = (i - 1) % ARRAYSIZE(_cmd_queue);
-		return -1;
+	case IMuse::PROP_DIRECT_PASSTHROUGH:
+		_direct_passthrough = (value != 0);
+		break;
+
+	case IMuse::PROP_GAME_ID:
+		_game_id = value;
+		break;
 	}
-}
 
-int IMuseInternal::query_queue(int param) {
-	switch (param) {
-	case 0: // Get trigger count
-		return _trigger_count;
-	case 1: // Get trigger type
-		if (_queue_end == _queue_pos)
-			return -1;
-		return _cmd_queue[_queue_end].array[1];
-	case 2: // Get trigger sound
-		if (_queue_end == _queue_pos)
-			return 0xFF;
-		return _cmd_queue[_queue_end].array[2];
-	default:
-		return -1;
-	}
+	return 0;
 }
 
-int IMuseInternal::setMusicVolume(uint vol) {
+////////////////////////////////////////
+//
+// MusicEngine interface methods
+//
+////////////////////////////////////////
+
+void IMuseInternal::setMusicVolume (int vol) {
+	Common::StackLock lock(_mutex, "IMuseInternal::setMusicVolume()");
 	if (vol > 255)
 		vol = 255;
 	if (_music_volume == vol)
-		return 0;
+		return;
 	_music_volume = vol;
 	vol = _master_volume * _music_volume / 255;
 	for (uint i = 0; i < ARRAYSIZE(_channel_volume); i++) {
@@ -625,34 +474,53 @@
 	}
 	if (!_paused)
 		update_volumes();
-	return 0;
 }
 
-int IMuseInternal::setImuseMasterVolume(uint vol) {
-	if (vol > 255)
-		vol = 255;
-	if (_master_volume == vol)
-		return 0;
-	_master_volume = vol;
-	vol = _master_volume * _music_volume / 255;
-	for (uint i = 0; i < ARRAYSIZE(_channel_volume); i++) {
-		_channel_volume_eff[i] = _channel_volume[i] * vol / 255;
-	}
-	if (!_paused)
-		update_volumes();
-	return 0;
+void IMuseInternal::startSound(int sound) {
+	Common::StackLock lock(_mutex, "IMuseInternal::startSound()");
+	startSound_internal (sound);
 }
 
-int IMuseInternal::terminate1() {
-	_initialized = false;
-	stopAllSounds();
-	return 0;
+void IMuseInternal::stopSound(int sound) {
+	Common::StackLock lock(_mutex, "IMuseInternal::stopSound()");
+	stopSound_internal (sound);
 }
 
-// This is the stuff that has to be done
-// outside the monitor's mutex, otherwise
-// a deadlock occurs.
-int IMuseInternal::terminate2() {
+void IMuseInternal::stopAllSounds() {
+	Common::StackLock lock(_mutex, "IMuseInternal::stopAllSounds()");
+	stopAllSounds_internal();
+}
+
+int IMuseInternal::getSoundStatus (int sound) const {
+	Common::StackLock lock(_mutex, "IMuseInternal::getSoundStatus()");
+	return getSoundStatus_internal (sound, true);
+}
+
+int IMuseInternal::getMusicTimer() const {
+	Common::StackLock lock(_mutex, "IMuseInternal::getMusicTimer()");
+	int best_time = 0;
+	const Player *player = _players;
+	for (int i = ARRAYSIZE(_players); i; i--, player++) {
+		if (player->isActive()) {
+			int timer = player->getMusicTimer();
+			if (timer > best_time)
+				best_time = timer;
+		}
+	}
+	return best_time;
+}
+
+void IMuseInternal::terminate() {
+	// Do just enough stuff inside the mutex to
+	// make sure any MIDI timing threads won't
+	// interrupt us, and then do the rest outside
+	// the mutex.
+	{
+		Common::StackLock lock(_mutex, "IMuseInternal::terminate()");
+		_initialized = false;
+		stopAllSounds_internal();
+	}
+
 	if (_midi_adlib) {
 		_midi_adlib->close();
 		delete _midi_adlib;
@@ -663,42 +531,135 @@
 		if (_native_mt32) {
 			// Reset the MT-32
 			_midi_native->sysEx((const byte *) "\x41\x10\x16\x12\x7f\x00\x00\x01\x00", 9);
-			g_system->delayMillis(250);
+			_system->delayMillis(250);
 		}
 
 		_midi_native->close();
 		delete _midi_native;
 		_midi_native = 0;
 	}
-
-	return 0;
 }
 
-int IMuseInternal::enqueue_trigger(int sound, int marker) {
-	uint16 *p;
-	uint pos;
+////////////////////////////////////////
+//
+// Internal versions of the IMuse and
+// MusicEngine base class methods.
+// These methods assume the appropriate
+// mutex locks have already been set,
+// and may also have slightly different
+// semantics than the interface methods.
+//
+////////////////////////////////////////
 
-	pos = _queue_pos;
+bool IMuseInternal::startSound_internal (int sound) {
+	// Do not start a sound if it is already set to start on an ImTrigger
+	// event. This fixes carnival music problems where a sound has been set
+	// to trigger at the right time, but then is started up immediately
+	// anyway, only to be restarted later when the trigger occurs.
+	//
+	// However, we have to make sure the sound with the trigger is actually
+	// playing, otherwise the music may stop when Sam and Max are thrown
+	// out of Bumpusville, because entering the mansion sets up a trigger
+	// for a sound that isn't necessarily playing. This is somewhat related
+	// to bug #780918.
 
-	p = _cmd_queue[pos].array;
-	p[0] = TRIGGER_ID;
-	p[1] = sound;
-	p[2] = marker;
+	int i;
+	ImTrigger *trigger = _snm_triggers;
+	for (i = ARRAYSIZE(_snm_triggers); i; --i, ++trigger) {
+		if (trigger->sound && trigger->id && trigger->command[0] == 8 && trigger->command[1] == sound && getSoundStatus_internal (trigger->sound,true))
+			return false;
+	}
 
-	pos = (pos + 1) % ARRAYSIZE(_cmd_queue);
-	if (_queue_end == pos) {
-		_queue_pos = (pos - 1) % ARRAYSIZE(_cmd_queue);
-		return -1;
+	void *ptr = findStartOfSound(sound);
+	if (!ptr) {
+		debug(2, "IMuseInternal::startSound(): Couldn't find sound %d!", sound);
+		return false;
 	}
 
-	_queue_pos = pos;
-	_queue_adding = true;
-	_queue_sound = sound;
-	_queue_marker = marker;
+	// Check which MIDI driver this track should use.
+	// If it's NULL, it ain't something we can play.
+	MidiDriver *driver = getBestMidiDriver(sound);
+	if (!driver)
+		return false;
+
+	// If the requested sound is already playing, start it over
+	// from scratch. This was originally a hack to prevent Sam & Max
+	// iMuse messiness while upgrading the iMuse engine, but it
+	// is apparently necessary to deal with fade-and-restart
+	// race conditions that were observed in MI2. Reference
+	// Bug #590511 and Patch #607175 (which was reversed to fix
+	// an FOA regression: Bug #622606).
+	Player *player = findActivePlayer(sound);
+	if (!player)
+		player = allocate_player(128);
+	if (!player)
+		return false;
+
+	// HACK: This is to work around a problem at the Dino Bungie Memorial.
+	// There are three pieces of music involved here:
+	//
+	// 80 - Main theme (looping)
+	// 81 - Music when entering Rex's and Wally's room (not looping)
+	// 82 - Music when listening to Rex or Wally
+	//
+	// When entering, tune 81 starts, tune 80 is faded down (not out) and
+	// a trigger is set in tune 81 to fade tune 80 back up.
+	//
+	// When listening to Rex or Wally, tune 82 is started, tune 81 is faded
+	// out and tune 80 is faded down even further.
+	//
+	// However, when tune 81 is faded out its trigger will cause tune 80 to
+	// fade back up, resulting in two tunes being played simultaneously at
+	// full blast. It's no use trying to keep tune 81 playing at volume 0.
+	// It doesn't loop, so eventually it will terminate on its own.
+	//
+	// I don't know how the original interpreter handled this - or even if
+	// it handled it at all - but it looks like sloppy scripting to me. Our
+	// workaround is to clear the trigger if the player listens to Rex or
+	// Wally before tune 81 has finished on its own.
+
+	if (_game_id == GID_SAMNMAX && sound == 82 && getSoundStatus_internal (81, false))
+		ImClearTrigger(81, 1);
+
+	player->clear();
+	return player->startSound(sound, driver, _direct_passthrough);
+}
+
+int IMuseInternal::stopSound_internal (int sound) {
+	int r = -1;
+	Player *player = findActivePlayer(sound);
+	if (player) {
+		player->clear();
+		r = 0;
+	}
+	return r;
+}
+
+int IMuseInternal::stopAllSounds_internal() {
+	Player *player = _players;
+	for (int i = ARRAYSIZE(_players); i; i--, player++) {
+		if (player->isActive())
+			player->clear();
+	}
 	return 0;
 }
 
-int32 IMuseInternal::doCommand(int a, int b, int c, int d, int e, int f, int g, int h) {
+int IMuseInternal::getSoundStatus_internal (int sound, bool ignoreFadeouts) const {
+	const Player *player = _players;
+	for (int i = ARRAYSIZE(_players); i; i--, player++) {
+		if (player->isActive() && (!ignoreFadeouts || !player->isFadingOut())) {
+			if (sound == -1)
+				return player->getID();
+			else if (player->getID() == (uint16)sound)
+				return 1;
+		}
+	}
+	return (sound == -1) ? 0 : get_queue_sound_status(sound);
+}
+
+int32 IMuseInternal::doCommand_internal
+	(int a, int b, int c, int d, int e, int f, int g, int h)
+{
 	int args[8];
 	args[0] = a;
 	args[1] = b;
@@ -708,14 +669,14 @@
 	args[5] = f;
 	args[6] = g;
 	args[7] = h;
-	return doCommand(8, args);
+	return doCommand_internal (8, args);
 }
 
-int32 IMuseInternal::doCommand(int numargs, int a[]) {
-	int i;
-
+int32 IMuseInternal::doCommand_internal (int numargs, int a[]) {
 	if (numargs < 1)
 		return -1;
+
+	int i;
 	byte cmd = a[0] & 0xFF;
 	byte param = a[0] >> 8;
 	Player *player = NULL;
@@ -744,17 +705,17 @@
 			debug(0, "IMuse doCommand(7) - getMasterVolume (%d)", a[1]);
 			return _master_volume / 2; // Convert from 0-255 to 0-127
 		case 8:
-			return startSound(a[1]) ? 0 : -1;
+			return startSound_internal (a[1]) ? 0 : -1;
 		case 9:
-			return stopSound(a[1]);
+			return stopSound_internal (a[1]);
 		case 10: // FIXME: Sam and Max - Not sure if this is correct
-			return stopAllSounds();
+			return stopAllSounds_internal();
 		case 11:
-			return stopAllSounds();
+			return stopAllSounds_internal();
 		case 12:
 			// Sam & Max: Player-scope commands
 			player = findActivePlayer(a[1]);
-			if (!player)
+			if (player == NULL)
 				return -1;
 
 			switch (a[3]) {
@@ -766,7 +727,7 @@
 			}
 			return -1;
 		case 13:
-			return getSoundStatus(a[1], true);
+			return getSoundStatus_internal (a[1], true);
 		case 14:
 			// Sam and Max: Parameter fade
 			player = findActivePlayer(a[1]);
@@ -786,7 +747,7 @@
 			debug(0, "IMuse doCommand(16) - set_volchan (%d, %d)", a[1], a[2]);
 			return set_volchan(a[1], a[2]);
 		case 17:
-			if (g_scumm->_game.id != GID_SAMNMAX) {
+			if (_game_id != GID_SAMNMAX) {
 				debug(0, "IMuse doCommand(17) - set_channel_volume (%d, %d)", a[1], a[2]);
 				return set_channel_volume(a[1], a[2]);
 			} else {
@@ -801,7 +762,7 @@
 				}
 			}
 		case 18:
-			if (g_scumm->_game.id != GID_SAMNMAX) {
+			if (_game_id != GID_SAMNMAX) {
 				return set_volchan_entry(a[1], a[2]);
 			} else {
 				// Sam & Max: ImCheckTrigger.
@@ -849,7 +810,7 @@
 
 		switch (cmd) {
 		case 0:
-			if (g_scumm->_game.id == GID_SAMNMAX) {
+			if (_game_id == GID_SAMNMAX) {
 				if (a[3] == 1) // Measure number
 					return ((player->getBeatIndex() - 1) >> 2) + 1;
 				else if (a[3] == 2) // Beat number
@@ -859,7 +820,7 @@
 				return player->getParam(a[2], a[3]);
 			}
 		case 1:
-			if (g_scumm->_game.id == GID_SAMNMAX) {
+			if (_game_id == GID_SAMNMAX) {
 				// FIXME: Could someone verify this?
 				//
 				// This jump instruction is known to be used in
@@ -946,6 +907,265 @@
 	return -1;
 }
 
+// mixin end
+
+void IMuseInternal::sequencer_timers(MidiDriver *midi) {
+	Player *player = _players;
+	int i;
+	for (i = ARRAYSIZE(_players); i != 0; i--, player++) {
+		if (player->isActive() && player->getMidiDriver() == midi) {
+			player->onTimer();
+		}
+	}
+}
+
+void IMuseInternal::handle_marker(uint id, byte data) {
+	uint16 *p = 0;
+	uint pos;
+
+	if (_queue_adding && _queue_sound == id && data == _queue_marker)
+		return;
+
+	// Fix for bug #733401, revised for bug #761637:
+	// It would seem that sometimes a marker is in the queue
+	// but not at the head position. In the case of our bug,
+	// this seems to be the result of commands in the queue
+	// for songs that are no longer playing. So we skip
+	// ahead to the appropriate marker, effectively chomping
+	// anything in the queue before it. This fixes the FOA
+	// end credits music, but needs to be tested for inappopriate
+	// behavior elsewhere.
+	pos = _queue_end;
+	while (pos != _queue_pos) {
+		p = _cmd_queue[pos].array;
+		if (p[0] == TRIGGER_ID && p[1] == id && p[2] == data)
+			break;
+		pos = (pos + 1) % ARRAYSIZE(_cmd_queue);
+	}
+
+	if (pos == _queue_pos)
+		return;
+
+	if (pos != _queue_end)
+		debug(0, "Skipping entries in iMuse command queue to reach marker");
+
+	_trigger_count--;
+	_queue_cleared = false;
+	do {
+		pos = (pos + 1) % ARRAYSIZE(_cmd_queue);
+		if (_queue_pos == pos)
+			break;
+		p = _cmd_queue[pos].array;
+		if (*p++ != COMMAND_ID)
+			break;
+		_queue_end = pos;
+
+		doCommand_internal (p[0], p[1], p[2], p[3], p[4], p[5], p[6], 0);
+
+		if (_queue_cleared)
+			return;
+		pos = _queue_end;
+	} while (1);
+
+	_queue_end = pos;
+}
+
+int IMuseInternal::get_channel_volume(uint a) {
+	if (a < 8)
+		return _channel_volume_eff[a];
+	return (_master_volume * _music_volume / 255) / 2;
+}
+
+Part *IMuseInternal::allocate_part(byte pri, MidiDriver *midi) {
+	Part *part, *best = NULL;
+	int i;
+
+	for (i = ARRAYSIZE(_parts), part = _parts; i != 0; i--, part++) {
+		if (!part->_player)
+			return part;
+		if (pri >= part->_pri_eff) {
+			pri = part->_pri_eff;
+			best = part;
+		}
+	}
+
+	if (best) {
+		best->uninit();
+		reallocateMidiChannels(midi);
+	} else {
+		debug(1, "Denying part request");
+	}
+	return best;
+}
+
+int IMuseInternal::get_queue_sound_status(int sound) const {
+	const uint16 *a;
+	int i, j;
+
+	j = _queue_pos;
+	i = _queue_end;
+
+	while (i != j) {
+		a = _cmd_queue[i].array;
+		if (a[0] == COMMAND_ID && a[1] == 8 && a[2] == (uint16)sound)
+			return 2;
+		i = (i + 1) % ARRAYSIZE(_cmd_queue);
+	}
+
+	for (i = 0; i < ARRAYSIZE (_deferredCommands); ++i) {
+		if (_deferredCommands[i].time_left && _deferredCommands[i].a == 8 &&
+			_deferredCommands[i].b == sound) {
+			return 2;
+		}
+	}
+
+	return 0;
+}
+
+int IMuseInternal::set_volchan(int sound, int volchan) {
+	int r;
+	int i;
+	int num;
+	Player *player, *best, *sameid;
+
+	r = get_volchan_entry(volchan);
+	if (r == -1)
+		return -1;
+
+	if (r >= 8) {
+		player = findActivePlayer(sound);
+		if (player && player->_vol_chan != (uint16)volchan) {
+			player->_vol_chan = volchan;
+			player->setVolume(player->getVolume());
+			return 0;
+		}
+		return -1;
+	} else {
+		best = NULL;
+		num = 0;
+		sameid = NULL;
+		for (i = ARRAYSIZE(_players), player = _players; i != 0; i--, player++) {
+			if (player->isActive()) {
+				if (player->_vol_chan == (uint16)volchan) {
+					num++;
+					if (!best || player->getPriority() <= best->getPriority())
+						best = player;
+				} else if (player->getID() == (uint16)sound) {
+					sameid = player;
+				}
+			}
+		}
+		if (sameid == NULL)
+			return -1;
+		if (num >= r)
+			best->clear();
+		player->_vol_chan = volchan;
+		player->setVolume(player->getVolume());
+		return 0;
+	}
+}
+
+int IMuseInternal::clear_queue() {
+	_queue_adding = false;
+	_queue_cleared = true;
+	_queue_pos = 0;
+	_queue_end = 0;
+	_trigger_count = 0;
+	return 0;
+}
+
+int IMuseInternal::enqueue_command(int a, int b, int c, int d, int e, int f, int g) {
+	uint16 *p;
+	uint i;
+
+	i = _queue_pos;
+
+	if (i == _queue_end)
+		return -1;
+
+	if (a == -1) {
+		_queue_adding = false;
+		_trigger_count++;
+		return 0;
+	}
+
+	p = _cmd_queue[_queue_pos].array;
+	p[0] = COMMAND_ID;
+	p[1] = a;
+	p[2] = b;
+	p[3] = c;
+	p[4] = d;
+	p[5] = e;
+	p[6] = f;
+	p[7] = g;
+
+	i = (i + 1) % ARRAYSIZE(_cmd_queue);
+
+	if (_queue_end != i) {
+		_queue_pos = i;
+		return 0;
+	} else {
+		_queue_pos = (i - 1) % ARRAYSIZE(_cmd_queue);
+		return -1;
+	}
+}
+
+int IMuseInternal::query_queue(int param) {
+	switch (param) {
+	case 0: // Get trigger count
+		return _trigger_count;
+	case 1: // Get trigger type
+		if (_queue_end == _queue_pos)
+			return -1;
+		return _cmd_queue[_queue_end].array[1];
+	case 2: // Get trigger sound
+		if (_queue_end == _queue_pos)
+			return 0xFF;
+		return _cmd_queue[_queue_end].array[2];
+	default:
+		return -1;
+	}
+}
+
+int IMuseInternal::setImuseMasterVolume(uint vol) {
+	if (vol > 255)
+		vol = 255;
+	if (_master_volume == vol)
+		return 0;
+	_master_volume = vol;
+	vol = _master_volume * _music_volume / 255;
+	for (uint i = 0; i < ARRAYSIZE(_channel_volume); i++) {
+		_channel_volume_eff[i] = _channel_volume[i] * vol / 255;
+	}
+	if (!_paused)
+		update_volumes();
+	return 0;
+}
+
+int IMuseInternal::enqueue_trigger(int sound, int marker) {
+	uint16 *p;
+	uint pos;
+
+	pos = _queue_pos;
+
+	p = _cmd_queue[pos].array;
+	p[0] = TRIGGER_ID;
+	p[1] = sound;
+	p[2] = marker;
+
+	pos = (pos + 1) % ARRAYSIZE(_cmd_queue);
+	if (_queue_end == pos) {
+		_queue_pos = (pos - 1) % ARRAYSIZE(_cmd_queue);
+		return -1;
+	}
+
+	_queue_pos = pos;
+	_queue_adding = true;
+	_queue_sound = sound;
+	_queue_marker = marker;
+	return 0;
+}
+
 int32 IMuseInternal::ImSetTrigger(int sound, int id, int a, int b, int c, int d, int e, int f, int g, int h) {
 	// Sam & Max: ImSetTrigger.
 	// Sets a trigger for a particular player and
@@ -1003,8 +1223,8 @@
 	// NOTE: We ONLY do this if the sound that will trigger the command is actually
 	// playing. Otherwise, there's a problem when exiting and re-entering the
 	// Bumpusville mansion. Ref Bug #780918.
-	if (trig->command[0] == 8 && getSoundStatus(trig->command[1],true) && getSoundStatus(sound,true))
-		stopSound(trig->command[1]);
+	if (trig->command[0] == 8 && getSoundStatus_internal (trig->command[1],true) && getSoundStatus_internal (sound,true))
+		stopSound_internal (trig->command[1]);
 	return 0;
 }
 
@@ -1029,7 +1249,7 @@
 	for (i = 0; i < ARRAYSIZE(_snm_triggers); ++i) {
 		if (_snm_triggers[i].sound == sound) {
 			_snm_triggers[i].sound = _snm_triggers[i].id = 0;
-			doCommand(8, _snm_triggers[i].command);
+			doCommand_internal (8, _snm_triggers[i].command);
 			++count;
 		}
 	}
@@ -1141,57 +1361,6 @@
 	return -1;
 }
 
-uint32 IMuseInternal::property(int prop, uint32 value) {
-	switch (prop) {
-	case IMuse::PROP_TEMPO_BASE:
-		// This is a specified as a percentage of normal
-		// music speed. The number must be an integer
-		// ranging from 50 to 200(for 50% to 200% normal speed).
-		if (value >= 50 && value <= 200)
-			_tempoFactor = value;
-		break;
-
-	case IMuse::PROP_NATIVE_MT32:
-		_native_mt32 = (value > 0);
-		Instrument::nativeMT32(_native_mt32);
-		if (_midi_native && _native_mt32)
-			initMT32(_midi_native);
-		break;
-
-	case IMuse::PROP_GS:
-		_enable_gs = (value > 0);
-
-		// If True Roland MT-32 is not selected, run in GM or GS mode.
-		// If it is selected, change the Roland GS synth to MT-32 mode.
-		if (_midi_native && !_native_mt32)
-			initGM(_midi_native);
-		else if (_midi_native && _native_mt32 && _enable_gs) {
-			_sc55 = true;
-			initGM(_midi_native);
-		}
-		break;
-
-	case IMuse::PROP_LIMIT_PLAYERS:
-		if (value > 0 && value <= ARRAYSIZE(_players))
-			_player_limit = (int)value;
-		break;
-
-	case IMuse::PROP_RECYCLE_PLAYERS:
-		_recycle_players = (value != 0);
-		break;
-
-	case IMuse::PROP_DIRECT_PASSTHROUGH:
-		_direct_passthrough = (value != 0);
-		break;
-	}
-
-	return 0;
-}
-
-void IMuseInternal::setBase(byte **base) {
-	_base_sounds = base;
-}
-
 IMuseInternal *IMuseInternal::create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver) {
 	IMuseInternal *i = new IMuseInternal;
 	i->initialize(syst, nativeMidiDriver, adlibMidiDriver);
@@ -1201,12 +1370,19 @@
 int IMuseInternal::initialize(OSystem *syst, MidiDriver *native_midi, MidiDriver *adlib_midi) {
 	int i;
 
+	_system = syst;
 	_midi_native = native_midi;
 	_midi_adlib = adlib_midi;
-	if (native_midi != NULL)
-		initMidiDriver(native_midi);
-	if (adlib_midi != NULL)
-		initMidiDriver(adlib_midi);
+	if (native_midi != NULL) {
+		_timer_info_native.imuse = this;
+		_timer_info_native.driver = native_midi;
+		initMidiDriver (&_timer_info_native);
+	}
+	if (adlib_midi != NULL) {
+		_timer_info_adlib.imuse = this;
+		_timer_info_adlib.driver = adlib_midi;
+		initMidiDriver (&_timer_info_adlib);
+	}
 
 	if (!_tempoFactor)
 		_tempoFactor = 100;
@@ -1224,14 +1400,14 @@
 	return 0;
 }
 
-void IMuseInternal::initMidiDriver(MidiDriver *midi) {
+void IMuseInternal::initMidiDriver (TimerCallbackInfo *info) {
 	// Open MIDI driver
-	int result = midi->open();
+	int result = info->driver->open();
 	if (result)
 		error("IMuse initialization - %s", MidiDriver::getErrorName(result));
 
 	// Connect to the driver's timer
-	midi->setTimerCallback(midi, &IMuseInternal::midiTimerCallback);
+	info->driver->setTimerCallback (info, &IMuseInternal::midiTimerCallback);
 }
 
 void IMuseInternal::initMT32(MidiDriver *midi) {
@@ -1241,16 +1417,16 @@
 
 	// Reset the MT-32
 	midi->sysEx((const byte *) "\x41\x10\x16\x12\x7f\x00\x00\x01\x00", 9);
-	g_system->delayMillis(250);
+	_system->delayMillis(250);
 
 	// Setup master tune, reverb mode, reverb time, reverb level,
 	// channel mapping, partial reserve and master volume
 	midi->sysEx((const byte *) "\x41\x10\x16\x12\x10\x00\x00\x40\x00\x04\x04\x04\x04\x04\x04\x04\x04\x04\x04\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x64\x77", 31);
-	g_system->delayMillis(250);
+	_system->delayMillis(250);
 
 	// Map percussion to notes 24 - 34 without reverb
 	midi->sysEx((const byte *) "\x41\x10\x16\x12\x03\x01\x10\x40\x64\x07\x00\x4a\x64\x06\x00\x41\x64\x07\x00\x4b\x64\x08\x00\x45\x64\x06\x00\x44\x64\x0b\x00\x51\x64\x05\x00\x43\x64\x08\x00\x50\x64\x07\x00\x42\x64\x03\x00\x4c\x64\x07\x00\x44", 52);
-	g_system->delayMillis(250);
+	_system->delayMillis(250);
 
 	// Compute version string (truncated to 20 chars max.)
 	strcat(info, gScummVMVersion);
@@ -1267,7 +1443,7 @@
 		checksum -= buffer[i];
 	buffer[27] = checksum & 0x7F;
 	midi->sysEx(buffer, 28);
-	g_system->delayMillis(1000);
+	_system->delayMillis(1000);
 }
 
 void IMuseInternal::initGM(MidiDriver *midi) {
@@ -1279,7 +1455,7 @@
 	memcpy(&buffer[0], "\xF0\x7E\x7F\x09\x01\xF7", 6);
 	midi->sysEx(buffer, 6);
 	debug(2, "GM SysEx: GM System On");
-	g_system->delayMillis(200);
+	_system->delayMillis(200);
 
 	if (_enable_gs) {
 
@@ -1297,7 +1473,7 @@
 		memcpy(&buffer[5], "\x40\x00\x7F\x00\x41\xF7", 6);
 		midi->sysEx(buffer, 11);
 		debug(2, "GS SysEx: GS Reset");
-		g_system->delayMillis(200);
+		_system->delayMillis(200);
 
 		if (_sc55) {
 			// This mode is for GS devices that support an MT-32-compatible
@@ -1419,29 +1595,6 @@
 	_trigger_count = 0;
 }
 
-void IMuseInternal::pause(bool paused) {
-	if (_paused == paused)
-		return;
-	int vol = _music_volume;
-	if (paused)
-		_music_volume = 0;
-	update_volumes();
-	_music_volume = vol;
-
-	// Fix for Bug #817871. The MT-32 apparently fails
-	// sometimes to respond to a channel volume message
-	// (or only uses it for subsequent note events).
-	// The result is hanging notes on pause. Reportedly
-	// happens in the original distro, too. To fix that,
-	// just send AllNotesOff to the channels.
-	if (_midi_native && _native_mt32) {
-		for (int i = 0; i < 16; ++i)
-			_midi_native->send(123 << 8 | 0xB0 | i);
-	}
-
-	_paused = paused;
-}
-
 void IMuseInternal::handleDeferredCommands(MidiDriver *midi) {
 	uint32 advance = midi->getBaseTempo();
 
@@ -1451,7 +1604,7 @@
 		if (!ptr->time_left)
 			continue;
 		if (ptr->time_left <= advance) {
-			doCommand(ptr->a, ptr->b, ptr->c, ptr->d, ptr->e, ptr->f, 0, 0);
+			doCommand_internal (ptr->a, ptr->b, ptr->c, ptr->d, ptr->e, ptr->f, 0, 0);
 			ptr->time_left = advance;
 		}
 		ptr->time_left -= advance;
@@ -1480,102 +1633,6 @@
 	}
 }
 
-////////////////////////////////////////////////////////////
-//
-// IMuseInternal load/save implementation
-//
-////////////////////////////////////////////////////////////
-
-int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) {
-	const SaveLoadEntry mainEntries[] = {
-		MKLINE(IMuseInternal, _queue_end, sleUint8, VER(8)),
-		MKLINE(IMuseInternal, _queue_pos, sleUint8, VER(8)),
-		MKLINE(IMuseInternal, _queue_sound, sleUint16, VER(8)),
-		MKLINE(IMuseInternal, _queue_adding, sleByte, VER(8)),
-		MKLINE(IMuseInternal, _queue_marker, sleByte, VER(8)),
-		MKLINE(IMuseInternal, _queue_cleared, sleByte, VER(8)),
-		MKLINE(IMuseInternal, _master_volume, sleByte, VER(8)),
-		MKLINE(IMuseInternal, _trigger_count, sleUint16, VER(8)),
-		MKLINE(IMuseInternal, _snm_trigger_index, sleUint16, VER(54)),
-		MKARRAY(IMuseInternal, _channel_volume[0], sleUint16, 8, VER(8)),
-		MKARRAY(IMuseInternal, _volchan_table[0], sleUint16, 8, VER(8)),
-		MKEND()
-	};
-
-	const SaveLoadEntry cmdQueueEntries[] = {
-		MKARRAY(CommandQueue, array[0], sleUint16, 8, VER(23)),
-		MKEND()
-	};
-
-	// VolumeFader is obsolete.
-	const SaveLoadEntry volumeFaderEntries[] = {
-		MK_OBSOLETE(VolumeFader, player, sleUint16, VER(8), VER(16)),
-		MK_OBSOLETE(VolumeFader, active, sleUint8, VER(8), VER(16)),
-		MK_OBSOLETE(VolumeFader, curvol, sleUint8, VER(8), VER(16)),
-		MK_OBSOLETE(VolumeFader, speed_lo_max, sleUint16, VER(8), VER(16)),
-		MK_OBSOLETE(VolumeFader, num_steps, sleUint16, VER(8), VER(16)),
-		MK_OBSOLETE(VolumeFader, speed_hi, sleInt8, VER(8), VER(16)),
-		MK_OBSOLETE(VolumeFader, direction, sleInt8, VER(8), VER(16)),
-		MK_OBSOLETE(VolumeFader, speed_lo, sleInt8, VER(8), VER(16)),
-		MK_OBSOLETE(VolumeFader, speed_lo_counter, sleUint16, VER(8), VER(16)),
-		MKEND()
-	};
-
-	const SaveLoadEntry snmTriggerEntries[] = {
-		MKLINE(ImTrigger, sound, sleInt16, VER(54)),
-		MKLINE(ImTrigger, id, sleByte, VER(54)),
-		MKLINE(ImTrigger, expire, sleUint16, VER(54)),
-		MKARRAY(ImTrigger, command[0], sleUint16, 8, VER(54)),
-		MKEND()
-	};
-
-	int i;
-
-	ser->saveLoadEntries(this, mainEntries);
-	ser->saveLoadArrayOf(_cmd_queue, ARRAYSIZE(_cmd_queue), sizeof(_cmd_queue[0]), cmdQueueEntries);
-	ser->saveLoadArrayOf(_snm_triggers, ARRAYSIZE(_snm_triggers), sizeof(_snm_triggers[0]), snmTriggerEntries);
-
-	// The players
-	for (i = 0; i < ARRAYSIZE(_players); ++i)
-		_players[i].saveLoadWithSerializer(ser);
-	
-	// The parts
-	for (i = 0; i < ARRAYSIZE(_parts); ++i)
-		_parts[i].saveLoadWithSerializer(ser);
-
-	{ // Load/save the instrument definitions, which were revamped with V11.
-		Part *part = &_parts[0];
-		if (ser->getVersion() >= VER(11)) {
-			for (i = ARRAYSIZE(_parts); i; --i, ++part) {
-				part->_instrument.saveOrLoad(ser);
-			}
-		} else {
-			for (i = ARRAYSIZE(_parts); i; --i, ++part)
-				part->_instrument.clear();
-		}
-	}
-
-	// VolumeFader has been replaced with the more generic ParameterFader.
-	// FIXME: replace this loop by something like
-	// if (loading && version <= 16)  ser->skip(XXX bytes);
-	for (i = 0; i < 8; ++i)
-		ser->saveLoadEntries(0, volumeFaderEntries);
-
-	if (ser->isLoading()) {
-		// Load all sounds that we need
-		fix_players_after_load(scumm);
-		fix_parts_after_load();
-		setImuseMasterVolume(_master_volume);
-
-		if (_midi_native)
-			reallocateMidiChannels(_midi_native);
-		if (_midi_adlib)
-			reallocateMidiChannels(_midi_adlib);
-	}
-
-	return 0;
-}
-
 void IMuseInternal::fix_parts_after_load() {
 	Part *part;
 	int i;
@@ -1606,10 +1663,9 @@
 //
 ////////////////////////////////////////
 
-void IMuseInternal::midiTimerCallback(void *data) {
-	MidiDriver *driver = (MidiDriver *)data;
-	if (g_scumm->_imuse)
-		g_scumm->_imuse->on_timer(driver);
+void IMuseInternal::midiTimerCallback (void *data) {
+	TimerCallbackInfo *info = (TimerCallbackInfo *) data;
+	info->imuse->on_timer (info->driver);
 }
 
 void IMuseInternal::reallocateMidiChannels(MidiDriver *midi) {
@@ -1666,60 +1722,20 @@
 	_global_adlib_instruments[slot].copy_to(dest);
 }
 
-////////////////////////////////////////////////////////////
-//
-// IMuse implementation
-//
-// IMuse actually serves as a concurency monitor front-end
-// to IMuseInternal and ensures that only one thread
-// accesses the object at a time. This is necessary to
-// prevent scripts and the MIDI parser from yanking objects
-// out from underneath each other.
-//
-////////////////////////////////////////////////////////////
 
-IMuse::IMuse(OSystem *system, IMuseInternal *target)
-	: _system(system), _target(target) {
-	_mutex = system->createMutex();
-}
 
-IMuse::~IMuse() {
-	if (_mutex)
-		_system->deleteMutex(_mutex);
-	if (_target)
-		delete _target;
-}
-
-inline void IMuse::in() const {
-	_system->lockMutex(_mutex);
-}
-inline void IMuse::out() const {
-	_system->unlockMutex(_mutex);
-}
-
-void IMuse::on_timer(MidiDriver *midi) { in(); _target->on_timer(midi); out(); }
-void IMuse::pause(bool paused) { in(); _target->pause(paused); out(); }
-int IMuse::save_or_load(Serializer *ser, ScummEngine *scumm) { in(); int ret = _target->save_or_load(ser, scumm); out(); return ret; }
-void IMuse::setMusicVolume(int vol) { in(); _target->setMusicVolume(vol); out(); }
-void IMuse::startSound(int sound) { in(); _target->startSound(sound); out(); }
-void IMuse::stopSound(int sound) { in(); _target->stopSound(sound); out(); }
-void IMuse::stopAllSounds() { in(); _target->stopAllSounds(); out(); }
-int IMuse::getSoundStatus(int sound) const { in(); int ret = _target->getSoundStatus(sound, true); out(); return ret; }
-bool IMuse::get_sound_active(int sound) const { in(); bool ret = _target->getSoundStatus(sound, false) ? 1 : 0; out(); return ret; }
-int IMuse::getMusicTimer() const { in(); int ret = _target->getMusicTimer(); out(); return ret; }
-int32 IMuse::doCommand(int a, int b, int c, int d, int e, int f, int g, int h) { in(); int32 ret = _target->doCommand(a,b,c,d,e,f,g,h); out(); return ret; }
-int32 IMuse::doCommand(int numargs, int args[]) { in(); int32 ret = _target->doCommand(numargs, args); out(); return ret; }
-int IMuse::clear_queue() { in(); int ret = _target->clear_queue(); out(); return ret; }
-void IMuse::setBase(byte **base) { in(); _target->setBase(base); out(); }
-uint32 IMuse::property(int prop, uint32 value) { in(); uint32 ret = _target->property(prop, value); out(); return ret; }
-void IMuse::terminate() { in(); _target->terminate1(); out(); _target->terminate2(); }
-
-// The IMuse::create method provides a front-end factory
-// for creating IMuseInternal without exposing that class
-// to the client.
+/**
+ * IMuseInternal factory creation method.
+ * This method provides a means for creating an IMuse
+ * implementation without requiring that the details
+ * of that implementation be exposed to the client
+ * through a header file. This allows the internals
+ * of the implementation to be changed and updated
+ * without requiring a recompile of the client code.
+ */
 IMuse *IMuse::create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver) {
 	IMuseInternal *engine = IMuseInternal::create(syst, nativeMidiDriver, adlibMidiDriver);
-	return new IMuse(syst, engine);
+	return engine;
 }
 
 } // End of namespace Scumm

Modified: scummvm/trunk/engines/scumm/imuse/imuse.h
===================================================================
--- scummvm/trunk/engines/scumm/imuse/imuse.h	2006-03-05 20:11:43 UTC (rev 21103)
+++ scummvm/trunk/engines/scumm/imuse/imuse.h	2006-03-05 21:30:44 UTC (rev 21104)
@@ -37,47 +37,41 @@
 class ScummEngine;
 class Serializer;
 
+/**
+ * iMuse implementation interface.
+ * MusicEngine derivative for state-tracked, interactive,
+ * persistent event-based music playback and control.
+ * This class serves as an interface to actual implementations
+ * so that client code is not exposed to the details of
+ * any specific implementation.
+ */
 class IMuse : public MusicEngine {
-private:
-	OSystem *_system;
-	IMuseInternal *_target;
-	mutable Common::MutexRef _mutex;
-
-	IMuse(OSystem *system, IMuseInternal *target);
-	void in() const;
-	void out() const;
-
 public:
-	~IMuse();
-
 	enum {
 		PROP_TEMPO_BASE,
 		PROP_NATIVE_MT32,
 		PROP_GS,
 		PROP_LIMIT_PLAYERS,
 		PROP_RECYCLE_PLAYERS,
-		PROP_DIRECT_PASSTHROUGH
+		PROP_DIRECT_PASSTHROUGH,
+		PROP_GAME_ID
 	};
 
-	void on_timer(MidiDriver *midi);
-	void pause(bool paused);
-	int save_or_load(Serializer *ser, ScummEngine *scumm);
-	bool get_sound_active(int sound) const;
-	int32 doCommand(int a, int b, int c, int d, int e, int f, int g, int h);
-	int32 doCommand(int numargs, int args[]);
-	int clear_queue();
-	void setBase(byte **base);
-	uint32 property(int prop, uint32 value);
+public:
+	virtual void on_timer(MidiDriver *midi) = 0;
+	virtual void pause(bool paused) = 0;
+	virtual int save_or_load(Serializer *ser, ScummEngine *scumm) = 0;
+	virtual bool get_sound_active(int sound) const = 0;
+	virtual int32 doCommand(int numargs, int args[]) = 0;
+	virtual int clear_queue() = 0;
+	virtual void setBase(byte **base) = 0;
+	virtual uint32 property(int prop, uint32 value) = 0;
 
-	// MusicEngine base class methods
-	virtual void setMusicVolume(int vol);
-	virtual void startSound(int sound);
-	virtual void stopSound(int sound);
-	virtual void stopAllSounds();
-	virtual int getSoundStatus(int sound) const;
-	virtual int getMusicTimer() const;
-	virtual void terminate();
+public:
+	// MusicEngine base class methods.
+	// Not actually redefined here because none are implemented.
 
+public:
 	// Factory methods
 	static IMuse *create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver);
 };

Modified: scummvm/trunk/engines/scumm/imuse/imuse_internal.h
===================================================================
--- scummvm/trunk/engines/scumm/imuse/imuse_internal.h	2006-03-05 20:11:43 UTC (rev 21103)
+++ scummvm/trunk/engines/scumm/imuse/imuse_internal.h	2006-03-05 21:30:44 UTC (rev 21104)
@@ -24,6 +24,7 @@
 #define DEFINED_IMUSE_INTERNAL
 
 #include "common/scummsys.h"
+#include "scumm/imuse/imuse.h"
 #include "scumm/imuse/instrument.h"
 #include "scumm/saveload.h"
 #include "sound/mididrv.h"
@@ -33,10 +34,6 @@
 
 namespace Scumm {
 
-// Unremark this statement to activate some of
-// the most common iMuse diagnostic messages.
-// #define IMUSE_DEBUG
-
 struct ParameterFader;
 struct DeferredCommand;
 struct ImTrigger;
@@ -96,6 +93,11 @@
 //
 //////////////////////////////////////////////////
 
+struct TimerCallbackInfo {
+	IMuseInternal *imuse;
+	MidiDriver *driver;
+};
+
 struct HookDatas {
 	byte _jump[2];
 	byte _transpose;
@@ -145,6 +147,14 @@
 	CommandQueue() { memset(this, 0, sizeof(CommandQueue)); }
 };
 
+
+
+//////////////////////////////////////////////////
+//
+// Player class definition
+//
+//////////////////////////////////////////////////
+
 class Player : public MidiDriver {
 protected:
 	// Moved from IMuseInternal.
@@ -273,6 +283,14 @@
 	MidiChannel *getPercussionChannel() { return 0; }
 };
 
+
+
+//////////////////////////////////////////////////
+//
+// Part pseudo-class definition
+//
+//////////////////////////////////////////////////
+
 struct Part : public Serializable {
 	IMuseInternal *_se;
 	int _slot;
@@ -341,10 +359,13 @@
 	void saveLoadWithSerializer(Serializer *ser);
 };
 
-// WARNING: This is the internal variant of the IMUSE class.
-// imuse.h contains a public version of the same class.
-// the public version, only contains a set of methods.
-class IMuseInternal {
+
+
+/**
+ * SCUMM implementation of IMuse.
+ * This class implements the IMuse mixin interface for the SCUMM environment.
+ */
+class IMuseInternal : public IMuse {
 	friend class Player;
 	friend struct Part;
 
@@ -354,9 +375,15 @@
 	bool _sc55;
 	MidiDriver *_midi_adlib;
 	MidiDriver *_midi_native;
+	TimerCallbackInfo _timer_info_adlib;
+	TimerCallbackInfo _timer_info_native;
 
+	uint32 _game_id;
 	byte **_base_sounds;
 
+	OSystem *_system;
+	Common::Mutex _mutex;
+
 protected:
 	bool _paused;
 	bool _initialized;
@@ -391,13 +418,19 @@
 	DeferredCommand _deferredCommands[4];
 
 protected:
+	IMuseInternal();
+	int initialize(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver);
+
+	static void midiTimerCallback (void *data);
+	void on_timer (MidiDriver *midi);
+
 	byte *findStartOfSound(int sound);
 	bool isMT32(int sound);
 	bool isMIDI(int sound);
 	int get_queue_sound_status(int sound) const;
 	void handle_marker(uint id, byte data);
 	int get_channel_volume(uint a);
-	void initMidiDriver(MidiDriver *midi);
+	void initMidiDriver (TimerCallbackInfo *info);
 	void initGM(MidiDriver *midi);
 	void initMT32(MidiDriver *midi);
 	void init_players();
@@ -419,6 +452,7 @@
 
 	int enqueue_command(int a, int b, int c, int d, int e, int f, int g);
 	int enqueue_trigger(int sound, int marker);
+	int clear_queue();
 	int query_queue(int param);
 	Player *findActivePlayer(int id);
 
@@ -432,38 +466,43 @@
 
 	void fix_parts_after_load();
 	void fix_players_after_load(ScummEngine *scumm);
+	int setImuseMasterVolume(uint vol);
 
-	static void midiTimerCallback(void *data);
-
-public:
-	IMuseInternal();
-
-	int initialize(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver);
 	void reallocateMidiChannels(MidiDriver *midi);
 	void setGlobalAdlibInstrument(byte slot, byte *data);
 	void copyGlobalAdlibInstrument(byte slot, Instrument *dest);
 	bool isNativeMT32() { return _native_mt32; }
 
+protected:
+	// Internal mutex-free versions of the IMuse and MusicEngine methods.
+	bool startSound_internal (int sound);
+	int stopSound_internal (int sound);
+	int stopAllSounds_internal();
+	int getSoundStatus_internal (int sound, bool ignoreFadeouts) const;
+	int32 doCommand_internal (int a, int b, int c, int d, int e, int f, int g, int h);
+	int32 doCommand_internal (int numargs, int args[]);
+
+public:
 	// IMuse interface
-
-	void on_timer(MidiDriver *midi);
 	void pause(bool paused);
-	int terminate1();
-	int terminate2();
 	int save_or_load(Serializer *ser, ScummEngine *scumm);
-	int setMusicVolume(uint vol);
-	int setImuseMasterVolume(uint vol);
-	bool startSound(int sound);
-	int stopSound(int sound);
-	int stopAllSounds();
-	int getSoundStatus(int sound, bool ignoreFadeouts) const;
-	int getMusicTimer() const;
-	int32 doCommand (int a, int b, int c, int d, int e, int f, int g, int h);
+	bool get_sound_active(int sound) const;
 	int32 doCommand (int numargs, int args[]);
-	int clear_queue();
 	void setBase(byte **base);
 	uint32 property(int prop, uint32 value);
 
+public:
+	// MusicEngine interface
+	void setMusicVolume(int vol);
+	void startSound(int sound);
+	void stopSound(int sound);
+	void stopAllSounds();
+	int getSoundStatus (int sound) const;
+	int getMusicTimer() const;
+	void terminate();
+
+public:
+	// Factory function
 	static IMuseInternal *create(OSystem *syst, MidiDriver *nativeMidiDriver, MidiDriver *adlibMidiDriver);
 };
 

Modified: scummvm/trunk/engines/scumm/imuse/imuse_player.cpp
===================================================================
--- scummvm/trunk/engines/scumm/imuse/imuse_player.cpp	2006-03-05 20:11:43 UTC (rev 21103)
+++ scummvm/trunk/engines/scumm/imuse/imuse_player.cpp	2006-03-05 21:30:44 UTC (rev 21104)
@@ -395,7 +395,7 @@
 
 	switch (code = *p++) {
 	case 0:
-		if (g_scumm->_game.id != GID_SAMNMAX) {
+		if (_se->_game_id != GID_SAMNMAX) {
 			// There are 17 bytes of useful information beyond
 			// what we've read so far. All we know about them is
 			// as follows:

Modified: scummvm/trunk/engines/scumm/scumm.cpp
===================================================================
--- scummvm/trunk/engines/scumm/scumm.cpp	2006-03-05 20:11:43 UTC (rev 21103)
+++ scummvm/trunk/engines/scumm/scumm.cpp	2006-03-05 21:30:44 UTC (rev 21104)
@@ -1692,6 +1692,7 @@
 
 		_musicEngine = _imuse = IMuse::create(_system, nativeMidiDriver, adlibMidiDriver);
 		if (_imuse) {
+			_imuse->property(IMuse::PROP_GAME_ID, _game.id);
 			if (ConfMan.hasKey("tempo"))
 				_imuse->property(IMuse::PROP_TEMPO_BASE, ConfMan.getInt("tempo"));
 			_imuse->property(IMuse::PROP_NATIVE_MT32, _native_mt32);

Modified: scummvm/trunk/engines/scumm/sound.cpp
===================================================================
--- scummvm/trunk/engines/scumm/sound.cpp	2006-03-05 20:11:43 UTC (rev 21103)
+++ scummvm/trunk/engines/scumm/sound.cpp	2006-03-05 21:30:44 UTC (rev 21104)
@@ -876,11 +876,6 @@
 	if (_vm->_musicEngine) {
 		_vm->_musicEngine->stopAllSounds();
 	}
-	if (_vm->_imuse) {
-		// FIXME: Maybe we could merge this call to clear_queue()
-		// into IMuse::stopAllSounds() ?
-		_vm->_imuse->clear_queue();
-	}
 
 	// Stop all SFX
 	if (!_vm->_imuseDigital) {







More information about the Scummvm-git-logs mailing list