diff options
Diffstat (limited to 'engines/scumm')
| -rw-r--r-- | engines/scumm/debugger.cpp | 40 | ||||
| -rw-r--r-- | engines/scumm/gfx.cpp | 2 | ||||
| -rw-r--r-- | engines/scumm/he/sound_he.cpp | 26 | ||||
| -rw-r--r-- | engines/scumm/imuse/imuse.cpp | 13 | ||||
| -rw-r--r-- | engines/scumm/imuse/imuse.h | 2 | ||||
| -rw-r--r-- | engines/scumm/imuse/imuse_internal.h | 2 | ||||
| -rw-r--r-- | engines/scumm/imuse/imuse_part.cpp | 15 | ||||
| -rw-r--r-- | engines/scumm/module.mk | 3 | ||||
| -rw-r--r-- | engines/scumm/music.h | 6 | ||||
| -rw-r--r-- | engines/scumm/object.cpp | 12 | ||||
| -rw-r--r-- | engines/scumm/player_mac.cpp | 415 | ||||
| -rw-r--r-- | engines/scumm/player_mac.h | 133 | ||||
| -rw-r--r-- | engines/scumm/player_v3m.cpp | 214 | ||||
| -rw-r--r-- | engines/scumm/player_v3m.h | 54 | ||||
| -rw-r--r-- | engines/scumm/player_v5m.cpp | 246 | ||||
| -rw-r--r-- | engines/scumm/player_v5m.h | 57 | ||||
| -rw-r--r-- | engines/scumm/saveload.cpp | 10 | ||||
| -rw-r--r-- | engines/scumm/saveload.h | 16 | ||||
| -rw-r--r-- | engines/scumm/script.cpp | 6 | ||||
| -rw-r--r-- | engines/scumm/scumm.cpp | 10 | ||||
| -rw-r--r-- | engines/scumm/sound.cpp | 246 | 
21 files changed, 1226 insertions, 302 deletions
diff --git a/engines/scumm/debugger.cpp b/engines/scumm/debugger.cpp index dc5acbdb7d..9b6dd1e687 100644 --- a/engines/scumm/debugger.cpp +++ b/engines/scumm/debugger.cpp @@ -741,10 +741,6 @@ bool ScummDebugger::Cmd_PrintDraft(int argc, const char **argv) {  		"Silence",      "Shaping",       "Unmaking",  		"Transcendence"  	}; -	int odds[] = { -		15162, 15676, 16190,    64, 16961, 17475, 17989, 18503, -		   73, 19274,    76,    77, 20302, 20816, 21330,    84 -	};  	const char *notes = "cdefgabC";  	int i, base, draft; @@ -754,9 +750,9 @@ bool ScummDebugger::Cmd_PrintDraft(int argc, const char **argv) {  		return true;  	} -	// There are 16 drafts, stored from variable 50 or 100 and upwards. -	// Each draft occupies two variables. Even-numbered variables contain -	// the notes for each draft, and a number of flags: +	// There are 16 drafts, stored from variable 50, 55 or 100 and upwards. +	// Each draft occupies two variables, the first of which contains the +	// notes for the draft and a number of flags.  	//  	// +---+---+---+---+-----+-----+-----+-----+  	// | A | B | C | D | 444 | 333 | 222 | 111 | @@ -771,13 +767,16 @@ bool ScummDebugger::Cmd_PrintDraft(int argc, const char **argv) {  	// 222 The second note  	// 111 The first note  	// -	// I don't yet know what the odd-numbered variables are used for. -	// Possibly they store information on where and/or how the draft can -	// be used. They appear to remain constant throughout the game. +	// I don't yet know what the second variable is used for. Possibly to +	// store information on where and/or how the draft can be used. They +	// appear to remain constant throughout the game.  	if (_vm->_game.version == 4 || _vm->_game.platform == Common::kPlatformPCEngine) {  		// DOS CD version / PC-Engine version  		base = 100; +	} else if (_vm->_game.platform == Common::kPlatformMacintosh) { +		// Macintosh version +		base = 55;  	} else {  		// All (?) other versions  		base = 50; @@ -801,28 +800,13 @@ bool ScummDebugger::Cmd_PrintDraft(int argc, const char **argv) {  			DebugPrintf("Learned all drafts and notes.\n");  			return true;  		} - -		// During the testing of EGA Loom we had some trouble with the -		// drafts data structure being overwritten. I don't expect -		// this command is particularly useful any more, but it will -		// attempt to repair the (probably) static part of it. - -		if (strcmp(argv[1], "fix") == 0) { -			for (i = 0; i < 16; i++) -				_vm->_scummVars[base + 2 * i + 1] = odds[i]; -			DebugPrintf( -				"An attempt has been made to repair\n" -				"the internal drafts data structure.\n" -				"Continue on your own risk.\n"); -			return true; -		}  	}  	// Probably the most useful command for ordinary use: list the drafts.  	for (i = 0; i < 16; i++) {  		draft = _vm->_scummVars[base + i * 2]; -		DebugPrintf("%d %-13s %c%c%c%c %c%c %5d %c\n", +		DebugPrintf("%d %-13s %c%c%c%c %c%c\n",  			base + 2 * i,  			names[i],  			notes[draft & 0x0007], @@ -830,9 +814,7 @@ bool ScummDebugger::Cmd_PrintDraft(int argc, const char **argv) {  			notes[(draft & 0x01c0) >> 6],  			notes[(draft & 0x0e00) >> 9],  			(draft & 0x2000) ? 'K' : ' ', -			(draft & 0x4000) ? 'U' : ' ', -			_vm->_scummVars[base + 2 * i + 1], -			(_vm->_scummVars[base + 2 * i + 1] != odds[i]) ? '!' : ' '); +			(draft & 0x4000) ? 'U' : ' ');  	}  	return true; diff --git a/engines/scumm/gfx.cpp b/engines/scumm/gfx.cpp index ffff329036..50ff0b3988 100644 --- a/engines/scumm/gfx.cpp +++ b/engines/scumm/gfx.cpp @@ -3609,7 +3609,7 @@ void Gdi::unkDecode9(byte *dst, int dstPitch, const byte *src, int height) const  	int i;  	uint buffer = 0, mask = 128;  	int h = height; -	i = run = 0; +	run = 0;  	int x = 8;  	for (;;) { diff --git a/engines/scumm/he/sound_he.cpp b/engines/scumm/he/sound_he.cpp index f94b74ac45..1afb1b4074 100644 --- a/engines/scumm/he/sound_he.cpp +++ b/engines/scumm/he/sound_he.cpp @@ -804,7 +804,7 @@ void ScummEngine_v80he::createSound(int snd1id, int snd2id) {  	byte *snd1Ptr, *snd2Ptr;  	byte *sbng1Ptr, *sbng2Ptr;  	byte *sdat1Ptr, *sdat2Ptr; -	byte *src, *dst, *tmp; +	byte *src, *dst;  	int len, offs, size;  	int sdat1size, sdat2size; @@ -844,6 +844,7 @@ void ScummEngine_v80he::createSound(int snd1id, int snd2id) {  	if (sbng1Ptr != NULL && sbng2Ptr != NULL) {  		if (chan != -1 && ((SoundHE *)_sound)->_heChannel[chan].codeOffs > 0) { +			// Copy any code left over to the beginning of the code block  			int curOffs = ((SoundHE *)_sound)->_heChannel[chan].codeOffs;  			src = snd1Ptr + curOffs; @@ -851,29 +852,33 @@ void ScummEngine_v80he::createSound(int snd1id, int snd2id) {  			size = READ_BE_UINT32(sbng1Ptr + 4);  			len = sbng1Ptr - snd1Ptr + size - curOffs; -			byte *data = (byte *)malloc(len); -			memcpy(data, src, len); -			memcpy(dst, data, len); -			free(data); +			memmove(dst, src, len); +			// Now seek to the end of this code block  			dst = sbng1Ptr + 8;  			while ((size = READ_LE_UINT16(dst)) != 0)  				dst += size;  		} else { +			// We're going to overwrite the code block completely  			dst = sbng1Ptr + 8;  		} -		((SoundHE *)_sound)->_heChannel[chan].codeOffs = sbng1Ptr - snd1Ptr + 8; +		// Reset the current code offset to the beginning of the code block +		if (chan >= 0) +			((SoundHE *)_sound)->_heChannel[chan].codeOffs = sbng1Ptr - snd1Ptr + 8; -		tmp = sbng2Ptr + 8; +		// Seek to the end of the code block for sound 2 +		byte *tmp = sbng2Ptr + 8;  		while ((offs = READ_LE_UINT16(tmp)) != 0) {  			tmp += offs;  		} +		// Copy the code block for sound 2 to the code block for sound 1  		src = sbng2Ptr + 8;  		len = tmp - sbng2Ptr - 6;  		memcpy(dst, src, len); +		// Rewrite the time for this new code block to be after the sound 1 code block  		int32 time;  		while ((size = READ_LE_UINT16(dst)) != 0) {  			time = READ_LE_UINT32(dst + 2); @@ -883,6 +888,7 @@ void ScummEngine_v80he::createSound(int snd1id, int snd2id) {  		}  	} +	// Find the data pointers and sizes  	if (findSoundTag(MKTAG('d','a','t','a'), snd1Ptr)) {  		sdat1Ptr = findSoundTag(MKTAG('d','a','t','a'), snd1Ptr);  		assert(sdat1Ptr); @@ -906,6 +912,8 @@ void ScummEngine_v80he::createSound(int snd1id, int snd2id) {  	sdat1size = _sndDataSize - _sndPtrOffs;  	if (sdat2size < sdat1size) { +		// We have space leftover at the end of sound 1 +		// -> Just append sound 2  		src = sdat2Ptr + 8;  		dst = sdat1Ptr + 8 + _sndPtrOffs;  		len = sdat2size; @@ -915,6 +923,8 @@ void ScummEngine_v80he::createSound(int snd1id, int snd2id) {  		_sndPtrOffs += sdat2size;  		_sndTmrOffs += sdat2size;  	} else { +		// We might not have enough space leftover at the end of sound 1 +		// -> Append as much of possible of sound 2 to sound 1  		src = sdat2Ptr + 8;  		dst = sdat1Ptr + 8 + _sndPtrOffs;  		len = sdat1size; @@ -922,6 +932,8 @@ void ScummEngine_v80he::createSound(int snd1id, int snd2id) {  		memcpy(dst, src, len);  		if (sdat2size != sdat1size) { +			// We don't have enough space +			// -> Start overwriting the beginning of the sound again  			src = sdat2Ptr + 8 + sdat1size;  			dst = sdat1Ptr + 8;  			len = sdat2size - sdat1size; diff --git a/engines/scumm/imuse/imuse.cpp b/engines/scumm/imuse/imuse.cpp index 016ba89e7b..b69ce552bc 100644 --- a/engines/scumm/imuse/imuse.cpp +++ b/engines/scumm/imuse/imuse.cpp @@ -363,7 +363,7 @@ void IMuseInternal::pause(bool paused) {  	_paused = paused;  } -int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) { +int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad) {  	Common::StackLock lock(_mutex, "IMuseInternal::save_or_load()");  	const SaveLoadEntry mainEntries[] = {  		MKLINE(IMuseInternal, _queue_end, sleUint8, VER(8)), @@ -440,7 +440,16 @@ int IMuseInternal::save_or_load(Serializer *ser, ScummEngine *scumm) {  	for (i = 0; i < 8; ++i)  		ser->saveLoadEntries(0, volumeFaderEntries); -	if (ser->isLoading()) { +	// Normally, we have to fix up the data structures after loading a +	// saved game. But there are cases where we don't. For instance, The +	// Macintosh version of Monkey Island 1 used to convert the Mac0 music +	// resources to General MIDI and play it through iMUSE as a rough +	// approximation. Now it has its own player, but old savegame still +	// have the iMUSE data in them. We have to skip that data, using a +	// dummy iMUSE object, but since the resource is no longer recognizable +	// to iMUSE, the fixup fails hard. So yes, this is a bit of a hack. + +	if (ser->isLoading() && fixAfterLoad) {  		// Load all sounds that we need  		fix_players_after_load(scumm);  		fix_parts_after_load(); diff --git a/engines/scumm/imuse/imuse.h b/engines/scumm/imuse/imuse.h index 23449e470b..cce5309229 100644 --- a/engines/scumm/imuse/imuse.h +++ b/engines/scumm/imuse/imuse.h @@ -62,7 +62,7 @@ public:  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 int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true) = 0;  	virtual bool get_sound_active(int sound) const = 0;  	virtual int32 doCommand(int numargs, int args[]) = 0;  	virtual int clear_queue() = 0; diff --git a/engines/scumm/imuse/imuse_internal.h b/engines/scumm/imuse/imuse_internal.h index 846e2d7545..6be564a517 100644 --- a/engines/scumm/imuse/imuse_internal.h +++ b/engines/scumm/imuse/imuse_internal.h @@ -518,7 +518,7 @@ protected:  public:  	// IMuse interface  	void pause(bool paused); -	int save_or_load(Serializer *ser, ScummEngine *scumm); +	int save_or_load(Serializer *ser, ScummEngine *scumm, bool fixAfterLoad = true);  	bool get_sound_active(int sound) const;  	int32 doCommand(int numargs, int args[]);  	uint32 property(int prop, uint32 value); diff --git a/engines/scumm/imuse/imuse_part.cpp b/engines/scumm/imuse/imuse_part.cpp index 89c16a8bb5..5e928f3d44 100644 --- a/engines/scumm/imuse/imuse_part.cpp +++ b/engines/scumm/imuse/imuse_part.cpp @@ -111,8 +111,19 @@ void Part::saveLoadWithSerializer(Serializer *ser) {  }  void Part::set_detune(int8 detune) { -	_detune_eff = clamp((_detune = detune) + _player->getDetune(), -128, 127); -	sendPitchBend(); +	// Sam&Max does not have detune, so we just ignore this here. We still get +	// this called, since Sam&Max uses the same controller for a different +	// purpose. +	if (_se->_game_id == GID_SAMNMAX) { +#if 0 +		if (_mc) { +			_mc->controlChange(17, detune + 0x40); +		} +#endif +	} else { +		_detune_eff = clamp((_detune = detune) + _player->getDetune(), -128, 127); +		sendPitchBend(); +	}  }  void Part::pitchBend(int16 value) { diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 8499c9bad3..28884d7f78 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -36,6 +36,7 @@ MODULE_OBJS := \  	object.o \  	palette.o \  	player_apple2.o \ +	player_mac.o \  	player_mod.o \  	player_nes.o \  	player_pce.o \ @@ -47,7 +48,9 @@ MODULE_OBJS := \  	player_v2base.o \  	player_v2cms.o \  	player_v3a.o \ +	player_v3m.o \  	player_v4a.o \ +	player_v5m.o \  	resource_v2.o \  	resource_v3.o \  	resource_v4.o \ diff --git a/engines/scumm/music.h b/engines/scumm/music.h index a527c77b72..9fd14d830e 100644 --- a/engines/scumm/music.h +++ b/engines/scumm/music.h @@ -24,6 +24,7 @@  #define SCUMM_MUSIC_H  #include "common/scummsys.h" +#include "engines/scumm/saveload.h"  namespace Scumm { @@ -78,6 +79,11 @@ public:  	 * @return the music timer  	 */  	virtual int  getMusicTimer() { return 0; } + +	/** +	 * Save or load the music state. +	 */ +	virtual void saveLoadWithSerializer(Serializer *ser) {}  };  } // End of namespace Scumm diff --git a/engines/scumm/object.cpp b/engines/scumm/object.cpp index 77c75c4ad6..ed77a863cd 100644 --- a/engines/scumm/object.cpp +++ b/engines/scumm/object.cpp @@ -433,10 +433,14 @@ void ScummEngine::getObjectXYPos(int object, int &x, int &y, int &dir) {  			y = od.y_pos + (int16)READ_LE_UINT16(&imhd->old.hotspot[state].y);  		}  	} else if (_game.version <= 2) { -		if (od.actordir) { -			x = od.walk_x; -			y = od.walk_y; -		} else { +		x = od.walk_x; +		y = od.walk_y; + +		// Adjust x, y when no actor direction is set, but only perform this +		// adjustment for V0 games (e.g. MM C64), otherwise certain scenes in +		// newer games are affected as well (e.g. the interior of the Shuttle +		// Bus scene in Zak V2, where no actor is present). Refer to bug #3526089. +		if (!od.actordir && _game.version == 0) {  			x = od.x_pos + od.width / 2;  			y = od.y_pos + od.height / 2;  		} diff --git a/engines/scumm/player_mac.cpp b/engines/scumm/player_mac.cpp new file mode 100644 index 0000000000..c16c85bff3 --- /dev/null +++ b/engines/scumm/player_mac.cpp @@ -0,0 +1,415 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/player_mac.h" +#include "scumm/resource.h" +#include "scumm/scumm.h" +#include "scumm/imuse/imuse.h" + +namespace Scumm { + +Player_Mac::Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds) +	: _vm(scumm), +	  _mixer(mixer), +	  _sampleRate(_mixer->getOutputRate()), +	  _soundPlaying(-1), +	  _numberOfChannels(numberOfChannels), +	  _channelMask(channelMask), +	  _fadeNoteEnds(fadeNoteEnds) { +	assert(scumm); +	assert(mixer); +} + +void Player_Mac::init() { +	_channel = new Player_Mac::Channel[_numberOfChannels]; + +	int i; + +	for (i = 0; i < _numberOfChannels; i++) { +		_channel[i]._looped = false; +		_channel[i]._length = 0; +		_channel[i]._data = NULL; +		_channel[i]._pos = 0; +		_channel[i]._pitchModifier = 0; +		_channel[i]._velocity = 0; +		_channel[i]._remaining = 0; +		_channel[i]._notesLeft = false; +		_channel[i]._instrument._data = NULL; +		_channel[i]._instrument._size = 0; +		_channel[i]._instrument._rate = 0; +		_channel[i]._instrument._loopStart = 0; +		_channel[i]._instrument._loopEnd = 0; +		_channel[i]._instrument._baseFreq = 0; +		_channel[i]._instrument._pos = 0; +		_channel[i]._instrument._subPos = 0; +	} + +	_pitchTable[116] = 1664510; +	_pitchTable[117] = 1763487; +	_pitchTable[118] = 1868350; +	_pitchTable[119] = 1979447; +	_pitchTable[120] = 2097152; +	_pitchTable[121] = 2221855; +	_pitchTable[122] = 2353973; +	_pitchTable[123] = 2493948; +	_pitchTable[124] = 2642246; +	_pitchTable[125] = 2799362; +	_pitchTable[126] = 2965820; +	_pitchTable[127] = 3142177; +	for (i = 115; i >= 0; --i) { +		_pitchTable[i] = _pitchTable[i + 12] / 2; +	} + +	setMusicVolume(255); + +	if (!checkMusicAvailable()) { +		return; +	} + +	_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +Player_Mac::~Player_Mac() { +	Common::StackLock lock(_mutex); +	_mixer->stopHandle(_soundHandle); +	stopAllSounds_Internal(); +	delete[] _channel; +} + +void Player_Mac::saveLoadWithSerializer(Serializer *ser) { +	Common::StackLock lock(_mutex); +	if (ser->getVersion() < VER(94)) { +		if (_vm->_game.id == GID_MONKEY && ser->isLoading()) { +			IMuse *dummyImuse = IMuse::create(_vm->_system, NULL, NULL); +			dummyImuse->save_or_load(ser, _vm, false); +			delete dummyImuse; +		} +	} else { +		static const SaveLoadEntry musicEntries[] = { +			MKLINE(Player_Mac, _sampleRate, sleUint32, VER(94)), +			MKLINE(Player_Mac, _soundPlaying, sleInt16, VER(94)), +			MKEND() +		}; + +		static const SaveLoadEntry channelEntries[] = { +			MKLINE(Channel, _pos, sleUint16, VER(94)), +			MKLINE(Channel, _pitchModifier, sleInt32, VER(94)), +			MKLINE(Channel, _velocity, sleUint8, VER(94)), +			MKLINE(Channel, _remaining, sleUint32, VER(94)), +			MKLINE(Channel, _notesLeft, sleUint8, VER(94)), +			MKEND() +		}; + +		static const SaveLoadEntry instrumentEntries[] = { +			MKLINE(Instrument, _pos, sleUint32, VER(94)), +			MKLINE(Instrument, _subPos, sleUint32, VER(94)), +			MKEND() +		}; + +		uint32 mixerSampleRate = _sampleRate; +		int i; + +		ser->saveLoadEntries(this, musicEntries); + +		if (ser->isLoading() && _soundPlaying != -1) { +			const byte *ptr = _vm->getResourceAddress(rtSound, _soundPlaying); +			assert(ptr); +			loadMusic(ptr); +		} + +		ser->saveLoadArrayOf(_channel, _numberOfChannels, sizeof(Channel), channelEntries); +		for (i = 0; i < _numberOfChannels; i++) { +			ser->saveLoadEntries(&_channel[i], instrumentEntries); +		} + +		if (ser->isLoading()) { +			// If necessary, adjust the channel data to fit the +			// current sample rate. +			if (_soundPlaying != -1 && _sampleRate != mixerSampleRate) { +				double mult = (double)_sampleRate / (double)mixerSampleRate; +				for (i = 0; i < _numberOfChannels; i++) { +					_channel[i]._pitchModifier = (int)((double)_channel[i]._pitchModifier * mult); +					_channel[i]._remaining = (int)((double)_channel[i]._remaining / mult); +				} +			} +			_sampleRate = mixerSampleRate; +		} +	} +} + +void Player_Mac::setMusicVolume(int vol) { +	debug(5, "Player_Mac::setMusicVolume(%d)", vol); +} + +void Player_Mac::stopAllSounds_Internal() { +	if (_soundPlaying != -1) { +		_vm->_res->unlock(rtSound, _soundPlaying); +	} +	_soundPlaying = -1; +	for (int i = 0; i < _numberOfChannels; i++) { +		// The channel data is managed by the resource manager, so +		// don't delete that. +		delete[] _channel[i]._instrument._data; +		_channel[i]._instrument._data = NULL; + +		_channel[i]._remaining = 0; +		_channel[i]._notesLeft = false; +	} +} + +void Player_Mac::stopAllSounds() { +	Common::StackLock lock(_mutex); +	debug(5, "Player_Mac::stopAllSounds()"); +	stopAllSounds_Internal(); +} + +void Player_Mac::stopSound(int nr) { +	Common::StackLock lock(_mutex); +	debug(5, "Player_Mac::stopSound(%d)", nr); + +	if (nr == _soundPlaying) { +		stopAllSounds(); +	} +} + +void Player_Mac::startSound(int nr) { +	Common::StackLock lock(_mutex); +	debug(5, "Player_Mac::startSound(%d)", nr); + +	stopAllSounds_Internal(); + +	const byte *ptr = _vm->getResourceAddress(rtSound, nr); +	assert(ptr); + +	if (!loadMusic(ptr)) { +		return; +	} + +	_vm->_res->lock(rtSound, nr); +	_soundPlaying = nr; +} + +bool Player_Mac::Channel::loadInstrument(Common::SeekableReadStream *stream) { +	uint16 soundType = stream->readUint16BE(); +	if (soundType != 1) { +		warning("Player_Mac::loadInstrument: Unsupported sound type %d", soundType); +		return false; +	} +	uint16 typeCount = stream->readUint16BE(); +	if (typeCount != 1) { +		warning("Player_Mac::loadInstrument: Unsupported data type count %d", typeCount); +		return false; +	} +	uint16 dataType = stream->readUint16BE(); +	if (dataType != 5) { +		warning("Player_Mac::loadInstrument: Unsupported data type %d", dataType); +		return false; +	} + +	stream->readUint32BE();	// initialization option + +	uint16 cmdCount = stream->readUint16BE(); +	if (cmdCount != 1) { +		warning("Player_Mac::loadInstrument: Unsupported command count %d", cmdCount); +		return false; +	} +	uint16 command = stream->readUint16BE(); +	if (command != 0x8050 && command != 0x8051) { +		warning("Player_Mac::loadInstrument: Unsupported command 0x%04X", command); +		return false; +	} + +	stream->readUint16BE(); // 0 +	uint32 soundHeaderOffset = stream->readUint32BE(); + +	stream->seek(soundHeaderOffset); + +	uint32 soundDataOffset = stream->readUint32BE(); +	uint32 size = stream->readUint32BE(); +	uint32 rate = stream->readUint32BE() >> 16; +	uint32 loopStart = stream->readUint32BE(); +	uint32 loopEnd = stream->readUint32BE(); +	byte encoding = stream->readByte(); +	byte baseFreq = stream->readByte(); + +	if (encoding != 0) { +		warning("Player_Mac::loadInstrument: Unsupported encoding %d", encoding); +		return false; +	} + +	stream->skip(soundDataOffset); + +	byte *data = new byte[size]; +	stream->read(data, size); + +	_instrument._data = data; +	_instrument._size = size; +	_instrument._rate = rate; +	_instrument._loopStart = loopStart; +	_instrument._loopEnd = loopEnd; +	_instrument._baseFreq = baseFreq; + +	return true; +} + +int Player_Mac::getMusicTimer() { +	return 0; +} + +int Player_Mac::getSoundStatus(int nr) const { +	return _soundPlaying == nr; +} + +uint32 Player_Mac::durationToSamples(uint16 duration) { +	// The correct formula should be: +	// +	// (duration * 473 * _sampleRate) / (4 * 480 * 480) +	// +	// But that's likely to cause integer overflow, so we do it in two +	// steps and hope that the rounding error won't be noticeable. +	// +	// The original code is a bit unclear on if it should be 473 or 437, +	// but since the comments indicated 473 I'm assuming 437 was a typo. +	uint32 samples = (duration * _sampleRate) / (4 * 480); +	samples = (samples * 473) / 480; +	return samples; +} + +int Player_Mac::noteToPitchModifier(byte note, Instrument *instrument) { +	if (note > 0) { +		const int pitchIdx = note + 60 - instrument->_baseFreq; +		// I don't want to use floating-point arithmetics here, but I +		// ran into overflow problems with the church music in Monkey +		// Island. It's only once per note, so it should be ok. +		double mult = (double)instrument->_rate / (double)_sampleRate; +		return (int)(mult * _pitchTable[pitchIdx]); +	} else { +		return 0; +	} +} + +int Player_Mac::readBuffer(int16 *data, const int numSamples) { +	Common::StackLock lock(_mutex); + +	memset(data, 0, numSamples * 2); +	if (_soundPlaying == -1) { +		return numSamples; +	} + +	bool notesLeft = false; + +	for (int i = 0; i < _numberOfChannels; i++) { +		if (!(_channelMask & (1 << i))) { +			continue; +		} + +		uint samplesLeft = numSamples; +		int16 *ptr = data; + +		while (samplesLeft > 0) { +			int generated; +			if (_channel[i]._remaining == 0) { +				uint32 samples; +				int pitchModifier; +				byte velocity; +				if (getNextNote(i, samples, pitchModifier, velocity)) { +					_channel[i]._remaining = samples; +					_channel[i]._pitchModifier = pitchModifier; +					_channel[i]._velocity = velocity; + +				} else { +					_channel[i]._pitchModifier = 0; +					_channel[i]._velocity = 0; +					_channel[i]._remaining = samplesLeft; +				} +			} +			generated = MIN<uint32>(_channel[i]._remaining, samplesLeft); +			if (_channel[i]._velocity != 0) { +				_channel[i]._instrument.generateSamples(ptr, _channel[i]._pitchModifier, _channel[i]._velocity, generated, _channel[i]._remaining, _fadeNoteEnds); +			} +			ptr += generated; +			samplesLeft -= generated; +			_channel[i]._remaining -= generated; +		} + +		if (_channel[i]._notesLeft) { +			notesLeft = true; +		} +	} + +	if (!notesLeft) { +		stopAllSounds_Internal(); +	} + +	return numSamples; +} + +void Player_Mac::Instrument::generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds) { +	int samplesLeft = numSamples; +	while (samplesLeft) { +		_subPos += pitchModifier; +		while (_subPos >= 0x10000) { +			_subPos -= 0x10000; +			_pos++; +			if (_pos >= _loopEnd) { +				_pos = _loopStart; +			} +		} + +		int newSample = (((int16)((_data[_pos] << 8) ^ 0x8000)) * volume) / 255; + +		if (fadeNoteEnds) { +			// Fade out the last 100 samples on each note. Even at +			// low output sample rates this is just a fraction of a +			// second, but it gets rid of distracting "pops" at the +			// end when the sample would otherwise go abruptly from +			// something to nothing. This was particularly +			// noticeable on the distaff notes in Loom. +			// +			// The reason it's conditional is that Monkey Island +			// appears to have a "hold current note" command, and +			// if we fade out the current note in that case we +			// will actually introduce new "pops". + +			remainingSamplesOnNote--; +			if (remainingSamplesOnNote < 100) { +				newSample = (newSample * remainingSamplesOnNote) / 100; +			} +		} + +		int sample = *data + newSample; +		if (sample > 32767) { +			sample = 32767; +		} else if (sample < -32768) { +			sample = -32768; +		} + +		*data++ = sample; +		samplesLeft--; +	} +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_mac.h b/engines/scumm/player_mac.h new file mode 100644 index 0000000000..09307b4e57 --- /dev/null +++ b/engines/scumm/player_mac.h @@ -0,0 +1,133 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCUMM_PLAYER_MAC_H +#define SCUMM_PLAYER_MAC_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/saveload.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +#define RES_SND MKTAG('s', 'n', 'd', ' ') + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm Macintosh music driver, base class. + */ +class Player_Mac : public Audio::AudioStream, public MusicEngine { +public: +	Player_Mac(ScummEngine *scumm, Audio::Mixer *mixer, int numberOfChannels, int channelMask, bool fadeNoteEnds); +	virtual ~Player_Mac(); + +	void init(); + +	// MusicEngine API +	virtual void setMusicVolume(int vol); +	virtual void startSound(int sound); +	virtual void stopSound(int sound); +	virtual void stopAllSounds(); +	virtual int  getMusicTimer(); +	virtual int  getSoundStatus(int sound) const; + +	// AudioStream API +	virtual int readBuffer(int16 *buffer, const int numSamples); +	virtual bool isStereo() const { return false; } +	virtual bool endOfData() const { return false; } +	virtual int getRate() const { return _sampleRate; } + +	virtual void saveLoadWithSerializer(Serializer *ser); + +private: +	Common::Mutex _mutex; +	Audio::Mixer *const _mixer; +	Audio::SoundHandle _soundHandle; +	uint32 _sampleRate; +	int _soundPlaying; + +	void stopAllSounds_Internal(); + +	struct Instrument { +		byte *_data; +		uint32 _size; +		uint32 _rate; +		uint32 _loopStart; +		uint32 _loopEnd; +		byte _baseFreq; + +		uint _pos; +		uint _subPos; + +		void newNote() { +			_pos = 0; +			_subPos = 0; +		} + +		void generateSamples(int16 *data, int pitchModifier, int volume, int numSamples, int remainingSamplesOnNote, bool fadeNoteEnds); +	}; + +	int _pitchTable[128]; +	int _numberOfChannels; +	int _channelMask; +	bool _fadeNoteEnds; + +	virtual bool checkMusicAvailable() { return false; } +	virtual bool loadMusic(const byte *ptr) { return false; } +	virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { return false; } + +protected: +	struct Channel { +		virtual ~Channel() {} + +		Instrument _instrument; +		bool _looped; +		uint32 _length; +		const byte *_data; + +		uint _pos; +		int _pitchModifier; +		byte _velocity; +		uint32 _remaining; + +		bool _notesLeft; + +		bool loadInstrument(Common::SeekableReadStream *stream); + 	}; + +	ScummEngine *const _vm; +	Channel *_channel; + +	uint32 durationToSamples(uint16 duration); +	int noteToPitchModifier(byte note, Instrument *instrument); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/player_v3m.cpp b/engines/scumm/player_v3m.cpp new file mode 100644 index 0000000000..0f222d84fe --- /dev/null +++ b/engines/scumm/player_v3m.cpp @@ -0,0 +1,214 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* +	We have the following information from Lars Christensen (lechimp) and +	Jamieson Christian (jamieson630): + +	RESOURCE DATA +	LE 2 bytes Resource size +	2 bytes Unknown +	2 bytes 'so' +	14 bytes Unknown +	BE 2 bytes Instrument for Stream 1 +	BE 2 bytes Instrument for Stream 2 +	BE 2 bytes Instrument for Stream 3 +	BE 2 bytes Instrument for Stream 4 +	BE 2 bytes Instrument for Stream 5 +	BE 2 bytes Offset to Stream 1 +	BE 2 bytes Offset to Stream 2 +	BE 2 bytes Offset to Stream 3 +	BE 2 bytes Offset to Stream 4 +	BE 2 bytes Offset to Stream 5 +	? bytes The streams + +	STREAM DATA +	BE 2 bytes Unknown (always 1?) +	2 bytes Unknown (always 0?) +	BE 2 bytes Number of events in stream +	? bytes Stream data + +	Each stream event is exactly 3 bytes, therefore one can +	assert that numEvents == (streamSize - 6) / 3. The +	polyphony of a stream appears to be 1; in other words, only +	one note at a time can be playing in each stream. The next +	event is not executed until the current note (or rest) is +	finished playing; therefore, note duration also serves as the +	time delta between events. + +	FOR EACH EVENTS +	BE 2 bytes Note duration +	1 byte Note number to play (0 = rest/silent) + +	Oh, and quick speculation -- Stream 1 may be used for a +	single-voice interleaved version of the music, where Stream 2- +	5 represent a version of the music in up to 4-voice +	polyphony, one voice per stream. I postulate thus because +	the first stream of the Mac Loom theme music contains +	interleaved voices, whereas the second stream seemed to +	contain only the pizzicato bottom-end harp. Stream 5, in this +	example, is empty, so if my speculation is correct, this +	particular musical number supports 3-voice polyphony at +	most. I must check out Streams 3 and 4 to see what they +	contain. + +	========== + +	The instruments appear to be identified by their resource IDs: + +	1000	Dual Harp +	10895	harp1 +	11445	strings1 +	11548	silent +	13811	staff1 +	15703	brass1 +	16324	flute1 +	25614	accordian 1 +	28110	f horn1 +	29042	bassoon1 +*/ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/player_v3m.h" +#include "scumm/scumm.h" + +namespace Scumm { + +Player_V3M::Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer) +	: Player_Mac(scumm, mixer, 5, 0x1E, true) { +	assert(_vm->_game.id == GID_LOOM); + +	// Channel 0 seems to be what was played on low-end macs, that couldn't +	// handle multi-channel music and play the game at the same time. I'm +	// not sure if stream 4 is ever used, but let's use it just in case. +} + +// \xAA is a trademark glyph in Mac OS Roman. We try that, but also the Windows +// version, the UTF-8 version, and just plain without in case the file system +// can't handle exotic characters like that. + +static const char *loomFileNames[] = { +	"Loom\xAA", +	"Loom\x99", +	"Loom\xE2\x84\xA2", +	"Loom" +}; + +bool Player_V3M::checkMusicAvailable() { +	Common::MacResManager resource; + +	for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { +		if (resource.exists(loomFileNames[i])) { +			return true; +		} +	} +		 +	GUI::MessageDialog dialog(_( +		"Could not find the 'Loom' Macintosh executable to read the\n" +		"instruments from. Music will be disabled."), _("OK")); +	dialog.runModal(); +	return false; +} + +bool Player_V3M::loadMusic(const byte *ptr) { +	Common::MacResManager resource; +	bool found = false; + +	for (int i = 0; i < ARRAYSIZE(loomFileNames); i++) { +		if (resource.open(loomFileNames[i])) { +			found = true; +			break; +		} +	} + +	if (!found) { +		return false; +	} + +	if (ptr[4] != 's' || ptr[5] != 'o') { +		// Like the original we ignore all sound resources which do not have +		// a 'so' tag in them. +		// See bug #3602239 ("Mac Loom crashes using opening spell on +		// gravestone") for a case where this is required. Loom Mac tries to +		// play resource 11 here. This resource is no Mac sound resource +		// though, it is a PC Speaker resource. A test with the original +		// interpreter also has shown that no sound is played while the +		// screen is shaking. +		debug(5, "Player_V3M::loadMusic: Skipping unknown music type %02X%02X", ptr[4], ptr[5]); +		resource.close(); +		return false; +	} + +	uint i; +	for (i = 0; i < 5; i++) { +		int instrument = READ_BE_UINT16(ptr + 20 + 2 * i); +		int offset = READ_BE_UINT16(ptr + 30 + 2 * i); + +		_channel[i]._looped = false; +		_channel[i]._length = READ_BE_UINT16(ptr + offset + 4) * 3; +		_channel[i]._data = ptr + offset + 6; +		_channel[i]._pos = 0; +		_channel[i]._pitchModifier = 0; +		_channel[i]._velocity = 0; +		_channel[i]._remaining = 0; +		_channel[i]._notesLeft = true; + +		Common::SeekableReadStream *stream = resource.getResource(RES_SND, instrument); +		if (_channel[i].loadInstrument(stream)) { +			debug(6, "Player_V3M::loadMusic: Channel %d - Loaded Instrument %d (%s)", i, instrument, resource.getResName(RES_SND, instrument).c_str()); +		} else { +			resource.close(); +			return false; +		} +	} + +	resource.close(); +	return true; +} + +bool Player_V3M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { +	_channel[ch]._instrument.newNote(); +	if (_channel[ch]._pos >= _channel[ch]._length) { +		if (!_channel[ch]._looped) { +			_channel[ch]._notesLeft = false; +			return false; +		} +		_channel[ch]._pos = 0; +	} +	uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); +	byte note = _channel[ch]._data[_channel[ch]._pos + 2]; +	samples = durationToSamples(duration); +	if (note > 0) { +		pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); +		velocity = 127; +	} else { +		pitchModifier = 0; +		velocity = 0; +	} +	_channel[ch]._pos += 3; +	return true; +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_v3m.h b/engines/scumm/player_v3m.h new file mode 100644 index 0000000000..359bab32a9 --- /dev/null +++ b/engines/scumm/player_v3m.h @@ -0,0 +1,54 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCUMM_PLAYER_V3M_H +#define SCUMM_PLAYER_V3M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/player_mac.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V3 Macintosh music driver. + */ +class Player_V3M : public Player_Mac { +public: +	Player_V3M(ScummEngine *scumm, Audio::Mixer *mixer); + +	virtual bool checkMusicAvailable(); +	virtual bool loadMusic(const byte *ptr); +	virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/player_v5m.cpp b/engines/scumm/player_v5m.cpp new file mode 100644 index 0000000000..500f3bbc40 --- /dev/null +++ b/engines/scumm/player_v5m.cpp @@ -0,0 +1,246 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* +	From Markus Magnuson (superqult) we got this information: +	Mac0 +	--- +	   4 bytes - 'SOUN' +	BE 4 bytes - block length + +		   4 bytes  - 'Mac0' +		BE 4 bytes  - (blockLength - 27) +		   28 bytes - ??? + +		   do this three times (once for each channel): +			  4 bytes  - 'Chan' +		   BE 4 bytes  - channel length +			  4 bytes  - instrument name (e.g. 'MARI') + +			  do this for ((chanLength-24)/4) times: +				 2 bytes  - note duration +				 1 byte   - note value +				 1 byte   - note velocity + +			  4 bytes - ??? +			  4 bytes - 'Loop'/'Done' +			  4 bytes - ??? + +	   1 byte - 0x09 +	--- + +	The instruments presumably correspond to the snd resource names in the +	Monkey Island executable: + +	Instruments +	"MARI" - MARIMBA +	"PLUC" - PLUCK +	"HARM" - HARMONIC +	"PIPE" - PIPEORGAN +	"TROM" - TROMBONE +	"STRI" - STRINGS +	"HORN" - HORN +	"VIBE" - VIBES +	"SHAK" - SHAKUHACHI +	"PANP" - PANPIPE +	"WHIS" - WHISTLE +	"ORGA" - ORGAN3 +	"BONG" - BONGO +	"BASS" - BASS + +	--- + +	Note values <= 1 are silent. +*/ + +#include "common/macresman.h" +#include "common/translation.h" +#include "engines/engine.h" +#include "gui/message.h" +#include "scumm/player_v5m.h" +#include "scumm/scumm.h" + +namespace Scumm { + +Player_V5M::Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer) +	: Player_Mac(scumm, mixer, 3, 0x07, false) { +	assert(_vm->_game.id == GID_MONKEY); +} + +// Try both with and without underscore in the filename, because hfsutils may +// turn the space into an underscore. At least, it did for me. + +static const char *monkeyIslandFileNames[] = { +	"Monkey Island", +	"Monkey_Island" +}; + +bool Player_V5M::checkMusicAvailable() { +	Common::MacResManager resource; + +	for (int i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) { +		if (resource.exists(monkeyIslandFileNames[i])) { +			return true; +		} +	} + +	GUI::MessageDialog dialog(_( +		"Could not find the 'Monkey Island' Macintosh executable to read the\n" +		"instruments from. Music will be disabled."), _("OK")); +	dialog.runModal(); +	return false; +} + +bool Player_V5M::loadMusic(const byte *ptr) { +	Common::MacResManager resource; +	bool found = false; +	uint i; + +	for (i = 0; i < ARRAYSIZE(monkeyIslandFileNames); i++) { +		if (resource.open(monkeyIslandFileNames[i])) { +			found = true; +			break; +		} +	} + +	if (!found) { +		return false; +	} + +	ptr += 8; +	// TODO: Decipher the unknown bytes in the header. For now, skip 'em +	ptr += 28; + +	Common::MacResIDArray idArray = resource.getResIDArray(RES_SND); + +	// Load the three channels and their instruments +	for (i = 0; i < 3; i++) { +		assert(READ_BE_UINT32(ptr) == MKTAG('C', 'h', 'a', 'n')); +		uint32 len = READ_BE_UINT32(ptr + 4); +		uint32 instrument = READ_BE_UINT32(ptr + 8); + +		_channel[i]._length = len - 20; +		_channel[i]._data = ptr + 12; +		_channel[i]._looped = (READ_BE_UINT32(ptr + len - 8) == MKTAG('L', 'o', 'o', 'p')); +		_channel[i]._pos = 0; +		_channel[i]._pitchModifier = 0; +		_channel[i]._velocity = 0; +		_channel[i]._remaining = 0; +		_channel[i]._notesLeft = true; + +		for (uint j = 0; j < idArray.size(); j++) { +			Common::String name = resource.getResName(RES_SND, idArray[j]); +			if (instrument == READ_BE_UINT32(name.c_str())) { +				debug(6, "Player_V5M::loadMusic: Channel %d: Loading instrument '%s'", i, name.c_str()); +				Common::SeekableReadStream *stream = resource.getResource(RES_SND, idArray[j]); + +				if (!_channel[i].loadInstrument(stream)) { +					resource.close(); +					return false; +				} + +				break; +			} +		} + +		ptr += len; +	} + +	resource.close(); + +	// The last note of each channel is just zeroes. We will adjust this +	// note so that all the channels end at the same time. + +	uint32 samples[3]; +	uint32 maxSamples = 0; +	for (i = 0; i < 3; i++) { +		samples[i] = 0; +		for (uint j = 0; j < _channel[i]._length; j += 4) { +			samples[i] += durationToSamples(READ_BE_UINT16(&_channel[i]._data[j])); +		} +		if (samples[i] > maxSamples) { +			maxSamples = samples[i]; +		} +	} + +	for (i = 0; i < 3; i++) { +		_lastNoteSamples[i] = maxSamples - samples[i]; +	} + +	return true; +} + +bool Player_V5M::getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity) { +	if (_channel[ch]._pos >= _channel[ch]._length) { +		if (!_channel[ch]._looped) { +			_channel[ch]._notesLeft = false; +			return false; +		} +		// FIXME: Jamieson630: The jump seems to be happening +		// too quickly! There should maybe be a pause after +		// the last Note Off? But I couldn't find one in the +		// MI1 Lookout music, where I was hearing problems. +		_channel[ch]._pos = 0; +	} +	uint16 duration = READ_BE_UINT16(&_channel[ch]._data[_channel[ch]._pos]); +	byte note = _channel[ch]._data[_channel[ch]._pos + 2]; +	samples = durationToSamples(duration); + +	if (note != 1) { +		_channel[ch]._instrument.newNote(); +	} + +	if (note > 1) { +		pitchModifier = noteToPitchModifier(note, &_channel[ch]._instrument); +		velocity = _channel[ch]._data[_channel[ch]._pos + 3]; +	} else if (note == 1) { +		// This is guesswork, but Monkey Island uses two different +		// "special" note values: 0, which is clearly a rest, and 1 +		// which is... I thought at first it was a "soft" key off, to +		// fade out the note, but listening to the music in a Mac +		// emulator (which unfortunately doesn't work all that well), +		// I hear no trace of fading out. +		// +		// It could mean "change the volume on the current note", but +		// I can't hear that either, and it always seems to use the +		// exact same velocity on this note. +		// +		// So it appears it really just is a "hold the current note", +		// but why? Couldn't they just have made the original note +		// longer? + +		pitchModifier = _channel[ch]._pitchModifier; +		velocity = _channel[ch]._velocity; +	} else { +		pitchModifier = 0; +		velocity = 0; +	} + +	_channel[ch]._pos += 4; + +	if (_channel[ch]._pos >= _channel[ch]._length) { +		samples = _lastNoteSamples[ch]; +	} +	return true; +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_v5m.h b/engines/scumm/player_v5m.h new file mode 100644 index 0000000000..b2079ee331 --- /dev/null +++ b/engines/scumm/player_v5m.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCUMM_PLAYER_V5M_H +#define SCUMM_PLAYER_V5M_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/mutex.h" +#include "scumm/music.h" +#include "scumm/player_mac.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V5 Macintosh music driver. + */ +class Player_V5M : public Player_Mac { +public: +	Player_V5M(ScummEngine *scumm, Audio::Mixer *mixer); + +	virtual bool checkMusicAvailable(); +	virtual bool loadMusic(const byte *ptr); +	virtual bool getNextNote(int ch, uint32 &samples, int &pitchModifier, byte &velocity); + +private: +	uint32 _lastNoteSamples[3]; +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index 72896e097a..3453e53a18 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -1477,9 +1477,13 @@ void ScummEngine::saveOrLoad(Serializer *s) {  	} -	// Save/load FM-Towns audio status -	if (_townsPlayer) -		_townsPlayer->saveLoadWithSerializer(s); +	// +	// Save/load music engine status +	// +	if (_musicEngine) { +		_musicEngine->saveLoadWithSerializer(s); +	} +  	//  	// Save/load the charset renderer state diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h index a640bc1e17..7b2ff91ad3 100644 --- a/engines/scumm/saveload.h +++ b/engines/scumm/saveload.h @@ -47,7 +47,7 @@ namespace Scumm {   * only saves/loads those which are valid for the version of the savegame   * which is being loaded/saved currently.   */ -#define CURRENT_VER 93 +#define CURRENT_VER 94  /**   * An auxillary macro, used to specify savegame versions. We use this instead @@ -74,7 +74,7 @@ namespace Scumm {   * what POD means refer to <http://en.wikipedia.org/wiki/Plain_Old_Data_Structures> or   * to <http://www.informit.com/guides/content.asp?g=cplusplus&seqNum=32&rl=1>)   */ -#define OFFS(type,item) (((ptrdiff_t)(&((type *)42)->type::item))-42) +#define OFFS(type,item) ((uint32)(((ptrdiff_t)(&((type *)42)->type::item))-42))  /**   * Similar to the OFFS macro, this macro computes the size (in bytes) of a @@ -84,19 +84,19 @@ namespace Scumm {  // Any item that is still in use automatically gets a maxVersion equal to CURRENT_VER  #define MKLINE(type,item,saveas,minVer) {OFFS(type,item),saveas,SIZE(type,item),minVer,CURRENT_VER} -#define MKARRAY(type,item,saveas,dim,minVer) {OFFS(type,item),128|saveas,SIZE(type,item),minVer,CURRENT_VER}, {dim,1,0,0,0} -#define MKARRAY2(type,item,saveas,dim,dim2,rowlen,minVer) {OFFS(type,item),128|saveas,SIZE(type,item),minVer,CURRENT_VER}, {dim,dim2,rowlen,0,0} +#define MKARRAY(type,item,saveas,dim,minVer) {OFFS(type,item),128|saveas,SIZE(type,item),minVer,CURRENT_VER}, {(uint32)(dim),1,0,0,0} +#define MKARRAY2(type,item,saveas,dim,dim2,rowlen,minVer) {OFFS(type,item),128|saveas,SIZE(type,item),minVer,CURRENT_VER}, {(uint32)(dim),(uint32)(dim2),(uint16)(rowlen),0,0}  // Use this if you have an entry that used to be smaller:  #define MKLINE_OLD(type,item,saveas,minVer,maxVer) {OFFS(type,item),saveas,SIZE(type,item),minVer,maxVer} -#define MKARRAY_OLD(type,item,saveas,dim,minVer,maxVer) {OFFS(type,item),128|saveas,SIZE(type,item),minVer,maxVer}, {dim,1,0,0,0} -#define MKARRAY2_OLD(type,item,saveas,dim,dim2,rowlen,minVer,maxVer) {OFFS(type,item),128|saveas,SIZE(type,item),minVer,maxVer}, {dim,dim2,rowlen,0,0} +#define MKARRAY_OLD(type,item,saveas,dim,minVer,maxVer) {OFFS(type,item),128|saveas,SIZE(type,item),minVer,maxVer}, {(uint32)(dim),1,0,0,0} +#define MKARRAY2_OLD(type,item,saveas,dim,dim2,rowlen,minVer,maxVer) {OFFS(type,item),128|saveas,SIZE(type,item),minVer,maxVer}, {(uint32)(dim),(uint32)(dim2),(uint16)(rowlen),0,0}  // An obsolete item/array, to be ignored upon load. We retain the type/item params to make it easier to debug.  // Obsolete items have size == 0.  #define MK_OBSOLETE(type,item,saveas,minVer,maxVer) {0,saveas,0,minVer,maxVer} -#define MK_OBSOLETE_ARRAY(type,item,saveas,dim,minVer,maxVer) {0,128|saveas,0,minVer,maxVer}, {dim,1,0,0,0} -#define MK_OBSOLETE_ARRAY2(type,item,saveas,dim,dim2,rowlen,minVer,maxVer) {0,128|saveas,0,minVer,maxVer}, {dim,dim2,rowlen,0,0} +#define MK_OBSOLETE_ARRAY(type,item,saveas,dim,minVer,maxVer) {0,128|saveas,0,minVer,maxVer}, {(uint32)(dim),1,0,0,0} +#define MK_OBSOLETE_ARRAY2(type,item,saveas,dim,dim2,rowlen,minVer,maxVer) {0,128|saveas,0,minVer,maxVer}, {(uint32)(dim),(uint32)(dim2),(uint16)(rowlen),0,0}  // End marker  #define MKEND() {0xFFFF,0xFF,0xFF,0,0} diff --git a/engines/scumm/script.cpp b/engines/scumm/script.cpp index d8c4948ea8..8587fb8092 100644 --- a/engines/scumm/script.cpp +++ b/engines/scumm/script.cpp @@ -1366,9 +1366,15 @@ void ScummEngine::runInputScript(int clickArea, int val, int mode) {  		// Clicks are handled differently in Indy3 mac: param 2 of the  		// input script is set to 0 for normal clicks, and to 1 for double clicks. +		// The EGA DOS version of Loom also checks that the second click happens +		// close enough to the first one, but that seems like overkill.  		uint32 time = _system->getMillis();  		args[2] = (time < _lastInputScriptTime + 500);	// 500 ms double click delay  		_lastInputScriptTime = time; +	} else if (_game.id == GID_LOOM && _game.platform == Common::kPlatformMacintosh) { +		uint32 time = _system->getMillis(); +		VAR(52) = (time < _lastInputScriptTime + 500);	// 500 ms double click delay +		_lastInputScriptTime = time;  	}  	if (verbScript) diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 2c79fb8de0..3afeeda13d 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -61,7 +61,9 @@  #include "scumm/player_v2cms.h"  #include "scumm/player_v2a.h"  #include "scumm/player_v3a.h" +#include "scumm/player_v3m.h"  #include "scumm/player_v4a.h" +#include "scumm/player_v5m.h"  #include "scumm/resource.h"  #include "scumm/he/resource_he.h"  #include "scumm/scumm_v0.h" @@ -1819,6 +1821,12 @@ void ScummEngine::setupMusic(int midi) {  #endif  	} else if (_game.platform == Common::kPlatformAmiga && _game.version <= 4) {  		_musicEngine = new Player_V4A(this, _mixer); +	} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_LOOM) { +		_musicEngine = new Player_V3M(this, _mixer); +		((Player_V3M *)_musicEngine)->init(); +	} else if (_game.platform == Common::kPlatformMacintosh && _game.id == GID_MONKEY) { +		_musicEngine = new Player_V5M(this, _mixer); +		((Player_V5M *)_musicEngine)->init();  	} else if (_game.id == GID_MANIAC && _game.version == 1) {  		_musicEngine = new Player_V1(this, _mixer, MidiDriver::getMusicType(dev) != MT_PCSPK);  	} else if (_game.version <= 2) { @@ -1858,6 +1866,8 @@ void ScummEngine::setupMusic(int midi) {  			if (_sound->_musicType == MDT_ADLIB || _sound->_musicType == MDT_TOWNS || multi_midi) {  				adlibMidiDriver = MidiDriver::createMidi(MidiDriver::detectDevice(_sound->_musicType == MDT_TOWNS ? MDT_TOWNS : MDT_ADLIB));  				adlibMidiDriver->property(MidiDriver::PROP_OLD_ADLIB, (_game.features & GF_SMALL_HEADER) ? 1 : 0); +				// Try to use OPL3 mode for Sam&Max when possible. +				adlibMidiDriver->property(MidiDriver::PROP_SCUMM_OPL3, (_game.id == GID_SAMNMAX) ? 1 : 0);  			} else if (_sound->_musicType == MDT_PCSPK) {  				adlibMidiDriver = new PcSpkDriver(_mixer);  			} diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index a1cecfa0b3..2fe16c5441 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -346,29 +346,6 @@ void Sound::playSound(int soundID) {  			warning("Scumm::Sound::playSound: encountered audio resoure with chunk type 'SOUN' and sound type %d", type);  		}  	} -	else if ((_vm->_game.id == GID_LOOM) && (_vm->_game.platform == Common::kPlatformMacintosh))  { -		// Mac version of Loom uses yet another sound format -		/* -		playSound #9 (room 70) -		000000: 55 00 00 45  73 6f 00 64  01 00 00 00  00 00 00 00   |U..Eso.d........| -		000010: 00 05 00 8e  2a 8f 2d 1c  2a 8f 2a 8f  2d 1c 00 28   |....*.-.*.*.-..(| -		000020: 00 31 00 3a  00 43 00 4c  00 01 00 00  00 01 00 64   |.1.:.C.L.......d| -		000030: 5a 00 01 00  00 00 01 00  64 00 00 01  00 00 00 01   |Z.......d.......| -		000040: 00 64 5a 00  01 00 00 00  01 00 64 5a  00 01 00 00   |.dZ.......dZ....| -		000050: 00 01 00 64  00 00 00 00  00 00 00 07  00 00 00 64   |...d...........d| -		000060: 64 00 00 4e  73 6f 00 64  01 00 00 00  00 00 00 00   |d..Nso.d........| -		000070: 00 05 00 89  3d 57 2d 1c  3d 57 3d 57  2d 1c 00 28   |....=W-.=W=W-..(| -		playSound #16 (room 69) -		000000: dc 00 00 a5  73 6f 00 64  01 00 00 00  00 00 00 00   |....so.d........| -		000010: 00 05 00 00  2a 8f 03 e8  03 e8 03 e8  03 e8 00 28   |....*..........(| -		000020: 00 79 00 7f  00 85 00 d6  00 01 00 00  00 19 01 18   |.y..............| -		000030: 2f 00 18 00  01 18 32 00  18 00 01 18  36 00 18 00   |/.....2.....6...| -		000040: 01 18 3b 00  18 00 01 18  3e 00 18 00  01 18 42 00   |..;.....>.....B.| -		000050: 18 00 01 18  47 00 18 00  01 18 4a 00  18 00 01 18   |....G.....J.....| -		000060: 4e 00 10 00  01 18 53 00  10 00 01 18  56 00 10 00   |N.....S.....V...| -		000070: 01 18 5a 00  10 00 02 28  5f 00 01 00  00 00 00 00   |..Z....(_.......| -		*/ -	}  	else if ((_vm->_game.platform == Common::kPlatformMacintosh) && (_vm->_game.id == GID_INDY3) && READ_BE_UINT16(ptr + 8) == 0x1C) {  		// Sound format as used in Indy3 EGA Mac.  		// It seems to be closely related to the Amiga format, see player_v3a.cpp @@ -414,8 +391,7 @@ void Sound::playSound(int soundID) {  	}  	else { -		if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA -			|| (_vm->_game.id == GID_MONKEY && _vm->_game.platform == Common::kPlatformMacintosh)) { +		if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA) {  			// Works around the fact that in some places in MonkeyEGA/VGA,  			// the music is never explicitly stopped.  			// Rather it seems that starting a new music is supposed to @@ -1086,9 +1062,6 @@ void Sound::saveLoadWithSerializer(Serializer *ser) {  #pragma mark --- Sound resource handling ---  #pragma mark - -static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size); - -  /*   * TODO: The way we handle sound/music resources really is one huge hack.   * We probably should reconsider how we do this, and maybe come up with a @@ -1208,11 +1181,9 @@ int ScummEngine::readSoundResource(ResId idx) {  	case MKTAG('M','a','c','0'):  		_fileHandle->seek(-12, SEEK_CUR);  		total_size = _fileHandle->readUint32BE() - 8; -		ptr = (byte *)calloc(total_size, 1); +		ptr = _res->createResource(rtSound, idx, total_size);  		_fileHandle->read(ptr, total_size);  		//dumpResource("sound-", idx, ptr); -		convertMac0Resource(_res, idx, ptr, total_size); -		free(ptr);  		return 1;  	case MKTAG('M','a','c','1'): @@ -1445,219 +1416,6 @@ static byte *writeVLQ(byte *ptr, int value) {  	return ptr;  } -static byte Mac0ToGMInstrument(uint32 type, int &transpose) { -	transpose = 0; -	switch (type) { -	case MKTAG('M','A','R','I'): return 12; -	case MKTAG('P','L','U','C'): return 45; -	case MKTAG('H','A','R','M'): return 22; -	case MKTAG('P','I','P','E'): return 19; -	case MKTAG('T','R','O','M'): transpose = -12; return 57; -	case MKTAG('S','T','R','I'): return 48; -	case MKTAG('H','O','R','N'): return 60; -	case MKTAG('V','I','B','E'): return 11; -	case MKTAG('S','H','A','K'): return 77; -	case MKTAG('P','A','N','P'): return 75; -	case MKTAG('W','H','I','S'): return 76; -	case MKTAG('O','R','G','A'): return 17; -	case MKTAG('B','O','N','G'): return 115; -	case MKTAG('B','A','S','S'): transpose = -24; return 35; -	default: -		error("Unknown Mac0 instrument %s found", tag2str(type)); -	} -} - -static void convertMac0Resource(ResourceManager *res, ResId idx, byte *src_ptr, int size) { -	/* -	From Markus Magnuson (superqult) we got this information: -	Mac0 -	--- -	   4 bytes - 'SOUN' -	BE 4 bytes - block length - -		   4 bytes  - 'Mac0' -		BE 4 bytes  - (blockLength - 27) -		   28 bytes - ??? - -		   do this three times (once for each channel): -			  4 bytes  - 'Chan' -		   BE 4 bytes  - channel length -			  4 bytes  - instrument name (e.g. 'MARI') - -			  do this for ((chanLength-24)/4) times: -				 2 bytes  - note duration -				 1 byte   - note value -				 1 byte   - note velocity - -			  4 bytes - ??? -			  4 bytes - 'Loop'/'Done' -			  4 bytes - ??? - -	   1 byte - 0x09 -	--- - -	Instruments (General Midi): -	"MARI" - Marimba (12) -	"PLUC" - Pizzicato Strings (45) -	"HARM" - Harmonica (22) -	"PIPE" - Church Organ? (19) or Flute? (73) or Bag Pipe (109) -	"TROM" - Trombone (57) -	"STRI" - String Ensemble (48 or 49) -	"HORN" - French Horn? (60) or English Horn? (69) -	"VIBE" - Vibraphone (11) -	"SHAK" - Shakuhachi? (77) -	"PANP" - Pan Flute (75) -	"WHIS" - Whistle (78) / Bottle (76) -	"ORGA" - Drawbar Organ (16; but could also be 17-20) -	"BONG" - Woodblock? (115) -	"BASS" - Bass (32-39) - - -	Now the task could be to convert this into MIDI, to be fed into iMuse. -	Or we do something similiar to what is done in Player_V3, assuming -	we can identify SFX in the MI datafiles for each of the instruments -	listed above. -	*/ - -#if 0 -	byte *ptr = _res->createResource(rtSound, idx, size); -	memcpy(ptr, src_ptr, size); -#else -	const int ppqn = 480; -	byte *ptr, *start_ptr; - -	int total_size = 0; -	total_size += kMIDIHeaderSize; // Header -	total_size += 7;               // Tempo META -	total_size += 3 * 3;           // Three program change mesages -	total_size += 22;              // Possible jump SysEx -	total_size += 5;               // EOT META - -	int i, len; -	byte track_instr[3]; -	byte *track_data[3]; -	int track_len[3]; -	int track_transpose[3]; -	bool looped = false; - -	src_ptr += 8; -	// TODO: Decipher the unknown bytes in the header. For now, skip 'em -	src_ptr += 28; - -	// Parse the three channels -	for (i = 0; i < 3; i++) { -		assert(READ_BE_UINT32(src_ptr) == MKTAG('C','h','a','n')); -		len = READ_BE_UINT32(src_ptr + 4); -		track_len[i] = len - 24; -		track_instr[i] = Mac0ToGMInstrument(READ_BE_UINT32(src_ptr + 8), track_transpose[i]); -		track_data[i] = src_ptr + 12; -		src_ptr += len; -		looped = (READ_BE_UINT32(src_ptr - 8) == MKTAG('L','o','o','p')); - -		// For each note event, we need up to 6 bytes for the -		// Note On (3 VLQ, 3 event), and 6 bytes for the Note -		// Off (3 VLQ, 3 event). So 12 bytes total. -		total_size += 12 * track_len[i]; -	} -	assert(*src_ptr == 0x09); - -	// Create sound resource -	start_ptr = res->createResource(rtSound, idx, total_size); - -	// Insert MIDI header -	ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); - -	// Write a tempo change Meta event -	// 473 / 4 Hz, convert to micro seconds. -	uint32 dw = 1000000 * 437 / 4 / ppqn; // 1000000 * ppqn * 4 / 473; -	memcpy(ptr, "\x00\xFF\x51\x03", 4); ptr += 4; -	*ptr++ = (byte)((dw >> 16) & 0xFF); -	*ptr++ = (byte)((dw >> 8) & 0xFF); -	*ptr++ = (byte)(dw & 0xFF); - -	// Insert program change messages -	*ptr++ = 0; // VLQ -	*ptr++ = 0xC0; -	*ptr++ = track_instr[0]; -	*ptr++ = 0; // VLQ -	*ptr++ = 0xC1; -	*ptr++ = track_instr[1]; -	*ptr++ = 0; // VLQ -	*ptr++ = 0xC2; -	*ptr++ = track_instr[2]; - -	// And now, the actual composition. Please turn all cell phones -	// and pagers off during the performance. Thank you. -	uint16 nextTime[3] = { 1, 1, 1 }; -	int stage[3] = { 0, 0, 0 }; - -	while (track_len[0] | track_len[1] | track_len[2]) { -		int best = -1; -		uint16 bestTime = 0xFFFF; -		for (i = 0; i < 3; ++i) { -			if (track_len[i] && nextTime[i] < bestTime) { -				bestTime = nextTime[i]; -				best = i; -			} -		} -		assert (best != -1); - -		if (!stage[best]) { -			// We are STARTING this event. -			if (track_data[best][2] > 1) { -				// Note On -				ptr = writeVLQ(ptr, nextTime[best]); -				*ptr++ = 0x90 | best; -				*ptr++ = track_data[best][2] + track_transpose[best]; -				*ptr++ = track_data[best][3] * 127 / 100; // Scale velocity -				for (i = 0; i < 3; ++i) -					nextTime[i] -= bestTime; -			} -			nextTime[best] += READ_BE_UINT16 (track_data[best]); -			stage[best] = 1; -		} else { -			// We are ENDING this event. -			if (track_data[best][2] > 1) { -				// There was a Note On, so do a Note Off -				ptr = writeVLQ(ptr, nextTime[best]); -				*ptr++ = 0x80 | best; -				*ptr++ = track_data[best][2] + track_transpose[best]; -				*ptr++ = track_data[best][3] * 127 / 100; // Scale velocity -				for (i = 0; i < 3; ++i) -					nextTime[i] -= bestTime; -			} -			track_data[best] += 4; -			track_len[best] -= 4; -			stage[best] = 0; -		} -	} - -	// Is this a looped song? If so, effect a loop by -	// using the S&M maybe_jump SysEx command. -	// FIXME: Jamieson630: The jump seems to be happening -	// too quickly! There should maybe be a pause after -	// the last Note Off? But I couldn't find one in the -	// MI1 Lookout music, where I was hearing problems. -	if (looped) { -		memcpy(ptr, "\x00\xf0\x13\x7d\x30\00", 6); ptr += 6; // maybe_jump -		memcpy(ptr, "\x00\x00", 2); ptr += 2;            // cmd -> 0 means always jump -		memcpy(ptr, "\x00\x00\x00\x00", 4); ptr += 4;    // track -> 0 (only track) -		memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4;    // beat -> 1 (first beat) -		memcpy(ptr, "\x00\x00\x00\x01", 4); ptr += 4;    // tick -> 1 -		memcpy(ptr, "\x00\xf7", 2); ptr += 2;            // SysEx end marker -	} - -	// Insert end of song META -	memcpy(ptr, "\x00\xff\x2f\x00\x00", 5); ptr += 5; - -	assert(ptr <= start_ptr + total_size); - -	// Rewrite MIDI header, this time with true size -	total_size = ptr - start_ptr; -	ptr = writeMIDIHeader(start_ptr, "GMD ", ppqn, total_size); -#endif -} -  static void convertADResource(ResourceManager *res, const GameSettings& game, ResId idx, byte *src_ptr, int size) {  	// We will ignore the PPQN in the original resource, because  	// it's invalid anyway. We use a constant PPQN of 480.  | 
