[Scummvm-git-logs] scummvm master -> 434d1f37f9398b6a7a1b08cd5464a1d5ecccdc41

aquadran aquadran at gmail.com
Fri Dec 25 04:27:20 UTC 2020


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

Summary:
434d1f37f9 SCUMM: COMI: implement iMUSE crossfades between regions (#2672)


Commit: 434d1f37f9398b6a7a1b08cd5464a1d5ecccdc41
    https://github.com/scummvm/scummvm/commit/434d1f37f9398b6a7a1b08cd5464a1d5ecccdc41
Author: AndywinXp (andywinxp at gmail.com)
Date: 2020-12-25T05:27:17+01:00

Commit Message:
SCUMM: COMI: implement iMUSE crossfades between regions (#2672)

* SCUMM: COMI: implement region crossfades after JUMP instruction
* SCUMM: COMI: best attempt at fixing part 3 song crossfades
* SCUMM: COMI: generalise loop shift handling to remove hacks

Changed paths:
    engines/scumm/imuse_digi/dimuse.cpp
    engines/scumm/imuse_digi/dimuse.h
    engines/scumm/imuse_digi/dimuse_script.cpp
    engines/scumm/imuse_digi/dimuse_sndmgr.cpp
    engines/scumm/imuse_digi/dimuse_tables.cpp
    engines/scumm/imuse_digi/dimuse_tables.h
    engines/scumm/imuse_digi/dimuse_track.cpp
    engines/scumm/imuse_digi/dimuse_track.h


diff --git a/engines/scumm/imuse_digi/dimuse.cpp b/engines/scumm/imuse_digi/dimuse.cpp
index 1ea5aff8a5..7110b827ce 100644
--- a/engines/scumm/imuse_digi/dimuse.cpp
+++ b/engines/scumm/imuse_digi/dimuse.cpp
@@ -30,6 +30,7 @@
 #include "scumm/imuse_digi/dimuse_bndmgr.h"
 #include "scumm/imuse_digi/dimuse_codecs.h"
 #include "scumm/imuse_digi/dimuse_track.h"
+#include "scumm/imuse_digi/dimuse_tables.h"
 
 #include "audio/audiostream.h"
 #include "audio/mixer.h"
@@ -103,6 +104,10 @@ void IMuseDigital::resetState() {
 	_radioChatterSFX = 0;
 	_triggerUsed = false;
 	_speechIsPlaying = false;
+	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
+		_scheduledCrossfades[l].scheduled = false;
+		_scheduledCrossfades[l].isJumpToLoop = false;
+	}
 }
 
 static void syncWithSerializer(Common::Serializer &s, Track &t) {
@@ -177,6 +182,24 @@ void IMuseDigital::saveLoadEarly(Common::Serializer &s) {
 				continue;
 			}
 
+			if (_vm->_game.id == GID_CMI) {
+				if (track->soundId / 1000 == 1) { // State
+					for (l = 0; _comiStateMusicTable[l].soundId != -1; l++) {
+						if ((_comiStateMusicTable[l].soundId == track->soundId)) {
+							track->loopShiftType = _comiStateMusicTable[l].shiftLoop;
+							break;
+						}
+					}
+				} else if (track->soundId / 1000 == 2) { // Sequence
+					for (l = 0; _comiSeqMusicTable[l].soundId != -1; l++) {
+						if ((_comiSeqMusicTable[l].soundId == track->soundId)) {
+							track->loopShiftType = _comiSeqMusicTable[l].shiftLoop;
+							break; 
+						}
+					}
+				}
+			}
+
 			track->sndDataExtComp = _sound->isSndDataExtComp(track->soundDesc);
 			track->dataOffset = _sound->getRegionOffset(track->soundDesc, track->curRegion);
 			int bits = _sound->getBits(track->soundDesc);
@@ -204,9 +227,65 @@ void IMuseDigital::saveLoadEarly(Common::Serializer &s) {
 	}
 }
 
+void IMuseDigital::runScheduledCrossfades() {
+	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
+		if (_scheduledCrossfades[l].scheduled) {
+			_scheduledCrossfades[l].scheduled = false;
+			Track *oldTrack = _track[l];
+
+			int newTrackId = -1;
+
+			oldTrack->volFadeDelay = _scheduledCrossfades[l].fadeDelay;
+			if (oldTrack->volGroupId == IMUSE_VOLGRP_MUSIC) {
+				newTrackId = startMusicWithOtherPos(oldTrack->soundName, oldTrack->soundId, oldTrack->curHookId, 127, oldTrack);
+			} else {
+				newTrackId = startSound(oldTrack->soundId, "", IMUSE_RESOURCE, IMUSE_VOLGRP_SFX, NULL, 0, _scheduledCrossfades[l].volumeBefJump, oldTrack->soundPriority, oldTrack);
+			}
+
+			if (newTrackId == -1) {
+				debug(5, "IMuseDigital::runScheduledCrossfades(): couldn't allocate crossfade for sound %d", oldTrack->soundId);
+				return;
+			}
+
+			Track *newTrack = _track[newTrackId];
+			newTrack->curRegion = _scheduledCrossfades[l].destRegion;
+
+			// WORKAROUND for some files having a little bit earlier 
+			// loop point set in their iMUSE map; keep in mind we're considering
+			// regionOffset -= (oldTrack->feedSize / _callbackFps) as NO SHIFT.
+			// In COMI we're currently using 4 shift types.
+			if (newTrack->volGroupId == IMUSE_VOLGRP_SFX || !_scheduledCrossfades[l].isJumpToLoop) {
+				newTrack->regionOffset = 0;
+			} else if (_scheduledCrossfades[l].isJumpToLoop) {
+				switch (newTrack->loopShiftType) {
+				case 0:
+					newTrack->regionOffset -= (oldTrack->feedSize / _callbackFps);
+					break;
+				case 1:
+					newTrack->regionOffset = 0;
+					break;
+				case 2:
+					newTrack->regionOffset -= (oldTrack->feedSize / _callbackFps) + (oldTrack->feedSize / _callbackFps) / 2 + 2;
+					break;
+				case 3:
+					newTrack->regionOffset -= (oldTrack->feedSize / _callbackFps) - (oldTrack->feedSize / _callbackFps) / 2 + 2;
+					break;
+				case 4:
+					newTrack->regionOffset -= ((oldTrack->feedSize / _callbackFps) / 3) * 2;
+					break;
+				}
+			}
+
+			newTrack->dataOffset = _scheduledCrossfades[l].destDataOffset;
+			oldTrack->alreadyCrossfading = true; // We set this so to avoid duplicate crossfades
+			handleComiFadeOut(oldTrack, _scheduledCrossfades[l].fadeDelay);
+		}
+	}
+}
+
 void IMuseDigital::callback() {
 	Common::StackLock lock(_mutex, "IMuseDigital::callback()");
-
+	runScheduledCrossfades();
 	_speechIsPlaying = false;
 	// Check for any track playing a speech line
 	if (_vm->_game.id == GID_CMI) {
@@ -419,6 +498,8 @@ void IMuseDigital::callback() {
 
 					if (_sound->isEndOfRegion(track->soundDesc, track->curRegion)) {
 						switchToNextRegion(track);
+						if (_scheduledCrossfades[track->trackId].scheduled)
+							break;
 						if (!track->stream)	// Seems we reached the end of the stream
 							break;
 					}
@@ -439,6 +520,10 @@ void IMuseDigital::callback() {
 					// This allows for a fallback to pan = 64 (center) and volume = 127 (full)
 					if (track->speakingActor != nullptr) {
 						effVol = track->speakingActor->_talkVolume;
+						// Even though we fixed this in IMuseDigital::setVolume(),
+						// some sounds might be started without even calling that function
+						if (effVol > 127)
+							effVol /= 2;
 						effVol = int(round(effVol * 1.04));
 						effPan = (track->speakingActor->_talkPan != 64) ? 2 * track->speakingActor->_talkPan - 127 : 0;
 					}
@@ -504,20 +589,51 @@ void IMuseDigital::switchToNextRegion(Track *track) {
 	}
 
 	int jumpId = _sound->getJumpIdByRegionAndHookId(soundDesc, track->curRegion, track->curHookId);
-	if (jumpId != -1) {
+	if ((_vm->_game.id != GID_CMI && jumpId != -1) || (_vm->_game.id == GID_CMI && jumpId != -1 && !track->alreadyCrossfading)) {
 		int region = _sound->getRegionIdByJumpId(soundDesc, jumpId);
 		assert(region != -1);
 		int sampleHookId = _sound->getJumpHookId(soundDesc, jumpId);
 		assert(sampleHookId != -1);
+
+		bool isJumpToStart = (soundDesc->jump[jumpId].dest == soundDesc->marker[2].pos && !scumm_stricmp(soundDesc->marker[2].ptr, "start"));
+		bool isJumpToLoop = false;
+		if (!isJumpToStart) {
+			for (int m = 0; m < soundDesc->numMarkers; m++) {
+				if (soundDesc->jump[jumpId].dest == soundDesc->marker[m].pos) {
+					Common::String markerDesc = soundDesc->marker[m].ptr;
+					if (markerDesc.contains("loop")) {
+						isJumpToLoop = true;
+					}
+					break;
+				}
+			}
+		}
+
 		debug(5, "SwToNeReg(trackId:%d) - JUMP found - sound:%d, track hookId:%d, data hookId:%d", track->trackId, track->soundId, track->curHookId, sampleHookId);
 		if (track->curHookId == sampleHookId) {
 			int fadeDelay = (60 * _sound->getJumpFade(soundDesc, jumpId)) / 1000;
 			debug(5, "SwToNeReg(trackId:%d) - sound(%d) match hookId", track->trackId, track->soundId);
 			if (fadeDelay) {
-				// COMI specific: jump to new region without true crossfades; this is not true to the original interpreter,
-				// but crossfading between regions in the correct way requires an implementation which is more complex.
-				// Leaving as is, for now.
-				if (_vm->_game.id != GID_CMI) {
+				// If there's a fade time, it means we have to CROSSFADE the jump.
+				// To do this we schedule a crossfade to happen at the next callback call;
+				// the reason for the scheduling is due to the fact that calling the
+				// crossfade immediately causes inconsistencies (and this crashes ImuseDigiSndMgr::getDataFromRegion())
+				if (_vm->_game.id == GID_CMI) {
+					// Block crossfades when the track is already fading down; this prevents edge cases where a crossfade
+					// between two tracks with the same attribPos (like sounds 1202, 1203 and 1204) is happening at the
+					// same time as a loop; the result is that the former is prioritized and the latter
+					// is executed without a crossfade. Also, avoid music crossfades for start markers, these are just plain
+					// dangerous and useless since there's already a fade in for those.
+					if (!track->volFadeUsed && !(track->volFadeStep < 0) && !(isJumpToStart && track->volGroupId == IMUSE_VOLGRP_MUSIC)) {
+						_scheduledCrossfades[track->trackId].scheduled = true;
+						_scheduledCrossfades[track->trackId].destRegion = region;
+						_scheduledCrossfades[track->trackId].destDataOffset = _sound->getRegionOffset(soundDesc, region);
+						_scheduledCrossfades[track->trackId].fadeDelay = fadeDelay;
+						_scheduledCrossfades[track->trackId].destHookId = track->curHookId;
+						_scheduledCrossfades[track->trackId].volumeBefJump = track->vol / 1000;
+						_scheduledCrossfades[track->trackId].isJumpToLoop = isJumpToLoop;
+					}
+				} else {
 					debug(5, "SwToNeReg(trackId:%d) - call cloneToFadeOutTrack(delay:%d)", track->trackId, fadeDelay);
 					Track *fadeTrack = cloneToFadeOutTrack(track, fadeDelay);
 					if (fadeTrack) {
@@ -526,16 +642,18 @@ void IMuseDigital::switchToNextRegion(Track *track) {
 						debug(5, "SwToNeReg(trackId:%d) - sound(%d) faded track, select region %d, curHookId: %d", fadeTrack->trackId, fadeTrack->soundId, fadeTrack->curRegion, fadeTrack->curHookId);
 						fadeTrack->curHookId = 0;
 					}
-				}	
+				}
 			}
-			track->curRegion = region;
+			if (_vm->_game.id != GID_CMI || !_scheduledCrossfades[track->trackId].scheduled)
+				track->curRegion = region;
+
 			debug(5, "SwToNeReg(trackId:%d) - sound(%d) jump to region %d, curHookId: %d", track->trackId, track->soundId, track->curRegion, track->curHookId);
 			track->curHookId = 0;
 		} else {
 			// Check if the jump led to a  "start" marker; if so, we have to enforce it anyway.
 			// Fixes bug/edge-case #11956;
 			// Go see ImuseDigiSndMgr::getJumpIdByRegionAndHookId(...) for further information.
-			if (_vm->_game.id == GID_CMI && soundDesc->jump[jumpId].dest == soundDesc->marker[2].pos && !scumm_stricmp(soundDesc->marker[2].ptr, "start")) {
+			if (_vm->_game.id == GID_CMI && isJumpToStart) {
 				track->curRegion = region;
 				debug(5, "SwToNeReg(trackId:%d) - Enforced sound(%d) jump to region %d marked with a \"start\" marker, hookId(%d)", track->trackId, track->soundId, track->curRegion, track->curHookId);
 			} else {
diff --git a/engines/scumm/imuse_digi/dimuse.h b/engines/scumm/imuse_digi/dimuse.h
index 12871efd06..9f255f882a 100644
--- a/engines/scumm/imuse_digi/dimuse.h
+++ b/engines/scumm/imuse_digi/dimuse.h
@@ -69,6 +69,18 @@ private:
 	TriggerParams _triggerParams;
 	bool _triggerUsed;
 
+	struct ScheduledCrossfade {
+		bool scheduled;
+		int destRegion;
+		int destDataOffset;
+		int fadeDelay;
+		int destHookId;
+		int volumeBefJump;
+		bool isJumpToLoop;
+	};
+
+	ScheduledCrossfade _scheduledCrossfades[MAX_DIGITAL_TRACKS];
+
 	Track *_track[MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS];
 
 	Common::Mutex _mutex;
@@ -94,7 +106,7 @@ private:
 	void callback();
 	void switchToNextRegion(Track *track);
 	int allocSlot(int priority);
-	void startSound(int soundId, const char *soundName, int soundType, int volGroupId, Audio::AudioStream *input, int hookId, int volume, int priority, Track *otherTrack);
+	int startSound(int soundId, const char *soundName, int soundType, int volGroupId, Audio::AudioStream *input, int hookId, int volume, int priority, Track *otherTrack);
 	void selectVolumeGroup(int soundId, int volGroupId);
 
 	int32 getPosInMs(int soundId);
@@ -133,16 +145,17 @@ public:
 
 	void setAudioNames(int32 num, char *names);
 
-	void startVoice(int soundId, Audio::AudioStream *input);
-	void startVoice(int soundId, const char *soundName);
-	void startMusic(int soundId, int volume);
-	void startMusic(const char *soundName, int soundId, int hookId, int volume);
-	void startMusicWithOtherPos(const char *soundName, int soundId, int hookId, int volume, Track *otherTrack);
-	void startSfx(int soundId, int priority);
+	int startVoice(int soundId, Audio::AudioStream *input);
+	int startVoice(int soundId, const char *soundName);
+	int startMusic(int soundId, int volume);
+	int startMusic(const char *soundName, int soundId, int hookId, int volume);
+	int startMusicWithOtherPos(const char *soundName, int soundId, int hookId, int volume, Track *otherTrack);
+	int startSfx(int soundId, int priority);
 	void startSound(int sound) override
 		{ error("IMuseDigital::startSound(int) should be never called"); }
 
 	void saveLoadEarly(Common::Serializer &ser);
+	void runScheduledCrossfades();
 	void resetState();
 	void setRadioChatterSFX(bool state) {
 		_radioChatterSFX = state;
diff --git a/engines/scumm/imuse_digi/dimuse_script.cpp b/engines/scumm/imuse_digi/dimuse_script.cpp
index c906a927c2..4ab89c7cc0 100644
--- a/engines/scumm/imuse_digi/dimuse_script.cpp
+++ b/engines/scumm/imuse_digi/dimuse_script.cpp
@@ -212,34 +212,34 @@ void IMuseDigital::refreshScripts() {
 	}
 }
 
-void IMuseDigital::startVoice(int soundId, Audio::AudioStream *input) {
+int IMuseDigital::startVoice(int soundId, Audio::AudioStream *input) {
 	debug(5, "startVoiceStream(%d)", soundId);
-	startSound(soundId, "", 0, IMUSE_VOLGRP_VOICE, input, 0, 127, 127, NULL);
+	return startSound(soundId, "", 0, IMUSE_VOLGRP_VOICE, input, 0, 127, 127, NULL);
 }
 
-void IMuseDigital::startVoice(int soundId, const char *soundName) {
+int IMuseDigital::startVoice(int soundId, const char *soundName) {
 	debug(5, "startVoiceBundle(%s, %d)", soundName, soundId);
-	startSound(soundId, soundName, IMUSE_BUNDLE, IMUSE_VOLGRP_VOICE, NULL, 0, 127, 127, NULL);
+	return startSound(soundId, soundName, IMUSE_BUNDLE, IMUSE_VOLGRP_VOICE, NULL, 0, 127, 127, NULL);
 }
 
-void IMuseDigital::startMusic(int soundId, int volume) {
+int IMuseDigital::startMusic(int soundId, int volume) {
 	debug(5, "startMusicResource(%d)", soundId);
-	startSound(soundId, "", IMUSE_RESOURCE, IMUSE_VOLGRP_MUSIC, NULL, 0, volume, 126, NULL);
+	return startSound(soundId, "", IMUSE_RESOURCE, IMUSE_VOLGRP_MUSIC, NULL, 0, volume, 126, NULL);
 }
 
-void IMuseDigital::startMusic(const char *soundName, int soundId, int hookId, int volume) {
+int IMuseDigital::startMusic(const char *soundName, int soundId, int hookId, int volume) {
 	debug(5, "startMusicBundle(%s, soundId:%d, hookId:%d)", soundName, soundId, hookId);
-	startSound(soundId, soundName, IMUSE_BUNDLE, IMUSE_VOLGRP_MUSIC, NULL, hookId, volume, 126, NULL);
+	return startSound(soundId, soundName, IMUSE_BUNDLE, IMUSE_VOLGRP_MUSIC, NULL, hookId, volume, 126, NULL);
 }
 
-void IMuseDigital::startMusicWithOtherPos(const char *soundName, int soundId, int hookId, int volume, Track *otherTrack) {
+int IMuseDigital::startMusicWithOtherPos(const char *soundName, int soundId, int hookId, int volume, Track *otherTrack) {
 	debug(5, "startMusicWithOtherPos(%s, soundId:%d, hookId:%d, oldSoundId:%d)", soundName, soundId, hookId, otherTrack->soundId);
-	startSound(soundId, soundName, IMUSE_BUNDLE, IMUSE_VOLGRP_MUSIC, NULL, hookId, volume, 126, otherTrack);
+	return startSound(soundId, soundName, IMUSE_BUNDLE, IMUSE_VOLGRP_MUSIC, NULL, hookId, volume, 126, otherTrack);
 }
 
-void IMuseDigital::startSfx(int soundId, int priority) {
+int IMuseDigital::startSfx(int soundId, int priority) {
 	debug(5, "startSfx(%d)", soundId);
-	startSound(soundId, "", IMUSE_RESOURCE, IMUSE_VOLGRP_SFX, NULL, 0, 127, priority, NULL);
+	return startSound(soundId, "", IMUSE_RESOURCE, IMUSE_VOLGRP_SFX, NULL, 0, 127, priority, NULL);
 }
 
 void IMuseDigital::getLipSync(int soundId, int syncId, int32 msPos, int32 &width, int32 &height) {
diff --git a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
index ffd020a3fe..80be6000b2 100644
--- a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
+++ b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
@@ -653,7 +653,13 @@ int ImuseDigiSndMgr::getJumpFade(SoundDesc *soundDesc, int number) {
 int32 ImuseDigiSndMgr::getDataFromRegion(SoundDesc *soundDesc, int region, byte **buf, int32 offset, int32 size) {
 	debug(6, "getDataFromRegion() region:%d, offset:%d, size:%d, numRegions:%d", region, offset, size, soundDesc->numRegions);
 	assert(checkForProperHandle(soundDesc));
-	assert(buf && offset >= 0 && size >= 0);
+
+	// In COMI we allow at least -size*2 as offset, since music
+	// tracks need that in order to be realigned after crossfades
+	if (_vm->_game.id == GID_CMI)
+		assert(buf && offset >= -(size * 2) && size >= 0);
+	else
+		assert(buf && offset >= 0 && size >= 0);
 	assert(region >= 0 && region < soundDesc->numRegions);
 
 	int32 region_offset = soundDesc->region[region].offset;
diff --git a/engines/scumm/imuse_digi/dimuse_tables.cpp b/engines/scumm/imuse_digi/dimuse_tables.cpp
index 9bbafb8278..b88f2e1a1e 100644
--- a/engines/scumm/imuse_digi/dimuse_tables.cpp
+++ b/engines/scumm/imuse_digi/dimuse_tables.cpp
@@ -284,214 +284,214 @@ const imuseDigTable _digSeqMusicTable[] = {
 };
 
 const imuseComiTable _comiStateMusicTable[] = {
-	{0, 1000, "STATE_NULL",         0, 0,    0, ""},             /* 00 */
-	{0, 1001, "stateNoChange",      0, 0,    0, ""},             /* 01 */
-	{3, 1098, "stateCredits1",      0, 0,   60, "1098-C~1.IMX"}, /* 02 */
-	{3, 1099, "stateMenu",          0, 0,   60, "1099-M~1.IMX"}, /* 03 */
-	{3, 1100, "stateHold1",         4, 0,   60, "1100-H~1.IMX"}, /* 04 */
-	{3, 1101, "stateWaterline1",    4, 0,   60, "1101-W~1.IMX"}, /* 05 */
-	{3, 1102, "stateHold2",         6, 1,   60, "1102-H~1.IMX"}, /* 06 */
-	{3, 1103, "stateWaterline2",    6, 0,   60, "1103-W~1.IMX"}, /* 07 */
-	{3, 1104, "stateCannon",        0, 0,   60, "1104-C~1.IMX"}, /* 08 */
-	{3, 1105, "stateTreasure",      0, 0,   60, "1105-T~1.IMX"}, /* 09 */
-	{3, 1200, "stateFortBase",     10, 1,   60, "1200-F~1.IMX"}, /* 10 */
-	{3, 1201, "statePreFort",      10, 1,   60, "1201-P~1.IMX"}, /* 11 */
-	{3, 1202, "statePreVooOut",    12, 0,   60, "1202-P~1.IMX"}, /* 12 */
-	{3, 1203, "statePreVooIn",     12, 0,   60, "1203-P~1.IMX"}, /* 13 */
-	{3, 1204, "statePreVooLady",   12, 0,   60, "1204-P~1.IMX"}, /* 14 */
-	{3, 1205, "stateVoodooOut",     0, 0,   60, "1205-V~1.IMX"}, /* 15 */
-	{3, 1210, "stateVoodooIn",      0, 0,   60, "1210-V~1.IMX"}, /* 16 */
-	{12,1212, "stateVoodooInAlt",   0, 1,   42, "1210-V~1.IMX"}, /* 17 */
-	{3, 1215, "stateVoodooLady",    0, 0,   60, "1215-V~1.IMX"}, /* 18 */
-	{3, 1219, "statePrePlundermap", 0, 0,   60, "1219-P~1.IMX"}, /* 19 */
-	{3, 1220, "statePlundermap",    0, 0,   60, "1220-P~1.IMX"}, /* 20 */
-	{3, 1222, "statePreCabana",     0, 0,   60, "1222-P~1.IMX"}, /* 21 */
-	{3, 1223, "stateCabana",        0, 0,   60, "1223-C~1.IMX"}, /* 22 */
-	{3, 1224, "statePostCabana",   23, 0,   60, "1224-P~1.IMX"}, /* 23 */
-	{3, 1225, "stateBeachClub",    23, 0,   60, "1225-B~1.IMX"}, /* 24 */
-	{3, 1230, "stateCliff",         0, 0,   60, "1230-C~1.IMX"}, /* 25 */
-	{3, 1232, "stateBelly",         0, 0,   48, "1232-B~1.IMX"}, /* 26 */
-	{3, 1235, "stateQuicksand",     0, 0,   60, "1235-Q~1.IMX"}, /* 27 */
-	{3, 1240, "stateDangerBeach",   0, 0,   48, "1240-D~1.IMX"}, /* 28 */
-	{12,1241, "stateDangerBeachAlt",0, 2,   48, "1240-D~1.IMX"}, /* 29 */
-	{3, 1245, "stateRowBoat",       0, 0,   60, "1245-R~1.IMX"}, /* 30 */
-	{3, 1247, "stateAlongside",     0, 0,   48, "1247-A~1.IMX"}, /* 31 */
-	{12,1248, "stateAlongsideAlt",  0, 1,   48, "1247-A~1.IMX"}, /* 32 */
-	{3, 1250, "stateChimpBoat",     0, 0,   30, "1250-C~1.IMX"}, /* 33 */
-	{3, 1255, "stateMrFossey",      0, 0,   48, "1255-M~1.IMX"}, /* 34 */
-	{3, 1259, "statePreTown",       0, 0,   60, "1259-P~1.IMX"}, /* 35 */
-	{3, 1260, "stateTown",          0, 0,   60, "1260-T~1.IMX"}, /* 36 */
-	{3, 1264, "statePreMeadow",     0, 0,   60, "1264-P~1.IMX"}, /* 37 */
-	{3, 1265, "stateMeadow",        0, 0,   60, "1265-M~1.IMX"}, /* 38 */
-	{3, 1266, "stateMeadowAmb",     0, 0,   60, "1266-M~1.IMX"}, /* 39 */
-	{3, 1270, "stateWardrobePre",  40, 0,   60, "1270-W~1.IMX"}, /* 40 */
-	{3, 1272, "statePreShow",      40, 0,   60, "1272-P~1.IMX"}, /* 41 */
-	{3, 1274, "stateWardrobeShow", 42, 0,   60, "1274-W~1.IMX"}, /* 42 */
-	{3, 1276, "stateShow",         42, 0,   60, "1276-S~1.IMX"}, /* 43 */
-	{3, 1277, "stateWardrobeJug",  44, 0,   60, "1277-W~1.IMX"}, /* 44 */
-	{3, 1278, "stateJuggling",     44, 0,   60, "1278-J~1.IMX"}, /* 45 */
-	{3, 1279, "statePostShow",      0, 0,   60, "1279-P~1.IMX"}, /* 46 */
-	{3, 1280, "stateChickenShop",   0, 0,   60, "1280-C~1.IMX"}, /* 47 */
-	{3, 1285, "stateBarberShop",   48, 0,   60, "1285-B~1.IMX"}, /* 48 */
-	{3, 1286, "stateVanHelgen",    48, 0,   60, "1286-V~1.IMX"}, /* 49 */
-	{3, 1287, "stateBill",         48, 0,   60, "1287-B~1.IMX"}, /* 50 */
-	{3, 1288, "stateHaggis",       48, 0,   60, "1288-H~1.IMX"}, /* 51 */
-	{3, 1289, "stateRottingham",   48, 0,   60, "1289-R~1.IMX"}, /* 52 */
-	{3, 1305, "stateDeck",          0, 0,   60, "1305-D~1.IMX"}, /* 53 */
-	{3, 1310, "stateCombatMap",     0, 0,   60, "1310-C~1.IMX"}, /* 54 */
-	{3, 1320, "stateShipCombat",    0, 0,   60, "1320-S~1.IMX"}, /* 55 */
-	{3, 1325, "stateSwordfight",    0, 0,   60, "1325-S~1.IMX"}, /* 56 */
-	{3, 1327, "stateSwordRott",     0, 0,   60, "1327-S~1.IMX"}, /* 57 */
-	{3, 1330, "stateTownEdge",      0, 0,   60, "1330-T~1.IMX"}, /* 58 */
-	{3, 1335, "stateSwordLose",     0, 0,   60, "1335-S~1.IMX"}, /* 59 */
-	{3, 1340, "stateSwordWin",      0, 0,   60, "1340-S~1.IMX"}, /* 60 */
-	{3, 1345, "stateGetMap",        0, 0,   60, "1345-G~1.IMX"}, /* 61 */
-	{3, 1400, "stateWreckBeach",    0, 0,   60, "1400-W~1.IMX"}, /* 62 */
-	{3, 1405, "stateBloodMap",     63, 0,   60, "1405-B~1.IMX"}, /* 63 */
-	{3, 1410, "stateClearing",      0, 0,   60, "1410-C~1.IMX"}, /* 64 */
-	{3, 1415, "stateLighthouse",   63, 0,   60, "1415-L~1.IMX"}, /* 65 */
-	{3, 1420, "stateVillage",      66, 0,   60, "1420-V~1.IMX"}, /* 66 */
-	{3, 1423, "stateVolcano",      66, 0,   60, "1423-V~1.IMX"}, /* 67 */
-	{3, 1425, "stateAltar",        66, 0,   60, "1425-A~1.IMX"}, /* 68 */
-	{3, 1430, "stateHotelOut",      0, 0,   60, "1430-H~1.IMX"}, /* 69 */
-	{3, 1435, "stateHotelBar",     70, 0,   60, "1435-H~1.IMX"}, /* 70 */
-	{3, 1440, "stateHotelIn",      70, 0,   60, "1440-H~1.IMX"}, /* 71 */
-	{3, 1445, "stateTarotLady",    70, 0,   60, "1445-T~1.IMX"}, /* 72 */
-	{3, 1447, "stateGoodsoup",     70, 0,   60, "1447-G~1.IMX"}, /* 73 */
-	{3, 1448, "stateGuestRoom",     0, 0,   60, "1448-G~1.IMX"}, /* 74 */
-	{3, 1450, "stateWindmill",     63, 0,   60, "1450-W~1.IMX"}, /* 75 */
-	{3, 1455, "stateCemetary",      0, 0,   60, "1455-C~1.IMX"}, /* 76 */
-	{3, 1460, "stateCrypt",        77, 0,   60, "1460-C~1.IMX"}, /* 77 */
-	{3, 1463, "stateGraveDigger",  77, 0,   60, "1463-G~1.IMX"}, /* 78 */
-	{3, 1465, "stateMonkey1",       0, 0,   60, "1465-M~1.IMX"}, /* 79 */
-	{3, 1475, "stateStanDark",      0, 0,   60, "1475-S~1.IMX"}, /* 80 */
-	{3, 1477, "stateStanLight",     0, 0,   60, "1477-S~1.IMX"}, /* 81 */
-	{3, 1480, "stateEggBeach",     63, 0,   60, "1480-E~1.IMX"}, /* 82 */
-	{3, 1485, "stateSkullIsland",   0, 0,   60, "1485-S~1.IMX"}, /* 83 */
-	{3, 1490, "stateSmugglersCave", 0, 0,   60, "1490-S~1.IMX"}, /* 84 */
-	{3, 1500, "stateLeChuckTalk",   0, 0,   60, "1500-L~1.IMX"}, /* 85 */
-	{3, 1505, "stateCarnival",      0, 0,   60, "1505-C~1.IMX"}, /* 86 */
-	{3, 1511, "stateHang",         87, 0,   60, "1511-H~1.IMX"}, /* 87 */
-	{3, 1512, "stateRum",          87, 0,   60, "1512-RUM.IMX"}, /* 88 */
-	{3, 1513, "stateTorture",      87, 0,   60, "1513-T~1.IMX"}, /* 89 */
-	{3, 1514, "stateSnow",         87, 0,   60, "1514-S~1.IMX"}, /* 90 */
-	{3, 1515, "stateCredits",       0, 0,   60, "1515-C~1.IMX"}, /* 91 */
-	{3, 1520, "stateCarnAmb",       0, 0,   60, "1520-C~1.IMX"}, /* 92 */
-	{0,   -1, "",                   0, 0,    0, ""}
+	{0, 1000, "STATE_NULL",         0, 0,    0, ""            , 0}, /* 00 */
+	{0, 1001, "stateNoChange",      0, 0,    0, ""            , 0}, /* 01 */
+	{3, 1098, "stateCredits1",      0, 0,   60, "1098-C~1.IMX", 0}, /* 02 */
+	{3, 1099, "stateMenu",          0, 0,   60, "1099-M~1.IMX", 0}, /* 03 */
+	{3, 1100, "stateHold1",         4, 0,   60, "1100-H~1.IMX", 0}, /* 04 */
+	{3, 1101, "stateWaterline1",    4, 0,   60, "1101-W~1.IMX", 0}, /* 05 */
+	{3, 1102, "stateHold2",         6, 1,   60, "1102-H~1.IMX", 1}, /* 06 */
+	{3, 1103, "stateWaterline2",    6, 0,   60, "1103-W~1.IMX", 1}, /* 07 */
+	{3, 1104, "stateCannon",        0, 0,   60, "1104-C~1.IMX", 0}, /* 08 */
+	{3, 1105, "stateTreasure",      0, 0,   60, "1105-T~1.IMX", 0}, /* 09 */
+	{3, 1200, "stateFortBase",     10, 1,   60, "1200-F~1.IMX", 0}, /* 10 */
+	{3, 1201, "statePreFort",      10, 1,   60, "1201-P~1.IMX", 0}, /* 11 */
+	{3, 1202, "statePreVooOut",    12, 0,   60, "1202-P~1.IMX", 0}, /* 12 */
+	{3, 1203, "statePreVooIn",     12, 0,   60, "1203-P~1.IMX", 0}, /* 13 */
+	{3, 1204, "statePreVooLady",   12, 0,   60, "1204-P~1.IMX", 0}, /* 14 */
+	{3, 1205, "stateVoodooOut",     0, 0,   60, "1205-V~1.IMX", 0}, /* 15 */
+	{3, 1210, "stateVoodooIn",      0, 0,   60, "1210-V~1.IMX", 0}, /* 16 */
+	{12,1212, "stateVoodooInAlt",   0, 1,   42, "1210-V~1.IMX", 0}, /* 17 */
+	{3, 1215, "stateVoodooLady",    0, 0,   60, "1215-V~1.IMX", 0}, /* 18 */
+	{3, 1219, "statePrePlundermap", 0, 0,   60, "1219-P~1.IMX", 0}, /* 19 */
+	{3, 1220, "statePlundermap",    0, 0,   60, "1220-P~1.IMX", 0}, /* 20 */
+	{3, 1222, "statePreCabana",     0, 0,   60, "1222-P~1.IMX", 0}, /* 21 */
+	{3, 1223, "stateCabana",        0, 0,   60, "1223-C~1.IMX", 0}, /* 22 */
+	{3, 1224, "statePostCabana",   23, 0,   60, "1224-P~1.IMX", 0}, /* 23 */
+	{3, 1225, "stateBeachClub",    23, 0,   60, "1225-B~1.IMX", 0}, /* 24 */
+	{3, 1230, "stateCliff",         0, 0,   60, "1230-C~1.IMX", 0}, /* 25 */
+	{3, 1232, "stateBelly",         0, 0,   48, "1232-B~1.IMX", 0}, /* 26 */
+	{3, 1235, "stateQuicksand",     0, 0,   60, "1235-Q~1.IMX", 0}, /* 27 */
+	{3, 1240, "stateDangerBeach",   0, 0,   48, "1240-D~1.IMX", 0}, /* 28 */
+	{12,1241, "stateDangerBeachAlt",0, 2,   48, "1240-D~1.IMX", 0}, /* 29 */
+	{3, 1245, "stateRowBoat",       0, 0,   60, "1245-R~1.IMX", 0}, /* 30 */
+	{3, 1247, "stateAlongside",     0, 0,   48, "1247-A~1.IMX", 0}, /* 31 */
+	{12,1248, "stateAlongsideAlt",  0, 1,   48, "1247-A~1.IMX", 0}, /* 32 */
+	{3, 1250, "stateChimpBoat",     0, 0,   30, "1250-C~1.IMX", 1}, /* 33 */
+	{3, 1255, "stateMrFossey",      0, 0,   48, "1255-M~1.IMX", 0}, /* 34 */
+	{3, 1259, "statePreTown",       0, 0,   60, "1259-P~1.IMX", 0}, /* 35 */
+	{3, 1260, "stateTown",          0, 0,   60, "1260-T~1.IMX", 1}, /* 36 */
+	{3, 1264, "statePreMeadow",     0, 0,   60, "1264-P~1.IMX", 0}, /* 37 */
+	{3, 1265, "stateMeadow",        0, 0,   60, "1265-M~1.IMX", 0}, /* 38 */
+	{3, 1266, "stateMeadowAmb",     0, 0,   60, "1266-M~1.IMX", 0}, /* 39 */
+	{3, 1270, "stateWardrobePre",  40, 0,   60, "1270-W~1.IMX", 0}, /* 40 */
+	{3, 1272, "statePreShow",      40, 0,   60, "1272-P~1.IMX", 0}, /* 41 */
+	{3, 1274, "stateWardrobeShow", 42, 0,   60, "1274-W~1.IMX", 0}, /* 42 */
+	{3, 1276, "stateShow",         42, 0,   60, "1276-S~1.IMX", 0}, /* 43 */
+	{3, 1277, "stateWardrobeJug",  44, 0,   60, "1277-W~1.IMX", 0}, /* 44 */
+	{3, 1278, "stateJuggling",     44, 0,   60, "1278-J~1.IMX", 0}, /* 45 */
+	{3, 1279, "statePostShow",      0, 0,   60, "1279-P~1.IMX", 0}, /* 46 */
+	{3, 1280, "stateChickenShop",   0, 0,   60, "1280-C~1.IMX", 1}, /* 47 */
+	{3, 1285, "stateBarberShop",   48, 0,   60, "1285-B~1.IMX", 0}, /* 48 */
+	{3, 1286, "stateVanHelgen",    48, 0,   60, "1286-V~1.IMX", 0}, /* 49 */
+	{3, 1287, "stateBill",         48, 0,   60, "1287-B~1.IMX", 0}, /* 50 */
+	{3, 1288, "stateHaggis",       48, 0,   60, "1288-H~1.IMX", 0}, /* 51 */
+	{3, 1289, "stateRottingham",   48, 0,   60, "1289-R~1.IMX", 0}, /* 52 */
+	{3, 1305, "stateDeck",          0, 0,   60, "1305-D~1.IMX", 0}, /* 53 */
+	{3, 1310, "stateCombatMap",     0, 0,   60, "1310-C~1.IMX", 0}, /* 54 */
+	{3, 1320, "stateShipCombat",    0, 0,   60, "1320-S~1.IMX", 1}, /* 55 */
+	{3, 1325, "stateSwordfight",    0, 0,   60, "1325-S~1.IMX", 1}, /* 56 */
+	{3, 1327, "stateSwordRott",     0, 0,   60, "1327-S~1.IMX", 0}, /* 57 */
+	{3, 1330, "stateTownEdge",      0, 0,   60, "1330-T~1.IMX", 1}, /* 58 */
+	{3, 1335, "stateSwordLose",     0, 0,   60, "1335-S~1.IMX", 0}, /* 59 */
+	{3, 1340, "stateSwordWin",      0, 0,   60, "1340-S~1.IMX", 0}, /* 60 */
+	{3, 1345, "stateGetMap",        0, 0,   60, "1345-G~1.IMX", 0}, /* 61 */
+	{3, 1400, "stateWreckBeach",    0, 0,   60, "1400-W~1.IMX", 0}, /* 62 */
+	{3, 1405, "stateBloodMap",     63, 0,   60, "1405-B~1.IMX", 0}, /* 63 */
+	{3, 1410, "stateClearing",      0, 0,   60, "1410-C~1.IMX", 0}, /* 64 */
+	{3, 1415, "stateLighthouse",   63, 0,   60, "1415-L~1.IMX", 0}, /* 65 */
+	{3, 1420, "stateVillage",      66, 0,   60, "1420-V~1.IMX", 0}, /* 66 */
+	{3, 1423, "stateVolcano",      66, 0,   60, "1423-V~1.IMX", 0}, /* 67 */
+	{3, 1425, "stateAltar",        66, 0,   60, "1425-A~1.IMX", 0}, /* 68 */
+	{3, 1430, "stateHotelOut",      0, 0,   60, "1430-H~1.IMX", 0}, /* 69 */
+	{3, 1435, "stateHotelBar",     70, 0,   60, "1435-H~1.IMX", 0}, /* 70 */
+	{3, 1440, "stateHotelIn",      70, 0,   60, "1440-H~1.IMX", 0}, /* 71 */
+	{3, 1445, "stateTarotLady",    70, 0,   60, "1445-T~1.IMX", 0}, /* 72 */
+	{3, 1447, "stateGoodsoup",     70, 0,   60, "1447-G~1.IMX", 0}, /* 73 */
+	{3, 1448, "stateGuestRoom",     0, 0,   60, "1448-G~1.IMX", 0}, /* 74 */
+	{3, 1450, "stateWindmill",     63, 0,   60, "1450-W~1.IMX", 0}, /* 75 */
+	{3, 1455, "stateCemetary",      0, 0,   60, "1455-C~1.IMX", 0}, /* 76 */
+	{3, 1460, "stateCrypt",        77, 0,   60, "1460-C~1.IMX", 0}, /* 77 */
+	{3, 1463, "stateGraveDigger",  77, 0,   60, "1463-G~1.IMX", 0}, /* 78 */
+	{3, 1465, "stateMonkey1",       0, 0,   60, "1465-M~1.IMX", 0}, /* 79 */
+	{3, 1475, "stateStanDark",      0, 0,   60, "1475-S~1.IMX", 0}, /* 80 */
+	{3, 1477, "stateStanLight",     0, 0,   60, "1477-S~1.IMX", 1}, /* 81 */
+	{3, 1480, "stateEggBeach",     63, 0,   60, "1480-E~1.IMX", 0}, /* 82 */
+	{3, 1485, "stateSkullIsland",   0, 0,   60, "1485-S~1.IMX", 0}, /* 83 */
+	{3, 1490, "stateSmugglersCave", 0, 0,   60, "1490-S~1.IMX", 0}, /* 84 */
+	{3, 1500, "stateLeChuckTalk",   0, 0,   60, "1500-L~1.IMX", 0}, /* 85 */
+	{3, 1505, "stateCarnival",      0, 0,   60, "1505-C~1.IMX", 0}, /* 86 */
+	{3, 1511, "stateHang",         87, 0,   60, "1511-H~1.IMX", 0}, /* 87 */
+	{3, 1512, "stateRum",          87, 0,   60, "1512-RUM.IMX", 0}, /* 88 */
+	{3, 1513, "stateTorture",      87, 0,   60, "1513-T~1.IMX", 0}, /* 89 */
+	{3, 1514, "stateSnow",         87, 0,   60, "1514-S~1.IMX", 0}, /* 90 */
+	{3, 1515, "stateCredits",       0, 0,   60, "1515-C~1.IMX", 0}, /* 91 */
+	{3, 1520, "stateCarnAmb",       0, 0,   60, "1520-C~1.IMX", 0}, /* 92 */
+	{0,   -1, "",                   0, 0,    0, "",             0}
 };
 
 const imuseComiTable _comiSeqMusicTable[] = {
-	{0, 2000, "SEQ_NULL",        0, 0,    0, ""},
-	{0, 2100, "seqINTRO",        0, 0,    0, ""},
-	{3, 2105, "seqInterlude1",   0, 0,   60, "2105-I~1.IMX"},
-	{8, 2110, "seqLastBoat",     0, 1,    0, ""},
-	{0, 2115, "seqSINK_SHIP",    0, 0,    0, ""},
-	{0, 2120, "seqCURSED_RING",  0, 0,   60, ""},
-	{3, 2200, "seqInterlude2",   0, 0,   60, "2200-I~1.IMX"},
-	{3, 2210, "seqKidnapped",    0, 0,   60, "2210-K~1.IMX"},
-	{8, 2220, "seqSnakeVomits",  0, 1,    0, ""},
-	{8, 2222, "seqPopBalloon",   0, 1,    0, ""},
-	{3, 2225, "seqDropBalls",    0, 0,   60, "2225-D~1.IMX"},
-	{4, 2232, "seqArriveBarber", 0, 0,   60, "2232-A~1.IMX"},
-	{3, 2233, "seqAtonal",       0, 0,   60, "2233-A~1.IMX"},
-	{3, 2235, "seqShaveHead1",   0, 0,   60, "2235-S~1.IMX"},
-	{2, 2236, "seqShaveHead2",   0, 2,   60, "2235-S~1.IMX"},
-	{3, 2245, "seqCaberLose",    0, 0,   60, "2245-C~1.IMX"},
-	{3, 2250, "seqCaberWin",     0, 0,   60, "2250-C~1.IMX"},
-	{3, 2255, "seqDuel1",        0, 0,   60, "2255-D~1.IMX"},
-	{2, 2256, "seqDuel2",        0, 2,   60, "2255-D~1.IMX"},
-	{2, 2257, "seqDuel3",        0, 3,   60, "2255-D~1.IMX"},
-	{3, 2260, "seqBlowUpTree1",  0, 0,   60, "2260-B~1.IMX"},
-	{2, 2261, "seqBlowUpTree2",  0, 2,   60, "2260-B~1.IMX"},
-	{3, 2275, "seqMonkeys",      0, 0,   60, "2275-M~1.IMX"},
-	{9, 2277, "seqAttack",       0, 1,    0, ""},
-	{3, 2285, "seqSharks",       0, 0,   60, "2285-S~1.IMX"},
-	{3, 2287, "seqTowelWalk",    0, 0,   60, "2287-T~1.IMX"},
-	{0, 2293, "seqNICE_BOOTS",   0, 0,    0, ""},
-	{0, 2295, "seqBIG_BONED",    0, 0,    0, ""},
-	{3, 2300, "seqToBlood",      0, 0,   60, "2300-T~1.IMX"},
-	{3, 2301, "seqInterlude3",   0, 0,   60, "2301-I~1.IMX"},
-	{3, 2302, "seqRott1",        0, 0,   60, "2302-R~1.IMX"},
-	{2, 2304, "seqRott2",        0, 2,   60, "2302-R~1.IMX"},
-	{2, 2305, "seqRott2b",       0,21,   60, "2302-R~1.IMX"},
-	{2, 2306, "seqRott3",        0, 3,   60, "2302-R~1.IMX"},
-	{2, 2308, "seqRott4",        0, 4,   60, "2302-R~1.IMX"},
-	{2, 2309, "seqRott5",        0, 5,   60, "2302-R~1.IMX"},
-	{3, 2311, "seqVerse1",       0, 0,   60, "2311-S~1.IMX"},
-	{2, 2312, "seqVerse2",       0, 2,   60, "2311-S~1.IMX"},
-	{2, 2313, "seqVerse3",       0, 3,   60, "2311-S~1.IMX"},
-	{2, 2314, "seqVerse4",       0, 4,   60, "2311-S~1.IMX"},
-	{2, 2315, "seqVerse5",       0, 5,   60, "2311-S~1.IMX"},
-	{2, 2316, "seqVerse6",       0, 6,   60, "2311-S~1.IMX"},
-	{2, 2317, "seqVerse7",       0, 7,   60, "2311-S~1.IMX"},
-	{2, 2318, "seqVerse8",       0, 8,   60, "2311-S~1.IMX"},
-	{2, 2319, "seqSongEnd",      0, 9,   60, "2311-S~1.IMX"},
-	{2, 2336, "seqRiposteLose",  0, 0,   60, "2336-R~1.IMX"},
-	{2, 2337, "seqRiposteWin",   0, 0,   60, "2337-R~1.IMX"},
-	{2, 2338, "seqInsultLose",   0, 0,   60, "2338-I~1.IMX"},
-	{2, 2339, "seqInsultWin",    0, 0,   60, "2339-I~1.IMX"},
-	{3, 2340, "seqSwordLose",    0, 0,   60, "1335-S~1.IMX"},
-	{3, 2345, "seqSwordWin",     0, 0,   60, "1340-S~1.IMX"},
-	{3, 2347, "seqGetMap",       0, 0,   60, "1345-G~1.IMX"},
-	{3, 2400, "seqInterlude4",   0, 0,   60, "2400-I~1.IMX"},
-	{0, 2405, "seqSHIPWRECK",    0, 0,    0, ""},
-	{3, 2408, "seqFakeCredits",  0, 0,   60, "2408-F~1.IMX"},
-	{3, 2410, "seqPassOut",      0, 0,   60, "2410-P~1.IMX"},
-	{3, 2414, "seqGhostTalk",    0, 0,   60, "2414-G~1.IMX"},
-	{2, 2415, "seqGhostWedding", 0, 1,   60, "2414-G~1.IMX"},
-	{3, 2420, "seqEruption",     0, 0,   60, "2420-E~1.IMX"},
-	{3, 2425, "seqSacrifice",    0, 0,   60, "2425-S~1.IMX"},
-	{2, 2426, "seqSacrificeEnd", 0, 1,   60, "2425-S~1.IMX"},
-	{3, 2430, "seqScareDigger",  0, 0,   60, "2430-S~1.IMX"},
-	{3, 2445, "seqSkullArrive",  0, 0,   60, "2445-S~1.IMX"},
-	{3, 2450, "seqFloat",        0, 0,   60, "2450-C~1.IMX"},
-	{2, 2451, "seqFall",         0, 1,   60, "2450-C~1.IMX"},
-	{2, 2452, "seqUmbrella",     0, 2,   60, "2450-C~1.IMX"},
-	{3, 2460, "seqFight",        0, 0,   60, "2460-F~1.IMX"},
-	{0, 2465, "seqLAVE_RIDE",    0, 0,    0, ""},
-	{0, 2470, "seqMORE_SLAW",    0, 0,    0, ""},
-	{0, 2475, "seqLIFT_CURSE",   0, 0,    0, ""},
-	{3, 2500, "seqInterlude5",   0, 0,   60, "2500-I~1.IMX"},
-	{3, 2502, "seqExitSkycar",   0, 0,   60, "2502-E~1.IMX"},
-	{3, 2504, "seqGrow1",        0, 0,   60, "2504-G~1.IMX"},
-	{2, 2505, "seqGrow2",        0, 1,   60, "2504-G~1.IMX"},
-	{3, 2508, "seqInterlude6",   0, 0,   60, "2508-I~1.IMX"},
-	{0, 2515, "seqFINALE",       0, 0,    0, ""},
-	{3, 2520, "seqOut",          0, 0,   60, "2520-OUT.IMX"},
-	{3, 2530, "seqZap1a",        0, 0,   60, "2530-Z~1.IMX"},
-	{2, 2531, "seqZap1b",        0, 1,   60, "2530-Z~1.IMX"},
-	{2, 2532, "seqZap1c",        0, 2,   60, "2530-Z~1.IMX"},
-	{2, 2540, "seqZap2a",        0, 0,   60, "2540-Z~1.IMX"},
-	{2, 2541, "seqZap2b",        0, 1,   60, "2540-Z~1.IMX"},
-	{2, 2542, "seqZap2c",        0, 2,   60, "2540-Z~1.IMX"},
-	{3, 2550, "seqZap3a",        0, 0,   60, "2550-Z~1.IMX"},
-	{2, 2551, "seqZap3b",        0, 1,   60, "2550-Z~1.IMX"},
-	{2, 2552, "seqZap3c",        0, 2,   60, "2550-Z~1.IMX"},
-	{3, 2560, "seqZap4a",        0, 0,   60, "2560-Z~1.IMX"},
-	{2, 2561, "seqZap4b",        0, 1,   60, "2560-Z~1.IMX"},
-	{2, 2562, "seqZap4c",        0, 2,   60, "2560-Z~1.IMX"},
-	{0,   -1, "",                0, 0,    0, ""}
+	{0, 2000, "SEQ_NULL",        0, 0,    0, ""            , 0},
+	{0, 2100, "seqINTRO",        0, 0,    0, ""            , 0},
+	{3, 2105, "seqInterlude1",   0, 0,   60, "2105-I~1.IMX", 0},
+	{8, 2110, "seqLastBoat",     0, 1,    0, ""            , 0},
+	{0, 2115, "seqSINK_SHIP",    0, 0,    0, ""            , 0},
+	{0, 2120, "seqCURSED_RING",  0, 0,   60, ""            , 0},
+	{3, 2200, "seqInterlude2",   0, 0,   60, "2200-I~1.IMX", 0},
+	{3, 2210, "seqKidnapped",    0, 0,   60, "2210-K~1.IMX", 0},
+	{8, 2220, "seqSnakeVomits",  0, 1,    0, ""            , 0},
+	{8, 2222, "seqPopBalloon",   0, 1,    0, ""            , 0},
+	{3, 2225, "seqDropBalls",    0, 0,   60, "2225-D~1.IMX", 0},
+	{4, 2232, "seqArriveBarber", 0, 0,   60, "2232-A~1.IMX", 0},
+	{3, 2233, "seqAtonal",       0, 0,   60, "2233-A~1.IMX", 0},
+	{3, 2235, "seqShaveHead1",   0, 0,   60, "2235-S~1.IMX", 0},
+	{2, 2236, "seqShaveHead2",   0, 2,   60, "2235-S~1.IMX", 0},
+	{3, 2245, "seqCaberLose",    0, 0,   60, "2245-C~1.IMX", 0},
+	{3, 2250, "seqCaberWin",     0, 0,   60, "2250-C~1.IMX", 0},
+	{3, 2255, "seqDuel1",        0, 0,   60, "2255-D~1.IMX", 0},
+	{2, 2256, "seqDuel2",        0, 2,   60, "2255-D~1.IMX", 0},
+	{2, 2257, "seqDuel3",        0, 3,   60, "2255-D~1.IMX", 0},
+	{3, 2260, "seqBlowUpTree1",  0, 0,   60, "2260-B~1.IMX", 0},
+	{2, 2261, "seqBlowUpTree2",  0, 2,   60, "2260-B~1.IMX", 0},
+	{3, 2275, "seqMonkeys",      0, 0,   60, "2275-M~1.IMX", 0},
+	{9, 2277, "seqAttack",       0, 1,    0, ""            , 0},
+	{3, 2285, "seqSharks",       0, 0,   60, "2285-S~1.IMX", 0},
+	{3, 2287, "seqTowelWalk",    0, 0,   60, "2287-T~1.IMX", 0},
+	{0, 2293, "seqNICE_BOOTS",   0, 0,    0, ""            , 0},
+	{0, 2295, "seqBIG_BONED",    0, 0,    0, ""            , 0},
+	{3, 2300, "seqToBlood",      0, 0,   60, "2300-T~1.IMX", 0},
+	{3, 2301, "seqInterlude3",   0, 0,   60, "2301-I~1.IMX", 0},
+	{3, 2302, "seqRott1",        0, 0,   60, "2302-R~1.IMX", 0},
+	{2, 2304, "seqRott2",        0, 2,   60, "2302-R~1.IMX", 0},
+	{2, 2305, "seqRott2b",       0,21,   60, "2302-R~1.IMX", 0},
+	{2, 2306, "seqRott3",        0, 3,   60, "2302-R~1.IMX", 0},
+	{2, 2308, "seqRott4",        0, 4,   60, "2302-R~1.IMX", 0},
+	{2, 2309, "seqRott5",        0, 5,   60, "2302-R~1.IMX", 0},
+	{3, 2311, "seqVerse1",       0, 0,   60, "2311-S~1.IMX", 3},
+	{2, 2312, "seqVerse2",       0, 2,   60, "2311-S~1.IMX", 2},
+	{2, 2313, "seqVerse3",       0, 3,   60, "2311-S~1.IMX", 2},
+	{2, 2314, "seqVerse4",       0, 4,   60, "2311-S~1.IMX", 4},
+	{2, 2315, "seqVerse5",       0, 5,   60, "2311-S~1.IMX", 2},
+	{2, 2316, "seqVerse6",       0, 6,   60, "2311-S~1.IMX", 2},
+	{2, 2317, "seqVerse7",       0, 7,   60, "2311-S~1.IMX", 3},
+	{2, 2318, "seqVerse8",       0, 8,   60, "2311-S~1.IMX", 2},
+	{2, 2319, "seqSongEnd",      0, 9,   60, "2311-S~1.IMX", 2},
+	{2, 2336, "seqRiposteLose",  0, 0,   60, "2336-R~1.IMX", 0},
+	{2, 2337, "seqRiposteWin",   0, 0,   60, "2337-R~1.IMX", 0},
+	{2, 2338, "seqInsultLose",   0, 0,   60, "2338-I~1.IMX", 1},
+	{2, 2339, "seqInsultWin",    0, 0,   60, "2339-I~1.IMX", 1},
+	{3, 2340, "seqSwordLose",    0, 0,   60, "1335-S~1.IMX", 0},
+	{3, 2345, "seqSwordWin",     0, 0,   60, "1340-S~1.IMX", 0},
+	{3, 2347, "seqGetMap",       0, 0,   60, "1345-G~1.IMX", 0},
+	{3, 2400, "seqInterlude4",   0, 0,   60, "2400-I~1.IMX", 0},
+	{0, 2405, "seqSHIPWRECK",    0, 0,    0, ""            , 0},
+	{3, 2408, "seqFakeCredits",  0, 0,   60, "2408-F~1.IMX", 0},
+	{3, 2410, "seqPassOut",      0, 0,   60, "2410-P~1.IMX", 0},
+	{3, 2414, "seqGhostTalk",    0, 0,   60, "2414-G~1.IMX", 0},
+	{2, 2415, "seqGhostWedding", 0, 1,   60, "2414-G~1.IMX", 0},
+	{3, 2420, "seqEruption",     0, 0,   60, "2420-E~1.IMX", 0},
+	{3, 2425, "seqSacrifice",    0, 0,   60, "2425-S~1.IMX", 0},
+	{2, 2426, "seqSacrificeEnd", 0, 1,   60, "2425-S~1.IMX", 0},
+	{3, 2430, "seqScareDigger",  0, 0,   60, "2430-S~1.IMX", 0},
+	{3, 2445, "seqSkullArrive",  0, 0,   60, "2445-S~1.IMX", 0},
+	{3, 2450, "seqFloat",        0, 0,   60, "2450-C~1.IMX", 1},
+	{2, 2451, "seqFall",         0, 1,   60, "2450-C~1.IMX", 0},
+	{2, 2452, "seqUmbrella",     0, 2,   60, "2450-C~1.IMX", 0},
+	{3, 2460, "seqFight",        0, 0,   60, "2460-F~1.IMX", 0},
+	{0, 2465, "seqLAVE_RIDE",    0, 0,    0, ""            , 0},
+	{0, 2470, "seqMORE_SLAW",    0, 0,    0, ""            , 0},
+	{0, 2475, "seqLIFT_CURSE",   0, 0,    0, ""            , 0},
+	{3, 2500, "seqInterlude5",   0, 0,   60, "2500-I~1.IMX", 0},
+	{3, 2502, "seqExitSkycar",   0, 0,   60, "2502-E~1.IMX", 0},
+	{3, 2504, "seqGrow1",        0, 0,   60, "2504-G~1.IMX", 0},
+	{2, 2505, "seqGrow2",        0, 1,   60, "2504-G~1.IMX", 0},
+	{3, 2508, "seqInterlude6",   0, 0,   60, "2508-I~1.IMX", 0},
+	{0, 2515, "seqFINALE",       0, 0,    0, ""            , 0},
+	{3, 2520, "seqOut",          0, 0,   60, "2520-OUT.IMX", 0},
+	{3, 2530, "seqZap1a",        0, 0,   60, "2530-Z~1.IMX", 0},
+	{2, 2531, "seqZap1b",        0, 1,   60, "2530-Z~1.IMX", 0},
+	{2, 2532, "seqZap1c",        0, 2,   60, "2530-Z~1.IMX", 0},
+	{2, 2540, "seqZap2a",        0, 0,   60, "2540-Z~1.IMX", 0},
+	{2, 2541, "seqZap2b",        0, 1,   60, "2540-Z~1.IMX", 0},
+	{2, 2542, "seqZap2c",        0, 2,   60, "2540-Z~1.IMX", 0},
+	{3, 2550, "seqZap3a",        0, 0,   60, "2550-Z~1.IMX", 0},
+	{2, 2551, "seqZap3b",        0, 1,   60, "2550-Z~1.IMX", 0},
+	{2, 2552, "seqZap3c",        0, 2,   60, "2550-Z~1.IMX", 0},
+	{3, 2560, "seqZap4a",        0, 0,   60, "2560-Z~1.IMX", 0},
+	{2, 2561, "seqZap4b",        0, 1,   60, "2560-Z~1.IMX", 0},
+	{2, 2562, "seqZap4c",        0, 2,   60, "2560-Z~1.IMX", 0},
+	{0,   -1, "",                0, 0,    0, ""            , 0}
 };
 
 const imuseComiTable _comiDemoStateMusicTable[] = {
-	{ 0, 1000, "STATE_NULL",      0, 0,    0, "" },         /* 00 */
-	{ 0, 1001, "stateNoChange",   0, 0,    0, "" },         /* 01 */
-	{ 3, 1100, "stateHold1",      2, 0,   60, "in1.imx" },  /* 02 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 03 */
-	{ 3, 1120, "stateHold2",      4, 0,   60, "in2.imx" },  /* 04 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 05 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 06 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 07 */
-	{ 3, 1140, "stateWaterline1", 2, 0,   60, "out1.imx" }, /* 08 */
-	{ 3, 1150, "stateWaterline2", 4, 0,   60, "out2.imx" }, /* 09 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 10 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 11 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 12 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 13 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 14 */
-	{ 0, 1001, "empty",           0, 0,    0, "" },         /* 15 */
-	{ 3, 1210, "stateCannon",     4, 0,   60, "gun.imx" },  /* 16 */
-	{ 0,   -1, "",                0, 0,    0, "" }
+	{0, 1000, "STATE_NULL",      0, 0,    0, "",         0}, /* 00 */
+	{0, 1001, "stateNoChange",   0, 0,    0, "",         0}, /* 01 */
+	{3, 1100, "stateHold1",      2, 0,   60, "in1.imx",  0}, /* 02 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 03 */
+	{3, 1120, "stateHold2",      4, 0,   60, "in2.imx",  0}, /* 04 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 05 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 06 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 07 */
+	{3, 1140, "stateWaterline1", 2, 0,   60, "out1.imx", 0}, /* 08 */
+	{3, 1150, "stateWaterline2", 4, 0,   60, "out2.imx", 0}, /* 09 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 10 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 11 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 12 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 13 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 14 */
+	{0, 1001, "empty",           0, 0,    0, "",         0}, /* 15 */
+	{3, 1210, "stateCannon",     4, 0,   60, "gun.imx",  0}, /* 16 */
+	{0,   -1, "",                0, 0,    0, "",         0}
 };
 
 const imuseFtStateTable _ftStateMusicTable[] = {
diff --git a/engines/scumm/imuse_digi/dimuse_tables.h b/engines/scumm/imuse_digi/dimuse_tables.h
index 1c981d8124..404030adf1 100644
--- a/engines/scumm/imuse_digi/dimuse_tables.h
+++ b/engines/scumm/imuse_digi/dimuse_tables.h
@@ -53,6 +53,7 @@ struct imuseComiTable {
 	byte hookId;
 	int16 fadeOutDelay;
 	char filename[13];
+	int shiftLoop;
 };
 
 
diff --git a/engines/scumm/imuse_digi/dimuse_track.cpp b/engines/scumm/imuse_digi/dimuse_track.cpp
index ebd2fa2a9e..f81adc2caa 100644
--- a/engines/scumm/imuse_digi/dimuse_track.cpp
+++ b/engines/scumm/imuse_digi/dimuse_track.cpp
@@ -29,6 +29,7 @@
 #include "scumm/imuse_digi/dimuse.h"
 #include "scumm/imuse_digi/dimuse_bndmgr.h"
 #include "scumm/imuse_digi/dimuse_track.h"
+#include "scumm/imuse_digi/dimuse_tables.h"
 
 #include "audio/audiostream.h"
 #include "audio/mixer.h"
@@ -79,14 +80,14 @@ int IMuseDigital::allocSlot(int priority) {
 	return trackId;
 }
 
-void IMuseDigital::startSound(int soundId, const char *soundName, int soundType, int volGroupId, Audio::AudioStream *input, int hookId, int volume, int priority, Track *otherTrack) {
+int IMuseDigital::startSound(int soundId, const char *soundName, int soundType, int volGroupId, Audio::AudioStream *input, int hookId, int volume, int priority, Track *otherTrack) {
 	Common::StackLock lock(_mutex, "IMuseDigital::startSound()");
 	debug(5, "IMuseDigital::startSound(%d) - begin func", soundId);
 
 	int l = allocSlot(priority);
 	if (l == -1) {
 		warning("IMuseDigital::startSound() Can't start sound - no free slots");
-		return;
+		return -1;
 	}
 	debug(5, "IMuseDigital::startSound(%d, trackId:%d)", soundId, l);
 
@@ -105,6 +106,24 @@ void IMuseDigital::startSound(int soundId, const char *soundName, int soundType,
 	track->soundType = soundType;
 	track->trackId = l;
 
+	if (_vm->_game.id == GID_CMI) {
+		if (track->soundId / 1000 == 1) { // State
+			for (l = 0; _comiStateMusicTable[l].soundId != -1; l++) {
+				if ((_comiStateMusicTable[l].soundId == track->soundId)) {
+					track->loopShiftType = _comiStateMusicTable[l].shiftLoop;
+					break;
+				}
+			}
+		} else if (track->soundId / 1000 == 2) { // Sequence
+			for (l = 0; _comiSeqMusicTable[l].soundId != -1; l++) {
+				if ((_comiSeqMusicTable[l].soundId == track->soundId)) {
+					track->loopShiftType = _comiSeqMusicTable[l].shiftLoop;
+					break;
+				}
+			}
+		}
+	}
+
 	int bits = 0, freq = 0, channels = 0;
 
 	track->souStreamUsed = (input != 0);
@@ -120,7 +139,7 @@ void IMuseDigital::startSound(int soundId, const char *soundName, int soundType,
 			track->soundDesc = _sound->openSound(soundId, soundName, soundType, volGroupId, 2);
 
 		if (!track->soundDesc)
-			return;
+			return -1;
 
 		track->sndDataExtComp = _sound->isSndDataExtComp(track->soundDesc);
 
@@ -193,6 +212,8 @@ void IMuseDigital::startSound(int soundId, const char *soundName, int soundType,
 	}
 
 	track->used = true;
+
+	return track->trackId;
 }
 
 void IMuseDigital::setPriority(int soundId, int priority) {
@@ -213,6 +234,9 @@ void IMuseDigital::setVolume(int soundId, int volume) {
 	Common::StackLock lock(_mutex, "IMuseDigital::setVolume()");
 	debug(5, "IMuseDigital::setVolume(%d, %d)", soundId, volume);
 
+	if (_vm->_game.id == GID_CMI && volume > 127)
+		volume = volume / 2;
+
 	for (int l = 0; l < MAX_DIGITAL_TRACKS; l++) {
 		Track *track = _track[l];
 		if (track->used && !track->toBeRemoved && (track->soundId == soundId)) {
diff --git a/engines/scumm/imuse_digi/dimuse_track.h b/engines/scumm/imuse_digi/dimuse_track.h
index 9cad1c1fc6..5b8f10af17 100644
--- a/engines/scumm/imuse_digi/dimuse_track.h
+++ b/engines/scumm/imuse_digi/dimuse_track.h
@@ -45,35 +45,36 @@ enum {
 };
 
 struct Track {
-	int trackId;		   // used to identify track by value (0-15)
-						   
-	int8 pan;			   // panning value of sound
-	int32 vol;			   // volume level (values 0-127 * 1000)
-	int32 volFadeDest;	   // volume level which fading target (values 0-127 * 1000)
-	int32 volFadeStep;	   // delta of step while changing volume at each imuse callback
-	int32 volFadeDelay;	   // time in ms how long fading volume must be
-	bool volFadeUsed;	   // flag if fading is in progress
-	int32 gainReduction;   // amount of volume to subtract
-	int32 gainRedFadeDest; // target of fade for gain reduction
-	bool gainRedFadeUsed;  // flag if fading is in progress
-	int32 soundId;		   // sound id used by scumm script
-	char soundName[15];    // sound name but also filename of sound in bundle data
-	bool used;			   // flag mean that track is used
-	bool toBeRemoved;      // flag mean that track need to be free
-	bool souStreamUsed;	   // flag mean that track use stream from sou file
-	bool sndDataExtComp;   // flag mean that sound data is compressed by scummvm tools
-	int32 soundPriority;   // priority level of played sound (0-127)
-	int32 regionOffset;    // offset to sound data relative to begining of current region
-	int32 dataOffset;	   // offset to sound data relative to begining of 'DATA' chunk
-	int32 curRegion;	   // id of current used region
-	int32 curHookId;	   // id of current used hook id
-	int32 volGroupId;	   // id of volume group (IMUSE_VOLGRP_VOICE, IMUSE_VOLGRP_SFX, IMUSE_VOLGRP_MUSIC)
-	int32 soundType;	   // type of sound data (IMUSE_BUNDLE, IMUSE_RESOURCE)
-	int32 feedSize;		   // size of sound data needed to be filled at each callback iteration
-	int32 dataMod12Bit;	   // value used between all callback to align 12 bit source of data
-	int32 mixerFlags;	   // flags for sound mixer's channel (kFlagStereo, kFlag16Bits, kFlagUnsigned)
-	bool littleEndian;	   // Endianness: default is big for original files and native for recompressed ones
-
+	int trackId;		     // used to identify track by value (0-15)
+						     
+	int8 pan;			     // panning value of sound
+	int32 vol;			     // volume level (values 0-127 * 1000)
+	int32 volFadeDest;	     // volume level which fading target (values 0-127 * 1000)
+	int32 volFadeStep;	     // delta of step while changing volume at each imuse callback
+	int32 volFadeDelay;	     // time in ms how long fading volume must be
+	bool volFadeUsed;	     // flag if fading is in progress
+	int32 gainReduction;     // amount of volume to subtract
+	int32 gainRedFadeDest;   // target of fade for gain reduction
+	bool gainRedFadeUsed;    // flag if fading is in progress
+	bool alreadyCrossfading; // used by COMI to check if this track is already running a crossfade
+	int loopShiftType;       // currently used by COMI to check if the loop point for this track has to be shifted
+	int32 soundId;		     // sound id used by scumm script
+	char soundName[15];      // sound name but also filename of sound in bundle data
+	bool used;			     // flag mean that track is used
+	bool toBeRemoved;        // flag mean that track need to be free
+	bool souStreamUsed;	     // flag mean that track use stream from sou file
+	bool sndDataExtComp;     // flag mean that sound data is compressed by scummvm tools
+	int32 soundPriority;     // priority level of played sound (0-127)
+	int32 regionOffset;      // offset to sound data relative to begining of current region
+	int32 dataOffset;	     // offset to sound data relative to begining of 'DATA' chunk
+	int32 curRegion;	     // id of current used region
+	int32 curHookId;	     // id of current used hook id
+	int32 volGroupId;	     // id of volume group (IMUSE_VOLGRP_VOICE, IMUSE_VOLGRP_SFX, IMUSE_VOLGRP_MUSIC)
+	int32 soundType;	     // type of sound data (IMUSE_BUNDLE, IMUSE_RESOURCE)
+	int32 feedSize;		     // size of sound data needed to be filled at each callback iteration
+	int32 dataMod12Bit;	     // value used between all callback to align 12 bit source of data
+	int32 mixerFlags;	     // flags for sound mixer's channel (kFlagStereo, kFlag16Bits, kFlagUnsigned)
+	bool littleEndian;       // Endianness: default is big for original files and native for recompressed ones
 	ImuseDigiSndMgr::SoundDesc *soundDesc;	// sound handle used by iMuse sound manager
 	Audio::SoundHandle mixChanHandle;		// sound mixer's channel handle
 	Audio::QueuingAudioStream *stream;		// sound mixer's audio stream handle for *.la1 and *.bun
@@ -93,6 +94,8 @@ struct Track {
 		gainReduction = 0;
 		gainRedFadeDest = 127 * 290; // About 4 dB of gain reduction
 		gainRedFadeUsed = false;
+		alreadyCrossfading = false;
+		loopShiftType = 0; 
 		soundId = 0;
 		memset(soundName, 0, sizeof(soundName));
 		used = false;




More information about the Scummvm-git-logs mailing list