diff options
34 files changed, 3728 insertions, 473 deletions
@@ -769,7 +769,7 @@ site, please see the section on reporting bugs.        original game.    The Legend of Kyrandia: -    - No music or sound effects in the Amiga and Macintosh floppy versions. +    - No music or sound effects in the Macintosh floppy versions.      - Macintosh CD is using included DOS music and sound effects.      - PC-9821 version lacks support for sound effects. diff --git a/common/scummsys.h b/common/scummsys.h index a9c5fb3266..dc87f7103a 100644 --- a/common/scummsys.h +++ b/common/scummsys.h @@ -44,16 +44,6 @@  		#ifdef _MSC_VER  		#pragma once -		#pragma warning( disable : 4068 ) // turn off "unknown pragma" warning -		#pragma	warning( disable : 4103 ) // turn off "alignement changed after including header" warning. We use pack-start.h file -		#pragma warning( disable : 4244 ) // turn off "conversion type" warning -		#pragma warning( disable : 4250 ) // turn off "inherits via dominance" warning -		#pragma warning( disable : 4351 ) // turn off "new behavior ... will be default initialized" warning -		#pragma warning( disable : 4355 ) // turn off "base member init" warning -		#pragma warning( disable : 4510 ) // turn off "default constructor could not be generated" -		#pragma warning( disable : 4610 ) // turn off "struct can never be instantiated - user defined constructor required" -		#pragma warning( disable : 4701 ) // turn off "potentially uninitialized variables" warning -		#pragma warning( disable : 4800 ) // turn off "forcing value to bool 'true' or 'false' (performance warning)"  		// vsnprintf is already defined in Visual Studio 2008  		#if (_MSC_VER < 1500) diff --git a/dists/msvc9/ScummVM_Global.vsprops b/dists/msvc9/ScummVM_Global.vsprops index afcfab102b..2fe13f5cc4 100644 --- a/dists/msvc9/ScummVM_Global.vsprops +++ b/dists/msvc9/ScummVM_Global.vsprops @@ -8,14 +8,14 @@  	>  	<Tool  		Name="VCCLCompilerTool" -		DisableSpecificWarnings="4068;4100;4103;4121;4127;4189;4201;4221;4244;4250;4310;4351;4355;4510;4511;4512;4610;4701;4702;4706;4800;4996"  		AdditionalIncludeDirectories="../..;../../engines" -		PreprocessorDefinitions="USE_NASM;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_AGOS2;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LOL;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_IHNM;ENABLE_SAGA2;ENABLE_SCI;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL;ENABLE_TUCKER;ENABLE_GROOVIE" +		PreprocessorDefinitions="USE_NASM;USE_ZLIB;ENABLE_AGOS2;ENABLE_PN;ENABLE_KYRA;ENABLE_LOL;ENABLE_SCUMM;ENABLE_SCUMM_7_8;ENABLE_HE"  		ExceptionHandling="0"  		RuntimeTypeInfo="false"  		WarningLevel="4"  		WarnAsError="true"  		CompileAs="0" +		DisableSpecificWarnings="4068;4100;4103;4121;4127;4189;4201;4221;4244;4250;4310;4351;4355;4510;4511;4512;4610;4701;4702;4706;4800;4996"  	/>  	<Tool  		Name="VCLibrarianTool" diff --git a/dists/msvc9/kyra.vcproj b/dists/msvc9/kyra.vcproj index 98b2609718..053007fae4 100644 --- a/dists/msvc9/kyra.vcproj +++ b/dists/msvc9/kyra.vcproj @@ -100,6 +100,7 @@  		<File RelativePath="..\..\engines\kyra\sound.cpp" />  		<File RelativePath="..\..\engines\kyra\sound.h" />  		<File RelativePath="..\..\engines\kyra\sound_adlib.cpp" /> +		<File RelativePath="..\..\engines\kyra\sound_amiga.cpp" />  		<File RelativePath="..\..\engines\kyra\sound_digital.cpp" />  		<File RelativePath="..\..\engines\kyra\sound_intern.h" />  		<File RelativePath="..\..\engines\kyra\sound_lok.cpp" /> diff --git a/dists/msvc9/scumm.vcproj b/dists/msvc9/scumm.vcproj index c2c116ef8b..e77ab97292 100644 --- a/dists/msvc9/scumm.vcproj +++ b/dists/msvc9/scumm.vcproj @@ -152,6 +152,8 @@  		<File RelativePath="..\..\engines\scumm\player_v2cms.cpp" />  		<File RelativePath="..\..\engines\scumm\player_v3a.cpp" />  		<File RelativePath="..\..\engines\scumm\player_v3a.h" /> +		<File RelativePath="..\..\engines\scumm\player_v4a.cpp" /> +		<File RelativePath="..\..\engines\scumm\player_v4a.h" />  		<File RelativePath="..\..\engines\scumm\resource.cpp" />  		<File RelativePath="..\..\engines\scumm\resource.h" />  		<File RelativePath="..\..\engines\scumm\resource_v2.cpp" /> diff --git a/dists/msvc9/scummvm.vcproj b/dists/msvc9/scummvm.vcproj index 51c532a195..93e4d851dd 100644 --- a/dists/msvc9/scummvm.vcproj +++ b/dists/msvc9/scummvm.vcproj @@ -765,6 +765,14 @@  					>  				</File>  				<File +					RelativePath="..\..\sound\mods\maxtrax.cpp" +					> +				</File> +				<File +					RelativePath="..\..\sound\mods\maxtrax.h" +					> +				</File> +				<File  					RelativePath="..\..\sound\mods\module.cpp"  					>  				</File> @@ -804,6 +812,14 @@  					RelativePath="..\..\sound\mods\soundfx.h"  					>  				</File> +				<File +					RelativePath="..\..\sound\mods\tfmx.cpp" +					> +				</File> +				<File +					RelativePath="..\..\sound\mods\tfmx.h" +					> +				</File>  			</Filter>  		</Filter>  		<Filter diff --git a/engines/gob/inter_playtoons.cpp b/engines/gob/inter_playtoons.cpp deleted file mode 100644 index e224f29734..0000000000 --- a/engines/gob/inter_playtoons.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* 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. - * - * $URL$ - * $Id$ - * - */ - -#include "common/endian.h" - -#include "gob/gob.h" -#include "gob/inter.h" -#include "gob/helper.h" -#include "gob/global.h" -#include "gob/util.h" -#include "gob/dataio.h" -#include "gob/draw.h" -#include "gob/game.h" -#include "gob/script.h" -#include "gob/palanim.h" -#include "gob/video.h" -#include "gob/videoplayer.h" -#include "gob/save/saveload.h" -#include "gob/sound/sound.h" - -namespace Gob { - -#define OPCODEVER Inter_Playtoons -#define OPCODEDRAW(i, x)  _opcodesDraw[i]._OPCODEDRAW(OPCODEVER, x) -#define OPCODEFUNC(i, x)  _opcodesFunc[i]._OPCODEFUNC(OPCODEVER, x) -#define OPCODEGOB(i, x)   _opcodesGob[i]._OPCODEGOB(OPCODEVER, x) - -Inter_Playtoons::Inter_Playtoons(GobEngine *vm) : Inter_v6(vm) { -} - -void Inter_Playtoons::setupOpcodesDraw() { -	Inter_v6::setupOpcodesDraw(); - -// In the code, the Draw codes 0x00 to 0x06 and 0x13 are replaced by an engrish -// error message. As it's useless, they are simply cleared. -	CLEAROPCODEDRAW(0x00); -	CLEAROPCODEDRAW(0x01); -	CLEAROPCODEDRAW(0x02); -	CLEAROPCODEDRAW(0x03); -	CLEAROPCODEDRAW(0x04); -	CLEAROPCODEDRAW(0x05); -	CLEAROPCODEDRAW(0x06); -	CLEAROPCODEDRAW(0x13); - -	CLEAROPCODEDRAW(0x21); -	CLEAROPCODEDRAW(0x22); -	CLEAROPCODEDRAW(0x24); - -	OPCODEDRAW(0x20, oPlaytoons_CD_20_23); -	OPCODEDRAW(0x23, oPlaytoons_CD_20_23); -	OPCODEDRAW(0x25, oPlaytoons_CD_25); -	OPCODEDRAW(0x85, oPlaytoons_openItk); -} - -void Inter_Playtoons::setupOpcodesFunc() { -	Inter_v6::setupOpcodesFunc(); - -	OPCODEFUNC(0x3F, oPlaytoons_checkData); -} - -void Inter_Playtoons::setupOpcodesGob() { -} - -bool Inter_Playtoons::oPlaytoons_checkData(OpFuncParams ¶ms) { -	int16 handle; -	int16 varOff; -	int32 size; -	SaveLoad::SaveMode mode; - -	_vm->_game->_script->evalExpr(0); -	varOff = _vm->_game->_script->readVarIndex(); - -	size = -1; -	handle = 1; - -	char *file = _vm->_game->_script->getResultStr(); - -	// WORKAROUND: In Playtoons games, some files are read on CD (and only on CD).  -	// In this case, "@:\" is replaced by the CD drive letter. -	// As the files are copied on the HDD, those characters are skipped.  -	if (strncmp(file, "@:\\", 3) == 0) { -		debugC(2, kDebugFileIO, "File check: \"%s\" instead of \"%s\"", file + 3, file); -		file += 3; -	} - -	mode = _vm->_saveLoad->getSaveMode(file); -	if (mode == SaveLoad::kSaveModeNone) { - -		if (_vm->_dataIO->existData(file)) -			size = _vm->_dataIO->getDataSize(file); -		else -			warning("File \"%s\" not found", file); - -	} else if (mode == SaveLoad::kSaveModeSave) -		size = _vm->_saveLoad->getSize(file); -	else if (mode == SaveLoad::kSaveModeExists) -		size = 23; - -	if (size == -1) -		handle = -1; - -	debugC(2, kDebugFileIO, "Requested size of file \"%s\": %d", -			file, size); - -	WRITE_VAR_OFFSET(varOff, handle); -	WRITE_VAR(16, (uint32) size); - -	return false; -} - -void Inter_Playtoons::oPlaytoons_CD_20_23() { -	_vm->_game->_script->evalExpr(0); -} - -void Inter_Playtoons::oPlaytoons_CD_25() { -	_vm->_game->_script->readVarIndex(); -	_vm->_game->_script->readVarIndex(); -} - -void Inter_Playtoons::oPlaytoons_openItk() { -	char fileName[128]; -	char *backSlash; - -	_vm->_game->_script->evalExpr(0); -	strncpy0(fileName, _vm->_game->_script->getResultStr(), 124); - -	if (!strchr(fileName, '.')) -		strcat(fileName, ".ITK"); - -	// Workaround for Bambou : In the script, the path is hardcoded (!!) -	if ((backSlash = strrchr(fileName, '\\'))) { -		debugC(2, kDebugFileIO, "Opening ITK file \"%s\" instead of \"%s\"", backSlash + 1, fileName); -		_vm->_dataIO->openDataFile(backSlash + 1, true); -	} else -		_vm->_dataIO->openDataFile(fileName, true); -	// All the other checks are meant to verify (if not found at the first try) -	// if the file is present on the CD or not. As everything is supposed to  -	// be copied, those checks are skipped -} - -} // End of namespace Gob diff --git a/engines/kyra/kyra_lok.cpp b/engines/kyra/kyra_lok.cpp index 36f134d9e4..fc19b2fb65 100644 --- a/engines/kyra/kyra_lok.cpp +++ b/engines/kyra/kyra_lok.cpp @@ -183,8 +183,13 @@ Common::Error KyraEngine_LoK::init() {  	_sound->setSoundList(&_soundData[kMusicIntro]); -	_trackMap = _dosTrackMap; -	_trackMapSize = _dosTrackMapSize; +	if (_flags.platform == Common::kPlatformAmiga) { +		_trackMap = _amigaTrackMap; +		_trackMapSize = _amigaTrackMapSize; +	} else { +		_trackMap = _dosTrackMap; +		_trackMapSize = _dosTrackMapSize; +	}  	if (!_sound->init())  		error("Couldn't init sound"); diff --git a/engines/kyra/kyra_lok.h b/engines/kyra/kyra_lok.h index e7cc92c5e1..5d20cb0652 100644 --- a/engines/kyra/kyra_lok.h +++ b/engines/kyra/kyra_lok.h @@ -507,6 +507,9 @@ protected:  	static const int8 _dosTrackMap[];  	static const int _dosTrackMapSize; +	static const int8 _amigaTrackMap[]; +	static const int _amigaTrackMapSize; +  	// TODO: get rid of all variables having pointers to the static resources if possible  	// i.e. let them directly use the _staticres functions  	void initStaticResource(); diff --git a/engines/kyra/kyra_v1.cpp b/engines/kyra/kyra_v1.cpp index d79d9a8f32..9ee6935384 100644 --- a/engines/kyra/kyra_v1.cpp +++ b/engines/kyra/kyra_v1.cpp @@ -50,7 +50,10 @@ KyraEngine_v1::KyraEngine_v1(OSystem *system, const GameFlags &flags)  	_emc = 0;  	_debugger = 0; -	_gameSpeed = 60; +	if (_flags.platform == Common::kPlatformAmiga) +		_gameSpeed = 50; +	else +		_gameSpeed = 60;  	_tickLength = (uint8)(1000.0 / _gameSpeed);  	_trackMap = 0; @@ -114,6 +117,8 @@ Common::Error KyraEngine_v1::init() {  				_sound = new SoundPC98(this, _mixer);  			else  				_sound = new SoundTownsPC98_v2(this, _mixer); +		} else if (_flags.platform == Common::kPlatformAmiga) { +			_sound = new SoundAmiga(this, _mixer);  		} else if (midiDriver == MD_ADLIB) {  			_sound = new SoundAdlibPC(this, _mixer);  		} else { diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk index 7b0c0dfb68..b484fa345f 100644 --- a/engines/kyra/module.mk +++ b/engines/kyra/module.mk @@ -50,6 +50,7 @@ MODULE_OBJS := \  	sequences_hof.o \  	sequences_mr.o \  	sound_adlib.o \ +	sound_amiga.o \  	sound_digital.o \  	sound_midi.o \  	sound_pcspk.o \ diff --git a/engines/kyra/seqplayer.cpp b/engines/kyra/seqplayer.cpp index 38d1b90e7a..4086d06c00 100644 --- a/engines/kyra/seqplayer.cpp +++ b/engines/kyra/seqplayer.cpp @@ -414,8 +414,6 @@ void SeqPlayer::s1_fillRect() {  void SeqPlayer::s1_playEffect() {  	uint8 track = *_seqData++; -	if (_vm->gameFlags().platform == Common::kPlatformAmiga) -		return;  	_vm->delay(3 * _vm->tickLength());  	_sound->playSoundEffect(track);  } @@ -423,9 +421,6 @@ void SeqPlayer::s1_playEffect() {  void SeqPlayer::s1_playTrack() {  	uint8 msg = *_seqData++; -	if (_vm->gameFlags().platform == Common::kPlatformAmiga) -		return; -  	if (msg == 1) {  		_sound->beginFadeOut();  	} else { diff --git a/engines/kyra/sequences_lok.cpp b/engines/kyra/sequences_lok.cpp index d2ef351767..26c09bcf43 100644 --- a/engines/kyra/sequences_lok.cpp +++ b/engines/kyra/sequences_lok.cpp @@ -110,7 +110,7 @@ void KyraEngine_LoK::seq_intro() {  	_seq->setCopyViewOffs(true);  	_screen->setFont(Screen::FID_8_FNT); -	if (_flags.platform != Common::kPlatformFMTowns && _flags.platform != Common::kPlatformPC98) +	if (_flags.platform != Common::kPlatformFMTowns && _flags.platform != Common::kPlatformPC98 && _flags.platform != Common::kPlatformAmiga)  		snd_playTheme(0, 2);  	_text->setTalkCoords(144); @@ -994,6 +994,14 @@ int KyraEngine_LoK::seq_playEnd() {  		snd_playWanderScoreViaMap(50, 1);  		setupPanPages(); +		if (_flags.platform == Common::kPlatformAmiga) { +			_sound->loadSoundFile(kMusicFinale); + +			// The original started song 0 directly here. Since our player +			// uses 0, 1 for stop and fade we start song 0 with 2 +			_sound->playTrack(2); +		} +  		_finalA = createWSAMovie();  		assert(_finalA);  		_finalA->open("finala.wsa", 1, 0); diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp index 91945d91ee..781516282e 100644 --- a/engines/kyra/sound.cpp +++ b/engines/kyra/sound.cpp @@ -215,6 +215,13 @@ void KyraEngine_v1::snd_playWanderScoreViaMap(int command, int restart) {  				_sound->playTrack(command);  			}  		} +	} else if (_flags.platform == Common::kPlatformAmiga) { +		if (_curMusicTheme != 1) +			snd_playTheme(1, -1); + +		assert(command < _trackMapSize); +		if (_trackMap[_lastMusicCommand] != _trackMap[command]) +			_sound->playTrack(_trackMap[command]);  	}  	_lastMusicCommand = command; diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h index 263cd586f7..2f24a264f1 100644 --- a/engines/kyra/sound.h +++ b/engines/kyra/sound.h @@ -55,7 +55,8 @@ public:  		kMidiGM,  		kTowns,  		kPC98, -		kPCSpkr +		kPCSpkr, +		kAmiga  	};  	virtual kType getMusicType() const = 0; diff --git a/engines/kyra/sound_amiga.cpp b/engines/kyra/sound_amiga.cpp new file mode 100644 index 0000000000..0b64e67525 --- /dev/null +++ b/engines/kyra/sound_amiga.cpp @@ -0,0 +1,239 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/system.h" +#include "common/mutex.h" +#include "kyra/resource.h" +#include "kyra/sound_intern.h" + +#include "sound/mixer.h" +#include "sound/mods/maxtrax.h" +#include "sound/audiostream.h" + +namespace { + +FORCEINLINE uint8 sfxTableGetNote(const byte* address) { +	return (uint8)address[0]; +} +FORCEINLINE uint8 sfxTableGetPatch(const byte* address) { +	return (uint8)address[1]; +} +FORCEINLINE uint16 sfxTableGetDuration(const byte* address) { +	return READ_BE_UINT16(&address[4]); +} +FORCEINLINE int8 sfxTableGetVolume(const byte* address) { +	return (int8)address[6]; +} +FORCEINLINE int8 sfxTableGetPan(const byte* address) { +	return (int8)address[7]; +} + +} // end of namespace + +namespace Kyra { + +SoundAmiga::SoundAmiga(KyraEngine_v1 *vm, Audio::Mixer *mixer) +	: Sound(vm, mixer), +	  _driver(0), +	  _musicHandle(), +	  _fileLoaded(kFileNone), +	  _tableSfxIntro(), +	  _tableSfxGame() { +} + +SoundAmiga::~SoundAmiga() { +	_mixer->stopHandle(_musicHandle); +	delete _driver; +} + +extern const byte LoKAmigaSfxIntro[]; +extern const byte LoKAmigaSfxGame[]; + +bool SoundAmiga::init() { +	_driver = new Audio::MaxTrax(_mixer->getOutputRate(), true); +	_tableSfxIntro = LoKAmigaSfxIntro; +	_tableSfxGame = LoKAmigaSfxGame; + +	return _driver != 0 && _tableSfxIntro && _tableSfxGame; +} + +void SoundAmiga::loadSoundFile(uint file) { +	debugC(5, kDebugLevelSound, "SoundAmiga::loadSoundFile(%d)", file); + +	static const char *const tableFilenames[3][2] = { +		{ "introscr.mx",  "introinst.mx" }, +		{ "kyramusic.mx", 0 }, +		{ "finalescr.mx", "introinst.mx" } +	}; +	assert(file < ARRAYSIZE(tableFilenames)); +	if (_fileLoaded == (FileType)file) +		return; +	const char* scoreName = tableFilenames[file][0]; +	const char* sampleName = tableFilenames[file][1]; +	bool loaded = false; + +	Common::SeekableReadStream *scoreIn = _vm->resource()->createReadStream(scoreName); +	if (sampleName) { +		Common::SeekableReadStream *sampleIn = _vm->resource()->createReadStream(sampleName); +		if (scoreIn && sampleIn) { +			_fileLoaded = kFileNone; +			loaded = _driver->load(*scoreIn, true, false); +			loaded = loaded && _driver->load(*sampleIn, false, true); +		} else +			warning("SoundAmiga: missing atleast one of those music files: %s, %s", scoreName, sampleName); +		delete sampleIn; +	} else { +		if (scoreIn) { +			_fileLoaded = kFileNone; +			loaded = _driver->load(*scoreIn); +		} else +			warning("SoundAmiga: missing music file: %s", scoreName); +	} +	delete scoreIn; + +	if (loaded) +		_fileLoaded = (FileType)file; +} + +void SoundAmiga::playTrack(uint8 track) { +	debugC(5, kDebugLevelSound, "SoundAmiga::playTrack(%d)", track); + +	static const byte tempoIntro[] = { 0x46, 0x55, 0x3C, 0x41 }; +	static const byte tempoFinal[] = { 0x78, 0x50 }; +	static const byte tempoIngame[] = { +		0x64, 0x64, 0x64, 0x64, 0x64, 0x73, 0x4B, 0x64, +		0x64, 0x64, 0x55, 0x9C, 0x6E, 0x91, 0x78, 0x84, +		0x32, 0x64, 0x64, 0x6E, 0x3C, 0xD8, 0xAF +	}; +	static const byte loopIngame[] = { +		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +		0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x01, 0x01, +		0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00 +	}; + +	int score = -1; +	bool loop = false; +	byte volume = 0x40; +	byte tempo = 0; + + +	switch (_fileLoaded) { +	case kFileIntro: +		if (track >= 2 && track < ARRAYSIZE(tempoIntro) + 2) { +			score = track - 2; +			tempo = tempoIntro[score]; +		} +		break; + +	case kFileGame: +		if (track >= 11 && track < ARRAYSIZE(tempoIngame) + 11) { +			score = track - 11; +			loop = loopIngame[score] != 0; +			tempo = tempoIngame[score]; +		} +		break; + +	case kFileFinal: +		// score 0 gets started immediately after loading the music-files with different tempo. +		// we need to define a track-value for the fake call of this function +		if (track >= 2 && track < ARRAYSIZE(tempoFinal) + 2) { +			score = track - 2; +			loop = true; +			tempo = tempoFinal[score]; +		} +		break; + +	default: +		return; +	} + +	if (score >= 0) { +		if (_musicEnabled && _driver->playSong(score, loop)) { +			_driver->setVolume(volume); +			_driver->setTempo(tempo << 4); +			if (!_mixer->isSoundHandleActive(_musicHandle)) +				_mixer->playInputStream(Audio::Mixer::kPlainSoundType, &_musicHandle, _driver, -1, Audio::Mixer::kMaxChannelVolume, 0, false); +		} +	} else if (track == 0) +		_driver->stopMusic(); +	else if (track == 1) +		beginFadeOut(); +} + +void SoundAmiga::haltTrack() { +	debugC(5, kDebugLevelSound, "SoundAmiga::haltTrack()"); +	_driver->stopMusic(); +} + +void SoundAmiga::beginFadeOut() { +	debugC(5, kDebugLevelSound, "SoundAmiga::beginFadeOut()"); +	for (int i = 0x3F; i >= 0; --i) { +		_driver->setVolume((byte)i); +		_vm->delay(_vm->tickLength()); +	} + +	_driver->stopMusic(); +	_vm->delay(_vm->tickLength()); +	_driver->setVolume(0x40); +} + +void SoundAmiga::playSoundEffect(uint8 track) { +	debugC(5, kDebugLevelSound, "SoundAmiga::playSoundEffect(%d)", track); +	const byte* tableEntry = 0; +	bool pan = false; + +	switch (_fileLoaded) { +	case kFileFinal: +	case kFileIntro: +		// We only allow playing of sound effects, which are included in the table. +		if (track < 40) { +			tableEntry = &_tableSfxIntro[track * 8]; +			pan = (sfxTableGetPan(tableEntry) != 0); +		} +		break; + +	case kFileGame: +		if (0x61 <= track && track <= 0x63) +			playTrack(track - 0x4F); + +		assert(track < 120); +		if (sfxTableGetNote(&_tableSfxGame[track * 8])) {  +			tableEntry = &_tableSfxGame[track * 8]; +			pan = (sfxTableGetPan(tableEntry) != 0) && (sfxTableGetPan(tableEntry) != 2); +		} +		break; +	default: +		; +	} + +	if (_sfxEnabled && tableEntry) { +		const bool success = _driver->playNote(sfxTableGetNote(tableEntry), sfxTableGetPatch(tableEntry), sfxTableGetDuration(tableEntry), sfxTableGetVolume(tableEntry), pan); +		if (success && !_mixer->isSoundHandleActive(_musicHandle)) +			_mixer->playInputStream(Audio::Mixer::kPlainSoundType, &_musicHandle, _driver, -1, Audio::Mixer::kMaxChannelVolume, 0, false); +	} +} + +} // end of namespace Kyra + diff --git a/engines/kyra/sound_intern.h b/engines/kyra/sound_intern.h index 975672b76a..b85b8a2e30 100644 --- a/engines/kyra/sound_intern.h +++ b/engines/kyra/sound_intern.h @@ -37,6 +37,7 @@  namespace Audio {  class PCSpeaker; +class MaxTrax;  } // end of namespace Audio  namespace Kyra { @@ -284,7 +285,34 @@ private:  	static const uint8 _noteTable2[];  }; +class SoundAmiga : public Sound { +public: +	SoundAmiga(KyraEngine_v1 *vm, Audio::Mixer *mixer); +	~SoundAmiga(); + +	virtual kType getMusicType() const { return kAmiga; } //FIXME + +	bool init(); + +	void process() {} +	void loadSoundFile(uint file); +	void loadSoundFile(Common::String) {} + +	void playTrack(uint8 track); +	void haltTrack(); +	void beginFadeOut(); + +	int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, bool isSfx) { return -1; } +	void playSoundEffect(uint8); + +protected: +	Audio::MaxTrax *_driver; +	Audio::SoundHandle _musicHandle; +	enum FileType { kFileNone = -1, kFileIntro = 0, kFileGame = 1, kFileFinal = 2 } _fileLoaded; +	const byte *_tableSfxIntro; +	const byte *_tableSfxGame; +}; +  } // end of namespace Kyra  #endif - diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp index 51288f31df..d4882a12af 100644 --- a/engines/kyra/staticres.cpp +++ b/engines/kyra/staticres.cpp @@ -2356,6 +2356,18 @@ const int8 KyraEngine_LoK::_dosTrackMap[] = {  const int KyraEngine_LoK::_dosTrackMapSize = ARRAYSIZE(KyraEngine_LoK::_dosTrackMap); +const int8 KyraEngine_LoK::_amigaTrackMap[] = { +	 0,  1, 32, 26, 31, 30, 33, 33, +	32, 17, 27, 32, 25, 29, 25, 24, +	23, 26, 26, 30, 28, 21, 21, 15, +	 3, 15, 23, 25, 33, 21, 30, 22, +	15,  3, 33, 11, 12, 13, 14, 22, +	22, 22,  3,  3,  3, 23,  3,  3, +	23,  3,  3,  3,  3,  3,  3, 33 +}; + +const int KyraEngine_LoK::_amigaTrackMapSize = ARRAYSIZE(KyraEngine_LoK::_amigaTrackMap); +  // kyra engine v2 static data  const int GUI_v2::_sliderBarsPosition[] = { @@ -3367,4 +3379,172 @@ const int LoLEngine::_outroMonsterScaleTableY[] = {  #endif // ENABLE_LOL +// TODO: fileoffset = 0x32D5C, len = 40 * 8 +extern const byte LoKAmigaSfxIntro[] = { +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x6E, 0x00, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x6E, 0x00, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x6E, 0x00, +	0x3C, 0x13, 0x00, 0x00, 0x1B, 0x91, 0x6E, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x16, 0x00, 0x00, 0x26, 0x77, 0x6E, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x17, 0x00, 0x00, 0x11, 0x98, 0x6E, 0x00, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x6E, 0x00, +	0x3C, 0x18, 0x00, 0x00, 0x22, 0xD1, 0x6E, 0x00, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x6E, 0x00, +	0x45, 0x03, 0x00, 0x00, 0x02, 0x24, 0x6E, 0x00, +	0x3C, 0x16, 0x00, 0x00, 0x26, 0x77, 0x6E, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +// TODO: fileoffset = 0x2C55E, len = 120 * 8 +extern const byte LoKAmigaSfxGame[] = { +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x13, 0x00, 0x00, 0x01, 0x56, 0x78, 0x02, +	0x3C, 0x14, 0x00, 0x00, 0x27, 0x2C, 0x78, 0x02, +	0x3C, 0x15, 0x00, 0x00, 0x1B, 0x91, 0x78, 0x02, +	0x3C, 0x16, 0x00, 0x00, 0x1E, 0x97, 0x78, 0x02, +	0x3C, 0x17, 0x00, 0x00, 0x12, 0x2B, 0x78, 0x02, +	0x3C, 0x16, 0x00, 0x00, 0x1E, 0x97, 0x78, 0x02, +	0x45, 0x03, 0x00, 0x00, 0x02, 0x24, 0x78, 0x02, +	0x3C, 0x16, 0x00, 0x00, 0x1E, 0x97, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x78, 0x02, +	0x2C, 0x04, 0x00, 0x00, 0x09, 0x10, 0x78, 0x02, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x78, 0x02, +	0x3C, 0x1A, 0x00, 0x00, 0x3A, 0xEB, 0x78, 0x02, +	0x25, 0x1B, 0x00, 0x00, 0x13, 0x8B, 0x78, 0x02, +	0x18, 0x03, 0x00, 0x00, 0x0F, 0x52, 0x78, 0x02, +	0x3E, 0x1C, 0x00, 0x00, 0x06, 0x22, 0x78, 0x02, +	0x3B, 0x1C, 0x00, 0x00, 0x07, 0x54, 0x78, 0x02, +	0x16, 0x03, 0x00, 0x00, 0x20, 0x6F, 0x78, 0x02, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x78, 0x02, +	0x3C, 0x1D, 0x00, 0x00, 0x09, 0xEA, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x14, 0x00, 0x00, 0x27, 0x2C, 0x78, 0x02, +	0x3C, 0x1E, 0x00, 0x00, 0x03, 0x6E, 0x78, 0x02, +	0x3C, 0x17, 0x00, 0x00, 0x12, 0x2B, 0x78, 0x02, +	0x4E, 0x0B, 0x00, 0x00, 0x09, 0x91, 0x78, 0x02, +	0x47, 0x1B, 0x00, 0x00, 0x02, 0xBC, 0x78, 0x02, +	0x4C, 0x1B, 0x00, 0x00, 0x02, 0x11, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x13, 0x00, 0x00, 0x01, 0x56, 0x78, 0x02, +	0x3C, 0x13, 0x00, 0x00, 0x01, 0x56, 0x78, 0x02, +	0x3C, 0x1F, 0x00, 0x00, 0x0E, 0x9E, 0x78, 0x02, +	0x3C, 0x20, 0x00, 0x00, 0x01, 0x0C, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x78, 0x02, +	0x3C, 0x21, 0x00, 0x00, 0x0F, 0x7C, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x2A, 0x0B, 0x00, 0x00, 0x4C, 0x47, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x1B, 0x00, 0x00, 0x05, 0x28, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x2C, 0x04, 0x00, 0x00, 0x09, 0x10, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x22, 0x00, 0x00, 0x0A, 0xEE, 0x78, 0x02, +	0x3C, 0x16, 0x00, 0x00, 0x1E, 0x97, 0x78, 0x02, +	0x3C, 0x15, 0x00, 0x00, 0x1B, 0x91, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x14, 0x00, 0x00, 0x27, 0x2C, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x22, 0x00, 0x00, 0x0A, 0xEE, 0x78, 0x02, +	0x3C, 0x14, 0x00, 0x00, 0x27, 0x2C, 0x78, 0x02, +	0x32, 0x23, 0x00, 0x00, 0x14, 0x19, 0x9C, 0x02, +	0x3C, 0x19, 0x00, 0x00, 0x17, 0x1C, 0x78, 0x02, +	0x3C, 0x14, 0x00, 0x00, 0x27, 0x2C, 0x78, 0x02, +	0x3E, 0x1C, 0x00, 0x00, 0x06, 0x22, 0x78, 0x02, +	0x43, 0x13, 0x00, 0x00, 0x02, 0x01, 0x78, 0x02, +	0x3C, 0x24, 0x00, 0x00, 0x12, 0x43, 0x5A, 0x02, +	0x3E, 0x20, 0x00, 0x00, 0x00, 0xEE, 0x78, 0x02, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x78, 0x02, +	0x29, 0x04, 0x00, 0x00, 0x19, 0xEA, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x20, 0x00, 0x00, 0x01, 0x0C, 0x78, 0x02, +	0x3C, 0x25, 0x00, 0x00, 0x30, 0xB6, 0x78, 0x02, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x16, 0x00, 0x00, 0x1E, 0x97, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x1A, 0x00, 0x00, 0x3A, 0xEB, 0x78, 0x02, +	0x1B, 0x04, 0x00, 0x00, 0x39, 0xF3, 0x78, 0x02, +	0x30, 0x23, 0x00, 0x00, 0x16, 0x99, 0x50, 0x02, +	0x3C, 0x15, 0x00, 0x00, 0x1B, 0x91, 0x78, 0x02, +	0x29, 0x06, 0x00, 0x00, 0x19, 0xEA, 0x50, 0x02, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x78, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x1A, 0x00, 0x00, 0x3A, 0xEB, 0x78, 0x02, +	0x3C, 0x19, 0x00, 0x00, 0x25, 0x2C, 0x78, 0x02, +	0x3C, 0x26, 0x00, 0x00, 0x07, 0x13, 0x78, 0x02, +	0x3C, 0x26, 0x00, 0x00, 0x07, 0x13, 0x78, 0x02, +	0x3C, 0x14, 0x00, 0x00, 0x27, 0x2C, 0x78, 0x02, +	0x30, 0x23, 0x00, 0x00, 0x16, 0x99, 0x50, 0x02, +	0x30, 0x23, 0x00, 0x00, 0x16, 0x99, 0x50, 0x02, +	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +	0x3C, 0x13, 0x00, 0x00, 0x01, 0x56, 0x78, 0x02 +}; +  } // End of namespace Kyra diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk index 58d8db91fc..51fbecdb0d 100644 --- a/engines/scumm/module.mk +++ b/engines/scumm/module.mk @@ -40,6 +40,7 @@ MODULE_OBJS := \  	player_v2a.o \  	player_v2cms.o \  	player_v3a.o \ +	player_v4a.o \  	resource_v2.o \  	resource_v3.o \  	resource_v4.o \ diff --git a/engines/scumm/music.h b/engines/scumm/music.h index c6555318a9..2fd7c50bce 100644 --- a/engines/scumm/music.h +++ b/engines/scumm/music.h @@ -81,12 +81,6 @@ public:  	 * @return the music timer  	 */  	virtual int  getMusicTimer() const { return 0; } - -	/** -	 * Terminate the music engine. Called just before the music engine -	 * is deleted. -	 */ -	virtual void terminate() {}  };  } // End of namespace Scumm diff --git a/engines/scumm/palette.cpp b/engines/scumm/palette.cpp index c356e7a06c..1b531f6bab 100644 --- a/engines/scumm/palette.cpp +++ b/engines/scumm/palette.cpp @@ -34,230 +34,155 @@  namespace Scumm {  void ScummEngine::resetPalette() { +	static const byte tableC64Palette[] = { +		0x00, 0x00, 0x00, 	0xFD, 0xFE, 0xFC, 	0xBE, 0x1A, 0x24, 	0x30, 0xE6, 0xC6, +		0xB4, 0x1A, 0xE2, 	0x1F, 0xD2, 0x1E, 	0x21, 0x1B, 0xAE, 	0xDF, 0xF6, 0x0A, +		0xB8, 0x41, 0x04, 	0x6A, 0x33, 0x04, 	0xFE, 0x4A, 0x57, 	0x42, 0x45, 0x40, +		0x70, 0x74, 0x6F, 	0x59, 0xFE, 0x59, 	0x5F, 0x53, 0xFE, 	0xA4, 0xA7, 0xA2, + +		// Use 17 color table for v1 games to allow correct color for inventory and +		// sentence line. Original games used some kind of dynamic color table +		// remapping between rooms. +		0xFF, 0x55, 0xFF +	}; + +	static const byte tableNESPalette[] = { +		/*    0x1D     */ +		0x00, 0x00, 0x00, 	0x00, 0x24, 0x92, 	0x00, 0x00, 0xDB, 	0x6D, 0x49, 0xDB, +		0x92, 0x00, 0x6D, 	0xB6, 0x00, 0x6D, 	0xB6, 0x24, 0x00, 	0x92, 0x49, 0x00, +		0x6D, 0x49, 0x00, 	0x24, 0x49, 0x00, 	0x00, 0x6D, 0x24, 	0x00, 0x92, 0x00, +		0x00, 0x49, 0x49, 	0x00, 0x00, 0x00, 	0x00, 0x00, 0x00, 	0x00, 0x00, 0x00, + +		0xB6, 0xB6, 0xB6, 	0x00, 0x6D, 0xDB, 	0x00, 0x49, 0xFF, 	0x92, 0x00, 0xFF, +		0xB6, 0x00, 0xFF, 	0xFF, 0x00, 0x92, 	0xFF, 0x00, 0x00, 	0xDB, 0x6D, 0x00, +		0x92, 0x6D, 0x00, 	0x24, 0x92, 0x00, 	0x00, 0x92, 0x00, 	0x00, 0xB6, 0x6D, +							/*    0x00     */ +		0x00, 0x92, 0x92, 	0x6D, 0x6D, 0x6D, 	0x00, 0x00, 0x00, 	0x00, 0x00, 0x00, + +		0xFF, 0xFF, 0xFF, 	0x6D, 0xB6, 0xFF, 	0x92, 0x92, 0xFF, 	0xDB, 0x6D, 0xFF, +		0xFF, 0x00, 0xFF, 	0xFF, 0x6D, 0xFF, 	0xFF, 0x92, 0x00, 	0xFF, 0xB6, 0x00, +		0xDB, 0xDB, 0x00, 	0x6D, 0xDB, 0x00, 	0x00, 0xFF, 0x00, 	0x49, 0xFF, 0xDB, +		0x00, 0xFF, 0xFF, 	0x49, 0x49, 0x49, 	0x00, 0x00, 0x00, 	0x00, 0x00, 0x00, + +		0xFF, 0xFF, 0xFF, 	0xB6, 0xDB, 0xFF, 	0xDB, 0xB6, 0xFF, 	0xFF, 0xB6, 0xFF, +		0xFF, 0x92, 0xFF, 	0xFF, 0xB6, 0xB6, 	0xFF, 0xDB, 0x92, 	0xFF, 0xFF, 0x49, +		0xFF, 0xFF, 0x6D, 	0xB6, 0xFF, 0x49, 	0x92, 0xFF, 0x6D, 	0x49, 0xFF, 0xDB, +		0x92, 0xDB, 0xFF, 	0x92, 0x92, 0x92, 	0x00, 0x00, 0x00, 	0x00, 0x00, 0x00 +	}; + +	static const byte tableAmigaPalette[] = { +		0x00, 0x00, 0x00, 	0x00, 0x00, 0xBB, 	0x00, 0xBB, 0x00, 	0x00, 0xBB, 0xBB, +		0xBB, 0x00, 0x00, 	0xBB, 0x00, 0xBB, 	0xBB, 0x77, 0x00, 	0xBB, 0xBB, 0xBB, +		0x77, 0x77, 0x77, 	0x77, 0x77, 0xFF, 	0x00, 0xFF, 0x00, 	0x00, 0xFF, 0xFF, +		0xFF, 0x88, 0x88, 	0xFF, 0x00, 0xFF, 	0xFF, 0xFF, 0x00, 	0xFF, 0xFF, 0xFF +	}; + +	static const byte tableAmigaMIPalette[] = { +		0x00, 0x00, 0x00, 	0x00, 0x00, 0xAA, 	0x00, 0x88, 0x22, 	0x00, 0x66, 0x77, +		0xBB, 0x66, 0x66, 	0xAA, 0x22, 0xAA, 	0x88, 0x55, 0x22, 	0x77, 0x77, 0x77, +		0x33, 0x33, 0x33, 	0x22, 0x55, 0xDD, 	0x22, 0xDD, 0x44, 	0x00, 0xCC, 0xFF, +		0xFF, 0x99, 0x99, 	0xFF, 0x55, 0xFF, 	0xFF, 0xFF, 0x77, 	0xFF, 0xFF, 0xFF +	}; + +	static const byte tableEGAPalette[] = { +		0x00, 0x00, 0x00, 	0x00, 0x00, 0xAA, 	0x00, 0xAA, 0x00, 	0x00, 0xAA, 0xAA, +		0xAA, 0x00, 0x00, 	0xAA, 0x00, 0xAA, 	0xAA, 0x55, 0x00, 	0xAA, 0xAA, 0xAA, +		0x55, 0x55, 0x55, 	0x55, 0x55, 0xFF, 	0x55, 0xFF, 0x55, 	0x55, 0xFF, 0xFF, +		0xFF, 0x55, 0x55, 	0xFF, 0x55, 0xFF, 	0xFF, 0xFF, 0x55, 	0xFF, 0xFF, 0xFF +	}; + +	static const byte tableV1Palette[] = { +		0x00, 0x00, 0x00, 	0xFF, 0xFF, 0xFF, 	0xAA, 0x00, 0x00, 	0x00, 0xAA, 0xAA, +		0xAA, 0x00, 0xAA, 	0x00, 0xAA, 0x00, 	0x00, 0x00, 0xAA, 	0xFF, 0xFF, 0x55, +		0xFF, 0x55, 0x55, 	0xAA, 0x55, 0x00, 	0xFF, 0x55, 0x55, 	0x55, 0x55, 0x55, +		0xAA, 0xAA, 0xAA, 	0x55, 0xFF, 0x55, 	0x55, 0x55, 0xFF, 	0x55, 0x55, 0x55, + +		0xFF, 0x55, 0xFF +	}; + +	static const byte tableCGAPalette[] = { +		0x00, 0x00, 0x00, 	0x00, 0xA8, 0xA8,	0xA8, 0x00, 0xA8, 	0xA8, 0xA8, 0xA8 +	}; + +	static const byte tableHercAPalette[] = { +		0x00, 0x00, 0x00, 	0xAE, 0x69, 0x38 +	}; + +	static const byte tableHercGPalette[] = { +		0x00, 0x00, 0x00, 	0x00, 0xFF, 0x00 +	}; +  	if (_game.version <= 1) {  		if (_game.platform == Common::kPlatformApple2GS) {  			// TODO: unique palette? -			setC64Palette(); +			setPaletteFromTable(tableC64Palette, sizeof(tableC64Palette) / 3);  		} else if (_game.platform == Common::kPlatformC64) { -			setC64Palette(); +			setPaletteFromTable(tableC64Palette, sizeof(tableC64Palette) / 3);  		} else if (_game.platform == Common::kPlatformNES) { -			setNESPalette(); +			setPaletteFromTable(tableNESPalette, sizeof(tableNESPalette) / 3);  		} else { -			setV1Palette(); +			setPaletteFromTable(tableV1Palette, sizeof(tableV1Palette) / 3); +			if (_game.id == GID_ZAK) +				setPalColor(15, 170, 170, 170);  		}  	} else if (_game.features & GF_16COLOR) { +		bool setupCursor = false; +  		switch (_renderMode) {  		case Common::kRenderEGA: -			setEGAPalette(); +			setPaletteFromTable(tableEGAPalette, sizeof(tableEGAPalette) / 3);  			break;  		case Common::kRenderAmiga: -			setAmigaPalette(); +			setPaletteFromTable(tableAmigaPalette, sizeof(tableAmigaPalette) / 3);  			break;  		case Common::kRenderCGA: -			setCGAPalette(); +			setPaletteFromTable(tableCGAPalette, sizeof(tableCGAPalette) / 3); +			setupCursor = true;  			break;  		case Common::kRenderHercA: +			setPaletteFromTable(tableHercAPalette, sizeof(tableHercAPalette) / 3); +			setupCursor = true; +			break; +  		case Common::kRenderHercG: -			setHercPalette(); +			setPaletteFromTable(tableHercGPalette, sizeof(tableHercGPalette) / 3); +			setupCursor = true;  			break;  		default:  			if ((_game.platform == Common::kPlatformAmiga) || (_game.platform == Common::kPlatformAtariST)) -				setAmigaPalette(); +				setPaletteFromTable(tableAmigaPalette, sizeof(tableAmigaPalette) / 3);  			else -				setEGAPalette(); +				setPaletteFromTable(tableEGAPalette, sizeof(tableEGAPalette) / 3); +		} +		if (setupCursor) { +			// Setup cursor palette +			setPalColor( 7, 170, 170, 170); +			setPalColor( 8,  85,  85,  85); +			setPalColor(15, 255, 255, 255);  		} -	} else -		setDirtyColors(0, 255); -} - -void ScummEngine::setC64Palette() { -	setPalColor( 0, 0x00, 0x00, 0x00); -	setPalColor( 1, 0xFD, 0xFE, 0xFC); -	setPalColor( 2, 0xBE, 0x1A, 0x24); -	setPalColor( 3, 0x30, 0xE6, 0xC6); -	setPalColor( 4, 0xB4, 0x1A, 0xE2); -	setPalColor( 5, 0x1F, 0xD2, 0x1E); -	setPalColor( 6, 0x21, 0x1B, 0xAE); -	setPalColor( 7, 0xDF, 0xF6, 0x0A); -	setPalColor( 8, 0xB8, 0x41, 0x04); -	setPalColor( 9, 0x6A, 0x33, 0x04); -	setPalColor(10, 0xFE, 0x4A, 0x57); -	setPalColor(11, 0x42, 0x45, 0x40); -	setPalColor(12, 0x70, 0x74, 0x6F); -	setPalColor(13, 0x59, 0xFE, 0x59); -	setPalColor(14, 0x5F, 0x53, 0xFE); -	setPalColor(15, 0xA4, 0xA7, 0xA2); - -	// Use 17 color table for v1 games to allow correct color for inventory and -	// sentence line. Original games used some kind of dynamic color table -	// remapping between rooms. -	setPalColor(16, 255,  85, 255); -} - -void ScummEngine::setNESPalette() { -	setPalColor(0x00,0x00,0x00,0x00); // 0x1D -	setPalColor(0x01,0x00,0x24,0x92); -	setPalColor(0x02,0x00,0x00,0xDB); -	setPalColor(0x03,0x6D,0x49,0xDB); -	setPalColor(0x04,0x92,0x00,0x6D); -	setPalColor(0x05,0xB6,0x00,0x6D); -	setPalColor(0x06,0xB6,0x24,0x00); -	setPalColor(0x07,0x92,0x49,0x00); -	setPalColor(0x08,0x6D,0x49,0x00); -	setPalColor(0x09,0x24,0x49,0x00); -	setPalColor(0x0A,0x00,0x6D,0x24); -	setPalColor(0x0B,0x00,0x92,0x00); -	setPalColor(0x0C,0x00,0x49,0x49); -	setPalColor(0x0D,0x00,0x00,0x00); -	setPalColor(0x0E,0x00,0x00,0x00); -	setPalColor(0x0F,0x00,0x00,0x00); - -	setPalColor(0x10,0xB6,0xB6,0xB6); -	setPalColor(0x11,0x00,0x6D,0xDB); -	setPalColor(0x12,0x00,0x49,0xFF); -	setPalColor(0x13,0x92,0x00,0xFF); -	setPalColor(0x14,0xB6,0x00,0xFF); -	setPalColor(0x15,0xFF,0x00,0x92); -	setPalColor(0x16,0xFF,0x00,0x00); -	setPalColor(0x17,0xDB,0x6D,0x00); -	setPalColor(0x18,0x92,0x6D,0x00); -	setPalColor(0x19,0x24,0x92,0x00); -	setPalColor(0x1A,0x00,0x92,0x00); -	setPalColor(0x1B,0x00,0xB6,0x6D); -	setPalColor(0x1C,0x00,0x92,0x92); -	setPalColor(0x1D,0x6D,0x6D,0x6D); // 0x00 -	setPalColor(0x1E,0x00,0x00,0x00); -	setPalColor(0x1F,0x00,0x00,0x00); - -	setPalColor(0x20,0xFF,0xFF,0xFF); -	setPalColor(0x21,0x6D,0xB6,0xFF); -	setPalColor(0x22,0x92,0x92,0xFF); -	setPalColor(0x23,0xDB,0x6D,0xFF); -	setPalColor(0x24,0xFF,0x00,0xFF); -	setPalColor(0x25,0xFF,0x6D,0xFF); -	setPalColor(0x26,0xFF,0x92,0x00); -	setPalColor(0x27,0xFF,0xB6,0x00); -	setPalColor(0x28,0xDB,0xDB,0x00); -	setPalColor(0x29,0x6D,0xDB,0x00); -	setPalColor(0x2A,0x00,0xFF,0x00); -	setPalColor(0x2B,0x49,0xFF,0xDB); -	setPalColor(0x2C,0x00,0xFF,0xFF); -	setPalColor(0x2D,0x49,0x49,0x49); -	setPalColor(0x2E,0x00,0x00,0x00); -	setPalColor(0x2F,0x00,0x00,0x00); - -	setPalColor(0x30,0xFF,0xFF,0xFF); -	setPalColor(0x31,0xB6,0xDB,0xFF); -	setPalColor(0x32,0xDB,0xB6,0xFF); -	setPalColor(0x33,0xFF,0xB6,0xFF); -	setPalColor(0x34,0xFF,0x92,0xFF); -	setPalColor(0x35,0xFF,0xB6,0xB6); -	setPalColor(0x36,0xFF,0xDB,0x92); -	setPalColor(0x37,0xFF,0xFF,0x49); -	setPalColor(0x38,0xFF,0xFF,0x6D); -	setPalColor(0x39,0xB6,0xFF,0x49); -	setPalColor(0x3A,0x92,0xFF,0x6D); -	setPalColor(0x3B,0x49,0xFF,0xDB); -	setPalColor(0x3C,0x92,0xDB,0xFF); -	setPalColor(0x3D,0x92,0x92,0x92); -	setPalColor(0x3E,0x00,0x00,0x00); -	setPalColor(0x3F,0x00,0x00,0x00); -} - -void ScummEngine::setAmigaPalette() { -	setPalColor( 0,   0,   0,   0); -	setPalColor( 1,   0,   0, 187); -	setPalColor( 2,   0, 187,   0); -	setPalColor( 3,   0, 187, 187); -	setPalColor( 4, 187,   0,   0); -	setPalColor( 5, 187,   0, 187); -	setPalColor( 6, 187, 119,   0); -	setPalColor( 7, 187, 187, 187); -	setPalColor( 8, 119, 119, 119); -	setPalColor( 9, 119, 119, 255); -	setPalColor(10,   0, 255,   0); -	setPalColor(11,   0, 255, 255); -	setPalColor(12, 255, 136, 136); -	setPalColor(13, 255,   0, 255); -	setPalColor(14, 255, 255,   0); -	setPalColor(15, 255, 255, 255); -} - -void ScummEngine::setHercPalette() { -	setPalColor( 0,   0,   0,   0); - -	if (_renderMode == Common::kRenderHercA) -		setPalColor( 1, 0xAE, 0x69, 0x38); -	else -		setPalColor( 1, 0x00, 0xFF, 0x00); - -	// Setup cursor palette -	setPalColor( 7, 170, 170, 170); -	setPalColor( 8,  85,  85,  85); -	setPalColor(15, 255, 255, 255); -} - -void ScummEngine::setCGAPalette() { -	setPalColor( 0,   0,   0,   0); -	setPalColor( 1,   0, 168, 168); -	setPalColor( 2, 168,   0, 168); -	setPalColor( 3, 168, 168, 168); - -	// Setup cursor palette -	setPalColor( 7, 170, 170, 170); -	setPalColor( 8,  85,  85,  85); -	setPalColor(15, 255, 255, 255); -} -void ScummEngine::setEGAPalette() { -	setPalColor( 0,   0,   0,   0); -	setPalColor( 1,   0,   0, 170); -	setPalColor( 2,   0, 170,   0); -	setPalColor( 3,   0, 170, 170); -	setPalColor( 4, 170,   0,   0); -	setPalColor( 5, 170,   0, 170); -	setPalColor( 6, 170,  85,   0); -	setPalColor( 7, 170, 170, 170); -	setPalColor( 8,  85,  85,  85); -	setPalColor( 9,  85,  85, 255); -	setPalColor(10,  85, 255,  85); -	setPalColor(11,  85, 255, 255); -	setPalColor(12, 255,  85,  85); -	setPalColor(13, 255,  85, 255); -	setPalColor(14, 255, 255,  85); -	setPalColor(15, 255, 255, 255); +	} else { +		if ((_game.platform == Common::kPlatformAmiga) && _game.version == 4) { +			// if rendermode is set to EGA we use the full palette from the resources +			// else we initialise and then lock down the first 16 colors. +			if (_renderMode != Common::kRenderEGA) +				setPaletteFromTable(tableAmigaMIPalette, sizeof(tableAmigaMIPalette) / 3); +		} +		setDirtyColors(0, 255); +	}  } -void ScummEngine::setV1Palette() { -	setPalColor( 0,   0,   0,   0); -	setPalColor( 1, 255, 255, 255); -	setPalColor( 2, 170,   0,   0); -	setPalColor( 3,   0, 170, 170); -	setPalColor( 4, 170,   0, 170); -	setPalColor( 5,   0, 170,   0); -	setPalColor( 6,   0,   0, 170); -	setPalColor( 7, 255, 255,  85); -	setPalColor( 8, 255,  85,  85); -	setPalColor( 9, 170,  85,   0); -	setPalColor(10, 255,  85,  85); -	setPalColor(11,  85,  85,  85); -	setPalColor(12, 170, 170, 170); -	setPalColor(13,  85, 255,  85); -	setPalColor(14,  85,  85, 255); - -	if (_game.id == GID_ZAK) -		setPalColor(15, 170, 170, 170); -	else -		setPalColor(15,  85,  85,  85); - -	setPalColor(16, 255,  85, 255); +void ScummEngine::setPaletteFromTable(const byte *ptr, int numcolor, int index) { +	for ( ; numcolor > 0; --numcolor, ++index, ptr += 3) +		setPalColor( index, ptr[0], ptr[1], ptr[2]);  }  void ScummEngine::setPaletteFromPtr(const byte *ptr, int numcolor) { +	int firstIndex = 0;  	int i;  	byte *dest, r, g, b; @@ -276,8 +201,13 @@ void ScummEngine::setPaletteFromPtr(const byte *ptr, int numcolor) {  	assertRange(0, numcolor, 256, "setPaletteFromPtr: numcolor");  	dest = _currentPalette; +	if ((_game.platform == Common::kPlatformAmiga) && _game.version == 4 && _renderMode != Common::kRenderEGA) { +		firstIndex = 16; +		dest += 3 * 16; +		ptr += 3 * 16; +	} -	for (i = 0; i < numcolor; i++) { +	for (i = firstIndex; i < numcolor; i++) {  		r = *ptr++;  		g = *ptr++;  		b = *ptr++; @@ -302,7 +232,7 @@ void ScummEngine::setPaletteFromPtr(const byte *ptr, int numcolor) {  		memcpy(_darkenPalette, _currentPalette, 768);  	} -	setDirtyColors(0, numcolor - 1); +	setDirtyColors(firstIndex, numcolor - 1);  }  void ScummEngine::setDirtyColors(int min, int max) { diff --git a/engines/scumm/player_v4a.cpp b/engines/scumm/player_v4a.cpp new file mode 100644 index 0000000000..f441b3b364 --- /dev/null +++ b/engines/scumm/player_v4a.cpp @@ -0,0 +1,193 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "engines/engine.h" +#include "scumm/player_v4a.h" +#include "scumm/scumm.h" + +#include "common/file.h" + +namespace Scumm { + +Player_V4A::Player_V4A(ScummEngine *scumm, Audio::Mixer *mixer) +	: _vm(scumm), +	  _mixer(mixer), +	  _tfmxMusic(_mixer->getOutputRate(), true), +	  _tfmxSfx(_mixer->getOutputRate(), true), +	  _musicHandle(), +	  _sfxHandle(), +	  _musicId(), +	  _sfxSlots(), +	  _initState(0), +	  _signal(0) { + +	assert(scumm); +	assert(mixer); +	assert(_vm->_game.id == GID_MONKEY_VGA); +	_tfmxMusic.setSignalPtr(&_signal, 1); +} + +bool Player_V4A::init() { +	if (_vm->_game.id != GID_MONKEY_VGA) +		error("player_v4a - unknown game"); +	 +	Common::File fileMdat, fileSample; + +	if (fileMdat.open("music.dat") && fileSample.open("sample.dat")) { +		// explicitly request that no instance delets the resources automatically +		if (_tfmxMusic.load(fileMdat, fileSample, false)) { +			_tfmxSfx.setModuleData(_tfmxMusic); +			return true; +		} +	} else +		warning("player_v4a: couldnt load one of the music resources: music.dat, sample.dat"); +	 +	return false; +} + +Player_V4A::~Player_V4A() { +	_mixer->stopHandle(_musicHandle); +	_mixer->stopHandle(_sfxHandle); +	_tfmxMusic.freeResources(); +} + +void Player_V4A::setMusicVolume(int vol) { +	debug(5, "player_v4a: setMusicVolume %i", vol); +} + +void Player_V4A::stopAllSounds() { +	debug(5, "player_v4a: stopAllSounds"); +	if (_initState > 0) { +		_tfmxMusic.stopSong(); +		_signal = 0;		 +		_musicId = 0; + +		_tfmxSfx.stopSong(); +		clearSfxSlots(); +	} else +		_mixer->stopHandle(_musicHandle); +} + +void Player_V4A::stopSound(int nr) { +	debug(5, "player_v4a: stopSound %d", nr); +	if (nr == 0) +		return; +	if (nr == _musicId) { +		_musicId = 0; +		if (_initState > 0) +			_tfmxMusic.stopSong(); +		else +			_mixer->stopHandle(_musicHandle); +		_signal = 0; +	} else { +		const int chan = getSfxChan(nr); +		if (chan != -1) { +			setSfxSlot(chan, 0); +			_tfmxSfx.stopMacroEffect(chan); +		} +	} +} + +void Player_V4A::startSound(int nr) { +	static const int8 monkeyCommands[52] = { +		 -1,  -2,  -3,  -4,  -5,  -6,  -7,  -8, +		 -9, -10, -11, -12, -13, -14,  18,  17, +		-17, -18, -19, -20, -21, -22, -23, -24, +		-25, -26, -27, -28, -29, -30, -31, -32, +		-33,  16, -35,   0,   1,   2,   3,   7, +		  8,  10,  11,   4,   5,  14,  15,  12, +		  6,  13,   9,  19 +	}; + +	const byte *ptr = _vm->getResourceAddress(rtSound, nr); +	assert(ptr); + +	const int val = ptr[9]; +	if (val < 0 || val >= ARRAYSIZE(monkeyCommands)) { +		warning("player_v4a: illegal Songnumber %i", val); +		return; +	} + +	if (!_initState) +		_initState = init() ? 1 : -1; + +	if (_initState < 0) +		return; + +	int index = monkeyCommands[val]; +	const byte type = ptr[6]; +	if (index < 0) {	// SoundFX +		index = -index - 1; +		debug(3, "player_v4a: play %d: custom %i - %02X", nr, index, type); + +		// start an empty Song so timing is setup +		if (_tfmxSfx.getSongIndex() < 0) +			_tfmxSfx.doSong(0x18); + +		const int chan = _tfmxSfx.doSfx((uint16)index); +		if (chan >= 0 && chan < ARRAYSIZE(_sfxSlots)) +			setSfxSlot(chan, nr, type); +		else +			warning("player_v4a: custom %i is not of required type", index); + +		// the Tfmx-player never "ends" the output by itself, so this should be threadsafe +		if (!_mixer->isSoundHandleActive(_sfxHandle)) +			_mixer->playInputStream(Audio::Mixer::kSFXSoundType, &_sfxHandle, &_tfmxSfx, -1, Audio::Mixer::kMaxChannelVolume, 0, false); + +	} else {	// Song +		debug(3, "player_v4a: play %d: song %i - %02X", nr, index, type); +		if (ptr[6] != 0x7F) +			warning("player_v4a: Song has wrong type"); + +		_tfmxMusic.doSong(index); +		_signal = 2; + +		// the Tfmx-player never "ends" the output by itself, so this should be threadsafe  +		if (!_mixer->isSoundHandleActive(_musicHandle)) +			_mixer->playInputStream(Audio::Mixer::kMusicSoundType, &_musicHandle, &_tfmxMusic, -1, Audio::Mixer::kMaxChannelVolume, 0, false); +		_musicId = nr; +	} +} + +int Player_V4A::getMusicTimer() const { +	// A workaround if the modplayer couldnt load the datafiles - just return a number big enough to pass all tests +	if (_initState < 0) +		return 2000; +	if (_musicId) { +		// The titlesong (and a few others) is running with ~70 ticks per second and the scale seems to be based on that.  +		// The Game itself doesnt get the timing from the Tfmx Player however, so we just use the elapsed time +		// 357 ~ 1000 * 25 * (1 / 70) +		return _mixer->getSoundElapsedTime(_musicHandle) / 357; +	} +	return 0; +} + +int Player_V4A::getSoundStatus(int nr) const { +	// For music the game queues a variable the Tfmx Player sets through a special command. +	// For sfx there seems to be no way to queue them, and the game doesnt try to. +	return (nr == _musicId) ? _signal : 0; +} + +} // End of namespace Scumm diff --git a/engines/scumm/player_v4a.h b/engines/scumm/player_v4a.h new file mode 100644 index 0000000000..5f5fae6b56 --- /dev/null +++ b/engines/scumm/player_v4a.h @@ -0,0 +1,98 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SCUMM_PLAYER_V4A_H +#define SCUMM_PLAYER_V4A_H + +#include "common/scummsys.h" +#include "scumm/music.h" +#include "sound/mixer.h" +#include "sound/mods/tfmx.h" + +class Mixer; + +namespace Scumm { + +class ScummEngine; + +/** + * Scumm V4 Amiga sound/music driver. + */ +class Player_V4A : public MusicEngine { +public: +	Player_V4A(ScummEngine *scumm, Audio::Mixer *mixer); +	virtual ~Player_V4A(); + +	virtual void setMusicVolume(int vol); +	virtual void startSound(int sound); +	virtual void stopSound(int sound); +	virtual void stopAllSounds(); +	virtual int  getMusicTimer() const; +	virtual int  getSoundStatus(int sound) const; + +private: +	ScummEngine *const _vm; +	Audio::Mixer *const _mixer; + +	Audio::Tfmx _tfmxMusic; +	Audio::Tfmx _tfmxSfx; +	Audio::SoundHandle _musicHandle; +	Audio::SoundHandle _sfxHandle; + +	int _musicId; +	uint16 _signal; + +	struct SfxChan { +		int id; +//		byte type; +	} _sfxSlots[4]; + +	int8 _initState; // < 0: failed, 0: uninitialised, > 0: initialised   + +	int getSfxChan(int id) const { +		for (int i = 0; i < ARRAYSIZE(_sfxSlots); ++i) +			if (_sfxSlots[i].id == id) +				return i; +		return -1; +	} + +	void setSfxSlot(int channel, int id, byte type = 0) { +		_sfxSlots[channel].id = id; +//		_sfxSlots[channel].type = type; +	} + +	void clearSfxSlots() { +		for (int i = 0; i < ARRAYSIZE(_sfxSlots); ++i){ +			_sfxSlots[i].id = 0; +//			_sfxSlots[i].type = 0; +		} +	} + +	bool init(); +}; + +} // End of namespace Scumm + +#endif diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 8c5731d539..9d6673d308 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -56,6 +56,7 @@  #include "scumm/player_v2.h"  #include "scumm/player_v2a.h"  #include "scumm/player_v3a.h" +#include "scumm/player_v4a.h"  #include "scumm/he/resource_he.h"  #include "scumm/scumm_v0.h"  #include "scumm/scumm_v8.h" @@ -281,7 +282,6 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr)  	_useTalkAnims = false;  	_defaultTalkDelay = 0;  	_musicType = MDT_NONE; -	_tempMusic = 0;  	_saveSound = 0;  	memset(_extraBoxFlags, 0, sizeof(_extraBoxFlags));  	memset(_scaleSlots, 0, sizeof(_scaleSlots)); @@ -496,7 +496,9 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr)  	case Common::kRenderCGA:  	case Common::kRenderEGA:  	case Common::kRenderAmiga: -		if ((_game.version >= 4 && !(_game.features & GF_16COLOR)) || (_game.features & GF_OLD256)) +		if ((_game.version >= 4 && !(_game.features & GF_16COLOR)  +			&& !(_game.platform == Common::kPlatformAmiga && _renderMode == Common::kRenderEGA)) +			|| (_game.features & GF_OLD256))  			_renderMode = Common::kRenderDefault;  		break; @@ -547,10 +549,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr)  ScummEngine::~ScummEngine() {  	Common::clearAllDebugChannels(); -	if (_musicEngine) { -		_musicEngine->terminate(); -		delete _musicEngine; -	} +	delete _musicEngine;  	_mixer->stopAll(); @@ -1281,7 +1280,6 @@ void ScummEngine::setupCostumeRenderer() {  void ScummEngine::resetScumm() {  	int i; -	_tempMusic = 0;  	debug(9, "resetScumm");  	if (_game.version == 0) { @@ -1685,7 +1683,7 @@ void ScummEngine::setupMusic(int midi) {  	} else if (_game.platform == Common::kPlatformPCEngine && _game.version == 3) {  		// TODO: Add support for music format  	} else if (_game.platform == Common::kPlatformAmiga && _game.version <= 4) { -		// TODO: Add support for music format +		_musicEngine = new Player_V4A(this, _mixer);  	} else if (_game.id == GID_MANIAC && _game.version == 1) {  		_musicEngine = new Player_V1(this, _mixer, midiDriver != MD_PCSPK);  	} else if (_game.version <= 2) { @@ -1896,17 +1894,6 @@ void ScummEngine::scummLoop(int delta) {  		if (_musicEngine) {  			// The music engine generates the timer data for us.  			VAR(VAR_MUSIC_TIMER) = _musicEngine->getMusicTimer(); -		} else { -			// Used for Money Island 1 (Amiga) -			// TODO: The music delay (given in milliseconds) might have to be tuned a little -			// to get it correct for all games. Without the ability to watch/listen to the -			// original games, I can't do that myself. -			const int MUSIC_DELAY = 350; -			_tempMusic += delta * 1000 / 60;	// Convert delta to milliseconds -			if (_tempMusic >= MUSIC_DELAY) { -				_tempMusic -= MUSIC_DELAY; -				VAR(VAR_MUSIC_TIMER) += 1; -			}  		}  	} diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index bfb188f1c7..f0f2521225 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -1028,14 +1028,7 @@ protected:  	const byte *getPalettePtr(int palindex, int room); -	void setC64Palette(); -	void setNESPalette(); -	void setAmigaPalette(); -	void setHercPalette(); -	void setCGAPalette(); -	void setEGAPalette(); -	void setV1Palette(); - +	void setPaletteFromTable(const byte *ptr, int numcolor, int firstIndex = 0);  	void resetPalette();  	void setCurrentPalette(int pal); @@ -1144,7 +1137,6 @@ protected:  	bool _haveActorSpeechMsg;  	bool _useTalkAnims;  	uint16 _defaultTalkDelay; -	int _tempMusic;  	int _saveSound;  	bool _native_mt32;  	bool _enable_gs; diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 528cceb0cc..07e2ce8bca 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -436,26 +436,6 @@ void Sound::playSound(int soundID) {  		if (_vm->_game.id == GID_MONKEY_VGA || _vm->_game.id == GID_MONKEY_EGA  			|| (_vm->_game.id == GID_MONKEY && _vm->_game.platform == Common::kPlatformMacintosh)) { -			// Sound is currently not supported at all in the amiga versions of these games -			if (_vm->_game.platform == Common::kPlatformAmiga) { -				int track = -1; -				if (soundID == 50) -					track = 17; -				else if (ptr[6] == 0x7F && ptr[7] == 0x00 && ptr[8] == 0x80) { -					static const char tracks[16] = {13,14,10,3,4,9,16,5,1,8,2,15,6,7,11,12}; -					if (ptr[9] == 0x0E) -						track = 18; -					else -						track = tracks[ptr[9] - 0x23]; -				} -				if (track != -1) { -					playCDTrack(track,((track < 5) || (track > 16)) ? 1 : -1,0,0); -					stopCDTimer(); -					_currentCDSound = soundID; -				} -				return; -			} -  			// 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 diff --git a/sound/mods/maxtrax.cpp b/sound/mods/maxtrax.cpp new file mode 100644 index 0000000000..4d02dc7152 --- /dev/null +++ b/sound/mods/maxtrax.cpp @@ -0,0 +1,986 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/scummsys.h" +#include "common/endian.h" +#include "common/stream.h" +#include "common/util.h" +#include "common/debug.h" + +#include "sound/mods/maxtrax.h" + +// test for engines using this class. +#if defined(SOUND_MODS_MAXTRAX_H) + +namespace { + +enum { K_VALUE = 0x9fd77, PREF_PERIOD = 0x8fd77, PERIOD_LIMIT = 0x6f73d }; +enum { NO_BEND = 64 << 7, MAX_BEND_RANGE = 24 }; + +int32 precalcNote(byte baseNote, int16 tune, byte octave) { +	return K_VALUE + 0x3C000 + (1 << 16) - ((baseNote << 14) + (tune << 11) / 3) / 3 - (octave << 16); +} + +int32 calcVolumeDelta(int32 delta, uint16 time, uint16 vBlankFreq) { +	const int32 div = time * vBlankFreq; +	// div <= 1000 means time to small (or even 0) +	return (div <= 1000) ? delta : (1000 * delta) / div; +} + +int32 calcTempo(const uint16 tempo, uint16 vBlankFreq) { +	return (int32)(((uint32)(tempo & 0xFFF0) << 8) / (uint16)(5 * vBlankFreq)); +} + +void nullFunc(int) {} + +// define sinetable if needed and setup a compile-time constant +#ifdef MAXTRAX_HAS_MODULATION +const int8 tableSine[256] = { 0 }; // todo - fillin values +const bool kHasModulation = true; +#else +const bool kHasModulation = false; +#endif + +} + +namespace Audio { + +MaxTrax::MaxTrax(int rate, bool stereo, uint16 vBlankFreq, uint16 maxScores) +	: Paula(stereo, rate, rate / vBlankFreq), +	  _patch(), +	  _scores(), +	  _numScores() { +	_playerCtx.maxScoreNum = maxScores; +	_playerCtx.vBlankFreq = vBlankFreq; +	_playerCtx.frameUnit = (uint16)((1000 << 8) /  vBlankFreq); +	_playerCtx.scoreIndex = -1; +	_playerCtx.volume = 0x40; + +	_playerCtx.tempo = 120; +	_playerCtx.tempoTime = 0; +	_playerCtx.filterOn = true; +	_playerCtx.syncCallBack = &nullFunc; + +	resetPlayer(); +	for (int i = 0; i < ARRAYSIZE(_channelCtx); ++i) +		_channelCtx[i].regParamNumber = 0; +} + +MaxTrax::~MaxTrax() { +	stopMusic(); +	freePatches(); +	freeScores(); +} + +void MaxTrax::interrupt() { +	// a5 - maxtraxm a4 . globaldata + +	// TODO +	// test for changes in shared struct and make changes +	// specifically all used channels get marked altered + +	_playerCtx.ticks += _playerCtx.tickUnit; +	const int32 millis = _playerCtx.ticks >> 8; // d4 + +	for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { +		VoiceContext &voice = _voiceCtx[i]; +		if (voice.stopEventTime >= 0) { +			assert(voice.channel); +			voice.stopEventTime -= (voice.channel < &_channelCtx[kNumChannels]) ? _playerCtx.tickUnit : _playerCtx.frameUnit; +			if (voice.stopEventTime <= 0 && voice.status > VoiceContext::kStatusRelease) { +				if ((voice.channel->flags & ChannelContext::kFlagDamper) != 0) +					voice.hasDamper = true; +				else +					voice.status = VoiceContext::kStatusRelease; +			} +		} +	} + +	if (_playerCtx.scoreIndex >= 0) { +		const Event *curEvent = _playerCtx.nextEvent; +		int32 eventDelta = _playerCtx.nextEventTime - millis; +		for (; eventDelta <= 0; eventDelta += (++curEvent)->startTime) { +			const byte cmd = curEvent->command; +			ChannelContext &channel = _channelCtx[curEvent->parameter & 0x0F]; + +			// outPutEvent(*curEvent); +			// debug("CurTime, EventDelta, NextDelta: %d, %d, %d", millis, eventDelta, eventDelta + curEvent[1].startTime ); + +			if (cmd < 0x80) {	// Note +				const int8 voiceIndex = noteOn(channel, cmd, (curEvent->parameter & 0xF0) >> 1, kPriorityScore); +				if (voiceIndex >= 0) +					_voiceCtx[voiceIndex].stopEventTime = MAX(0, (eventDelta + curEvent->stopTime) << 8); + +			} else { +				switch (cmd) { + +				case 0x80:	// TEMPO +					if ((_playerCtx.tickUnit >> 8) > curEvent->stopTime) { +						_playerCtx.tickUnit = calcTempo(curEvent->parameter << 4, _playerCtx.vBlankFreq); +						_playerCtx.tempoTime = 0; +					} else { +						_playerCtx.tempoStart = _playerCtx.tempo; +						_playerCtx.tempoDelta = (curEvent->parameter << 4) - _playerCtx.tempoStart; +						_playerCtx.tempoTime  = (curEvent->stopTime << 8); +						_playerCtx.tempoTicks = 0; +					} +					break; +				 +				case 0xC0:	// PROGRAM +					channel.patch = &_patch[curEvent->stopTime & (kNumPatches - 1)]; +					break; + +				case 0xE0:	// BEND +					channel.pitchBend = ((curEvent->stopTime & 0x7F00) >> 1) | (curEvent->stopTime & 0x7f); +					channel.pitchReal = (((int32)channel.pitchBendRange * channel.pitchBend) >> 5) - (channel.pitchBendRange << 8); +					channel.isAltered = true; +					break; + +				case 0xFF:	// END +					if (_playerCtx.musicLoop) { +						curEvent = _scores[_playerCtx.scoreIndex].events; +						eventDelta = curEvent->startTime - millis; +						_playerCtx.ticks = 0; +					} else +						_playerCtx.scoreIndex = -1; +					// stop processing for this tick +					goto endOfEventLoop; + +				case 0xA0: 	// SPECIAL +					switch (curEvent->stopTime >> 8){ +					case 0x01:	// SPECIAL_SYNC +						_playerCtx.syncCallBack(curEvent->stopTime & 0xFF); +						break; +					case 0x02:	// SPECIAL_BEGINREP +						// we allow a depth of 4 loops +						for (int i = 0; i < ARRAYSIZE(_playerCtx.repeatPoint); ++i) { +							if (!_playerCtx.repeatPoint[i]) { +								_playerCtx.repeatPoint[i] = curEvent; +								_playerCtx.repeatCount[i] = curEvent->stopTime & 0xFF; +								break; +							} +						} +						break; +					case 0x03:	// SPECIAL_ENDREP +						for (int i = ARRAYSIZE(_playerCtx.repeatPoint) - 1; i >= 0; --i) { +							if (_playerCtx.repeatPoint[i]) { +								if (_playerCtx.repeatCount[i]--) +									curEvent = _playerCtx.repeatPoint[i]; // gets incremented by 1 at end of loop +								else +									_playerCtx.repeatPoint[i] = 0; +								break; +							} +						} +						break; +					} +					break; + +				case 0xB0:	// CONTROL +					controlCh(channel, (byte)(curEvent->stopTime >> 8), (byte)curEvent->stopTime); +					break; + +				default: +					debug("Unhandled Command"); +					outPutEvent(*curEvent); +				} +			} +		} +endOfEventLoop: +		_playerCtx.nextEvent = curEvent; +		_playerCtx.nextEventTime = eventDelta + millis; + +		// tempoEffect +		if (_playerCtx.tempoTime) { +			_playerCtx.tempoTicks += _playerCtx.tickUnit; +			uint16 newTempo = _playerCtx.tempoStart; +			if (_playerCtx.tempoTicks < _playerCtx.tempoTime) { +				newTempo += (uint16)((_playerCtx.tempoTicks * _playerCtx.tempoDelta) / _playerCtx.tempoTime); +			} else { +				_playerCtx.tempoTime = 0; +				newTempo += _playerCtx.tempoDelta; +			} +			_playerCtx.tickUnit = calcTempo(newTempo, _playerCtx.vBlankFreq); +		} +	} + +	// Handling of Envelopes and Portamento +	for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { +		VoiceContext &voice = _voiceCtx[i]; +		if (!voice.channel) +			continue; +		const ChannelContext &channel = *voice.channel; +		const Patch &patch = *voice.patch; + +		switch (voice.status) { +		case VoiceContext::kStatusSustain: +			// we need to check if some voices have no sustainSample. +			// in that case they are finished after the attackSample is done +			if (voice.dmaOff && Paula::getChannelDmaCount((byte)i) >= voice.dmaOff ) { +				voice.dmaOff = 0; +				voice.isBlocked = false; +				voice.priority = 0; +				// disable it in next tick +				voice.stopEventTime = 0; +			} +			if (!channel.isAltered && !voice.hasPortamento && (!kHasModulation || !channel.modulation)) +				continue; +			// Update Volume and Period +			break; + +		case VoiceContext::kStatusHalt: +			killVoice((byte)i); +			continue; + +		case VoiceContext::kStatusStart: +			if (patch.attackLen) { +				voice.envelope = patch.attackPtr; +				const uint16 duration = voice.envelope->duration; +				voice.envelopeLeft = patch.attackLen; +				voice.ticksLeft = duration << 8; +				voice.status = VoiceContext::kStatusAttack; +				voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume, duration, _playerCtx.vBlankFreq); +				// Process Envelope +			} else { +				voice.status = VoiceContext::kStatusSustain; +				voice.baseVolume = patch.volume; +				// Update Volume and Period +			} +			break; + +		case VoiceContext::kStatusRelease: +			if (patch.releaseLen) { +				voice.envelope = patch.attackPtr + patch.attackLen; +				const uint16 duration = voice.envelope->duration; +				voice.envelopeLeft = patch.releaseLen; +				voice.ticksLeft = duration << 8; +				voice.status = VoiceContext::kStatusDecay; +				voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume - voice.baseVolume, duration, _playerCtx.vBlankFreq); +				// Process Envelope +			} else { +				voice.status = VoiceContext::kStatusHalt; +				voice.lastVolume = 0; +				// Send Audio Packet +			} +			voice.stopEventTime = -1; +			break; +		} + +		// Process Envelope +		const uint16 envUnit = _playerCtx.frameUnit; +		if (voice.envelope) { +			if (voice.ticksLeft > envUnit) {	// envelope still active +				voice.baseVolume = (uint16)MIN(MAX(0, voice.baseVolume + voice.incrVolume), 0x8000); +				voice.ticksLeft -= envUnit; +				// Update Volume and Period + +			} else {	// next or last Envelope +				voice.baseVolume = voice.envelope->volume; +				assert(voice.envelopeLeft > 0); +				if (--voice.envelopeLeft) { +					++voice.envelope; +					const uint16 duration = voice.envelope->duration; +					voice.ticksLeft = duration << 8; +					voice.incrVolume = calcVolumeDelta((int32)voice.envelope->volume - voice.baseVolume, duration, _playerCtx.vBlankFreq); +					// Update Volume and Period +				} else if (voice.status == VoiceContext::kStatusDecay) { +					voice.status = VoiceContext::kStatusHalt; +					voice.envelope = 0; +					voice.lastVolume = 0; +					// Send Audio Packet +				} else { +					assert(voice.status == VoiceContext::kStatusAttack); +					voice.status = VoiceContext::kStatusSustain; +					voice.envelope = 0; +					// Update Volume and Period +				} +			} +		} + +		// Update Volume and Period +		if (voice.status >= VoiceContext::kStatusDecay) { +			// Calc volume +			uint16 vol = (voice.noteVolume < (1 << 7)) ? (voice.noteVolume * _playerCtx.volume) >> 7 : _playerCtx.volume; +			if (voice.baseVolume < (1 << 15)) +				vol = (uint16)(((uint32)vol * voice.baseVolume) >> 15); +			if (voice.channel->volume < (1 << 7)) +				vol = (vol * voice.channel->volume) >> 7; +			voice.lastVolume = (byte)MIN(vol, (uint16)0x64); + +			// Calc Period +			if (voice.hasPortamento) { +				voice.portaTicks += envUnit; +				if ((uint16)(voice.portaTicks >> 8) >= channel.portamentoTime) { +					voice.hasPortamento = false; +					voice.baseNote = voice.endNote; +					voice.preCalcNote = precalcNote(voice.baseNote, patch.tune, voice.octave); +				} +				voice.lastPeriod = calcNote(voice); +			} else if (channel.isAltered || (kHasModulation && channel.modulation)) +				voice.lastPeriod = calcNote(voice); +		} + +		// Send Audio Packet +		Paula::setChannelPeriod((byte)i, (voice.lastPeriod) ? voice.lastPeriod : 1000); +		Paula::setChannelVolume((byte)i, (voice.lastPeriod) ? voice.lastVolume : 0); +	} +	for (ChannelContext *c = _channelCtx; c != &_channelCtx[ARRAYSIZE(_channelCtx)]; ++c) +		c->isAltered = false; + +	// original player had _playerCtx.sineValue = _playerCtx.frameUnit >> 2 +	// this should fit the comments that modtime=1000 is one second ? +	if (kHasModulation) +		_playerCtx.sineValue += _playerCtx.frameUnit; +} + +void MaxTrax::controlCh(ChannelContext &channel, const byte command, const byte data) { +	switch (command) { +	case 0x01:	// modulation level MSB +		channel.modulation = data << 8; +		break; +	case 0x21:	// modulation level LSB +		channel.modulation = (channel.modulation & 0xFF00) || ((data * 2) & 0xFF); +		break; +	case 0x05:	// portamento time MSB +		channel.portamentoTime = data << 7; +		break; +	case 0x25:	// portamento time LSB +		channel.portamentoTime = (channel.portamentoTime & 0x3f80) || data; +		break; +	case 0x06:	// data entry MSB +		if (channel.regParamNumber == 0) { +			channel.pitchBendRange = (int8)MIN((uint8)MAX_BEND_RANGE, (uint8)data); +			channel.pitchReal = (((int32)channel.pitchBendRange * channel.pitchBend) >> 5) - (channel.pitchBendRange << 8); +			channel.isAltered = true; +		} +		break; +	case 0x07:	// Main Volume MSB +		channel.volume = (data == 0) ? 0 : data + 1; +		channel.isAltered = true; +		break; +	case 0x0A:	// Pan +		if (data > 0x40 || (data == 0x40 && ((&channel - _channelCtx) & 1) != 0)) +			channel.flags |= ChannelContext::kFlagRightChannel; +		else +			channel.flags &= ~ChannelContext::kFlagRightChannel; +		break; +	case 0x10:	// GPC as Modulation Time MSB +		channel.modulationTime = data << 7; +		break; +	case 0x30:	// GPC as Modulation Time LSB +		channel.modulationTime = (channel.modulationTime & 0x3f80) || data; +		break; +	case 0x11:	// GPC as Microtonal Set MSB +		channel.microtonal = data << 8; +		break; +	case 0x31:	// GPC as Microtonal Set LSB +		channel.microtonal = (channel.microtonal & 0xFF00) || ((data * 2) & 0xFF); +		break; +	case 0x40:	// Damper Pedal +		if ((data & 0x40) != 0) +			channel.flags |= ChannelContext::kFlagDamper; +		else { +			channel.flags &= ~ChannelContext::kFlagDamper; +			// release all dampered voices on this channel +			for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { +				if (_voiceCtx[i].channel == &channel && _voiceCtx[i].hasDamper) { +					_voiceCtx[i].hasDamper = false; +					_voiceCtx[i].status = VoiceContext::kStatusRelease; +				} +			} +		} +		break; +	case 0x41:	// Portamento off/on +		if ((data & 0x40) != 0) +			channel.flags |= ChannelContext::kFlagPortamento; +		else +			channel.flags &= ~ChannelContext::kFlagPortamento; +		break; +	case 0x50:	// Microtonal off/on  +		if ((data & 0x40) != 0) +			channel.flags |= ChannelContext::kFlagMicrotonal; +		else +			channel.flags &= ~ChannelContext::kFlagMicrotonal; +		break; +	case 0x51:	// Audio Filter off/on +		Paula::setAudioFilter(data > 0x40 || (data == 0x40 && _playerCtx.filterOn)); +		break; +	case 0x65:	// RPN MSB +		channel.regParamNumber = (data << 8) || (channel.regParamNumber & 0xFF); +		break; +	case 0x64:	// RPN LSB +		channel.regParamNumber = (channel.regParamNumber & 0xFF00) || data; +		break; +	case 0x79:	// Reset All Controllers +		resetChannel(channel, ((&channel - _channelCtx) & 1) != 0); +		break; +	case 0x7E:	// MONO mode +		channel.flags |= ChannelContext::kFlagMono; +		goto allNotesOff; +	case 0x7F:	// POLY mode +		channel.flags &= ~ChannelContext::kFlagMono; +		// Fallthrough +	case 0x7B:	// All Notes Off +allNotesOff: +		for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { +			if (_voiceCtx[i].channel == &channel) { +				if ((channel.flags & ChannelContext::kFlagDamper) != 0) +					_voiceCtx[i].hasDamper = true; +				else +					_voiceCtx[i].status = VoiceContext::kStatusRelease; +			} +		} +		break; +	case 0x78:	// All Sounds Off +		for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { +			if (_voiceCtx[i].channel == &channel) +				killVoice((byte)i); +		} +		break; +	} +} + +void MaxTrax::setTempo(const uint16 tempo) { +	Common::StackLock lock(_mutex); +	_playerCtx.tickUnit = calcTempo(tempo, _playerCtx.vBlankFreq); +} + +void MaxTrax::resetPlayer() { +	for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) +		killVoice((byte)i); + +	for (int i = 0; i < ARRAYSIZE(_channelCtx); ++i) { +		_channelCtx[i].flags = 0; +		_channelCtx[i].lastNote = (uint8)-1; +		resetChannel(_channelCtx[i], (i & 1) != 0); +		_channelCtx[i].patch = (i < kNumChannels) ? &_patch[i] : 0; +	} + +#ifdef MAXTRAX_HAS_MICROTONAL +	for (int i = 0; i < ARRAYSIZE(_microtonal); ++i) +		_microtonal[i] = (int16)(i << 8); +#endif +} + +void MaxTrax::stopMusic() { +	Common::StackLock lock(_mutex); +	_playerCtx.scoreIndex = -1; +	for (int i = 0; i < ARRAYSIZE(_voiceCtx); ++i) { +		if (_voiceCtx[i].channel < &_channelCtx[kNumChannels]) +			killVoice((byte)i); +	} +} + +bool MaxTrax::playSong(int songIndex, bool loop) { +	if (songIndex < 0 || songIndex >= _numScores) +		return false; +	Common::StackLock lock(_mutex); +	_playerCtx.scoreIndex = -1; +	resetPlayer(); +	for (int i = 0; i < ARRAYSIZE(_playerCtx.repeatPoint); ++i) +		_playerCtx.repeatPoint[i] = 0; + +	setTempo(_playerCtx.tempoInitial << 4); +	Paula::setAudioFilter(_playerCtx.filterOn); +	_playerCtx.musicLoop = loop; +	_playerCtx.tempoTime = 0; +	_playerCtx.scoreIndex = songIndex; +	_playerCtx.ticks = 0; + +	_playerCtx.nextEvent = _scores[songIndex].events;; +	_playerCtx.nextEventTime = _playerCtx.nextEvent->startTime; + +	Paula::startPaula(); +	return true; +} + +void MaxTrax::advanceSong(int advance) { +	Common::StackLock lock(_mutex); +	if (_playerCtx.scoreIndex >= 0) { +		const Event *cev = _playerCtx.nextEvent; +		if (cev) { +			for (; advance > 0; --advance) { +				// TODO - check for boundaries  +				for (; cev->command != 0xFF && (cev->command != 0xA0 || (cev->stopTime >> 8) != 0x00); ++cev) +					; // no end_command or special_command + end +			} +			_playerCtx.nextEvent = cev; +		} +	} +} + +void MaxTrax::killVoice(byte num) { +	VoiceContext &voice = _voiceCtx[num]; +	voice.channel = 0; +	voice.envelope = 0; +	voice.status = VoiceContext::kStatusFree; +	voice.isBlocked = false; +	voice.hasDamper = false; +	voice.hasPortamento = false; +	voice.priority = 0; +	voice.stopEventTime = -1; +	voice.dmaOff = 0; +	voice.lastVolume = 0; +	//voice.uinqueId = 0; + +	// "stop" voice, set period to 1, vol to 0 +	Paula::disableChannel(num); +	Paula::setChannelPeriod(num, 1); +	Paula::setChannelVolume(num, 0); +} + +int8 MaxTrax::pickvoice(const VoiceContext voices[4], uint pick, int16 pri) { +	enum { kPrioFlagFixedSide = 1 << 3 }; +	if ((pri & (kPrioFlagFixedSide)) == 0) { +		const bool leftSide = (uint)(pick - 1) > 1; +		const int leftBest = MIN(voices[0].status, voices[3].status); +		const int rightBest = MIN(voices[1].status, voices[2].status); +		const int sameSide = (leftSide) ? leftBest : rightBest; +		const int otherSide = leftBest + rightBest - sameSide; + +		if (sameSide > VoiceContext::kStatusRelease && otherSide <= VoiceContext::kStatusRelease) +			pick ^= 1; // switches sides +	} +	pick &= 3; + +	for (int i = 2; i > 0; --i) { +		const VoiceContext *voice = &voices[pick]; +		const VoiceContext *alternate = &voices[pick ^ 3]; + +		if (voice->status > alternate->status  +			|| (voice->status == alternate->status && voice->lastVolume > alternate->lastVolume)) { +			// TODO: tiebreaking +			pick ^= 3; // switch channels +			const VoiceContext *tmp = voice; +			voice = alternate; +			alternate = tmp; +		} + +		if (voice->isBlocked || voice->priority > pri) { +			pick ^= 3; // switch channels +			if (alternate->isBlocked || alternate->priority > pri) { +				// if not already done, switch sides and try again +				pick ^= 1; +				continue; +			} +		} +		// succeded +		return (int8)pick; +	} +	// failed +	debug(5, "MaxTrax: could not find channel for note"); +	return -1; +} + +uint16 MaxTrax::calcNote(const VoiceContext &voice) { +	const ChannelContext &channel = *voice.channel; +	int16 bend = channel.pitchReal; + +#ifdef MAXTRAX_HAS_MICROTONAL +	if (voice.hasPortamento) { +		if ((channel.flags & ChannelContext::kFlagMicrotonal) != 0) +			bend += (int16)(((_microtonal[voice.endNote] - _microtonal[voice.baseNote]) * voice.portaTicks) >> 8) / channel.portamentoTime; +		else +			bend += (int16)(((int8)(voice.endNote - voice.baseNote)) * voice.portaTicks) / channel.portamentoTime; +	} + +	if ((channel.flags & ChannelContext::kFlagMicrotonal) != 0) +		bend += _microtonal[voice.baseNote]; +#else +	if (voice.hasPortamento) +		bend += (int16)(((int8)(voice.endNote - voice.baseNote)) * voice.portaTicks) / channel.portamentoTime; +#endif + +#ifdef MAXTRAX_HAS_MODULATION +	if (channel.modulation) { +		if ((channel.flags & ChannelContext::kFlagModVolume) == 0) { +			int sineInd = (_playerCtx.sineValue / channel.modulationTime) & 0xFF; +			// TODO - use table +			bend += (int16)(sinf(sineInd * (float)((2 * PI) / 256)) * channel.modulation); +		}  +	} +#endif + +	// tone = voice.baseNote << 8 + microtonal +	// bend = channelPitch + porta + modulation + +	const int32 tone = voice.preCalcNote + (bend << 6) / 3; + +	if (tone >= PERIOD_LIMIT + (1 << 16)) { +		// calculate 2^tone and round towards nearest integer  +		// 2*2^tone = exp((tone+1) * ln(2)) +		const uint16 periodX2 = (uint16)expf((float)tone * (float)(0.69314718055994530942 / (1 << 16))); +		return (periodX2 + 1) / 2; +	} +	return 0; +} + +int8 MaxTrax::noteOn(ChannelContext &channel, const byte note, uint16 volume, uint16 pri) { +#ifdef MAXTRAX_HAS_MICROTONAL +	if (channel.microtonal >= 0) +		_microtonal[note % 127] = channel.microtonal; +#endif + +	if (!volume) +		return -1; + +	const Patch &patch = *channel.patch; +	if (!patch.samplePtr || patch.sampleTotalLen == 0) +		return -1; +	int8 voiceNum = -1; +	if ((channel.flags & ChannelContext::kFlagMono) == 0) { +		voiceNum = pickvoice(_voiceCtx, (channel.flags & ChannelContext::kFlagRightChannel) != 0 ? 1 : 0, pri); +	} else { +		VoiceContext *voice = _voiceCtx + ARRAYSIZE(_voiceCtx) - 1; +		for (voiceNum = ARRAYSIZE(_voiceCtx) - 1; voiceNum >= 0 && voice->channel != &channel; --voiceNum, --voice) +			; +		if (voiceNum < 0) +			voiceNum = pickvoice(_voiceCtx, (channel.flags & ChannelContext::kFlagRightChannel) != 0 ? 1 : 0, pri); +		else if (voice->status >= VoiceContext::kStatusSustain && (channel.flags & ChannelContext::kFlagPortamento) != 0) { +			// reset previous porta +			if (voice->hasPortamento) +				voice->baseNote = voice->endNote; +			voice->preCalcNote = precalcNote(voice->baseNote, patch.tune, voice->octave); +			voice->noteVolume = (_playerCtx.handleVolume) ? volume + 1 : 128; +			voice->portaTicks = 0; +			voice->hasPortamento = true; +			voice->endNote = channel.lastNote = note; +			return voiceNum; +		} +	}	 + +	if (voiceNum >= 0) { +		VoiceContext &voice = _voiceCtx[voiceNum]; +		voice.hasDamper = false; +		voice.isBlocked = false; +		voice.hasPortamento = false; +		if (voice.channel) +			killVoice(voiceNum); +		voice.channel = &channel; +		voice.patch = &patch; +		voice.baseNote = note; + +		// always base octave on the note in the command, regardless of porta +		const int32 plainNote = precalcNote(note, patch.tune, 0); +		const int32 PREF_PERIOD1 = PREF_PERIOD + (1 << 16); +		// calculate which sample to use +		const int useOctave = (plainNote <= PREF_PERIOD1) ? 0 : MIN<int32>((plainNote + 0xFFFF - PREF_PERIOD1) >> 16, patch.sampleOctaves - 1); +		voice.octave = (byte)useOctave; +		voice.preCalcNote = plainNote - (useOctave << 16); + +		// next calculate the actual period which depends on wether porta is enabled +		if (&channel < &_channelCtx[kNumChannels] && (channel.flags & ChannelContext::kFlagPortamento) != 0) { +			if ((channel.flags & ChannelContext::kFlagMono) != 0 && channel.lastNote < 0x80 && channel.lastNote != note) { +				voice.portaTicks = 0; +				voice.baseNote = channel.lastNote; +				voice.endNote = note; +				voice.hasPortamento = true; +				voice.preCalcNote = precalcNote(voice.baseNote, patch.tune, voice.octave); +			} +			channel.lastNote = note; +		} +		 +		voice.lastPeriod = calcNote(voice); + +		voice.priority = (byte)pri; +		voice.status = VoiceContext::kStatusStart; + +		voice.noteVolume = (_playerCtx.handleVolume) ? volume + 1 : 128; +		voice.baseVolume = 0; + +		const uint16 period = (voice.lastPeriod) ? voice.lastPeriod : 1000; + +		// TODO: since the original player is using the OS-functions, more than 1 sample could be queued up already +		// get samplestart for the given octave +		const int8 *samplePtr = patch.samplePtr + (patch.sampleTotalLen << useOctave) - patch.sampleTotalLen; +		if (patch.sampleAttackLen) { +			Paula::setChannelSampleStart(voiceNum, samplePtr); +			Paula::setChannelSampleLen(voiceNum, (patch.sampleAttackLen << useOctave) / 2); +			Paula::setChannelPeriod(voiceNum, period); +			Paula::setChannelVolume(voiceNum, 0); + +			Paula::enableChannel(voiceNum); +			// wait  for dma-clear +		} + +		if (patch.sampleTotalLen > patch.sampleAttackLen) { +			Paula::setChannelSampleStart(voiceNum, samplePtr + (patch.sampleAttackLen << useOctave)); +			Paula::setChannelSampleLen(voiceNum, ((patch.sampleTotalLen - patch.sampleAttackLen) << useOctave) / 2); +			if (!patch.sampleAttackLen) { +				// need to enable channel +				Paula::setChannelPeriod(voiceNum, period); +				Paula::setChannelVolume(voiceNum, 0); + +				Paula::enableChannel(voiceNum); +			} +			// another pointless wait for DMA-Clear??? + +		} else { // no sustain sample +			// this means we must stop playback after the attacksample finished +			// so we queue up an "empty" sample and note that we need to kill the sample after dma finished +			Paula::setChannelSampleStart(voiceNum, 0); +			Paula::setChannelSampleLen(voiceNum, 0); +			Paula::setChannelDmaCount(voiceNum); +			voice.dmaOff = 1; +		} +	} +	return voiceNum; +} + +void MaxTrax::resetChannel(ChannelContext &chan, bool rightChannel) { +	chan.modulation = 0; +	chan.modulationTime = 1000; +	chan.microtonal = -1; +	chan.portamentoTime = 500; +	chan.pitchBend = NO_BEND; +	chan.pitchReal = 0; +	chan.pitchBendRange = MAX_BEND_RANGE; +	chan.volume = 128; +	chan.flags &= ~(ChannelContext::kFlagPortamento | ChannelContext::kFlagMicrotonal | ChannelContext::kFlagRightChannel); +	chan.isAltered = true; +	if (rightChannel) +		chan.flags |= ChannelContext::kFlagRightChannel; +} + +void MaxTrax::freeScores() { +	if (_scores) { +		for (int i = 0; i < _numScores; ++i) +			delete[] _scores[i].events; +		delete[] _scores; +		_scores = 0; +	} +	_numScores = 0; +	_playerCtx.tempo = 120; +	_playerCtx.filterOn = true; +} + +void MaxTrax::freePatches() { +	for (int i = 0; i < ARRAYSIZE(_patch); ++i) { +		delete[] _patch[i].samplePtr; +		delete[] _patch[i].attackPtr; +	} +	memset(_patch, 0, sizeof(_patch)); +} + +void MaxTrax::setSignalCallback(void (*callback) (int)) { +	Common::StackLock lock(_mutex); +	_playerCtx.syncCallBack = (callback == 0) ? nullFunc : callback; +} + +int MaxTrax::playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide) { +	Common::StackLock lock(_mutex); +	assert(patch < ARRAYSIZE(_patch)); + +	ChannelContext &channel = _channelCtx[kNumChannels]; +	channel.flags = (rightSide) ? ChannelContext::kFlagRightChannel : 0; +	channel.isAltered = false; +	channel.patch = &_patch[patch]; +	const int8 voiceIndex = noteOn(channel, note, (byte)volume, kPriorityNote); +	if (voiceIndex >= 0) +		_voiceCtx[voiceIndex].stopEventTime = duration << 8; +	return voiceIndex; +} + +bool MaxTrax::load(Common::SeekableReadStream &musicData, bool loadScores, bool loadSamples) { +	Common::StackLock lock(_mutex); +	stopMusic(); +	if (loadSamples) +		freePatches(); +	if (loadScores) +		freeScores(); +	const char *errorMsg = 0; +	// 0x0000: 4 Bytes Header "MXTX" +	// 0x0004: uint16 tempo +	// 0x0006: uint16 flags. bit0 = lowpassfilter, bit1 = attackvolume, bit15 = microtonal	 +	if (musicData.size() < 10 || musicData.readUint32BE() != 0x4D585458) { +		warning("Maxtrax: File is not a Maxtrax Module"); +		return false; +	} +	const uint16 songTempo = musicData.readUint16BE(); +	const uint16 flags = musicData.readUint16BE(); +	if (loadScores) { +		_playerCtx.tempoInitial = songTempo; +		_playerCtx.filterOn = (flags & 1) != 0; +		_playerCtx.handleVolume = (flags & 2) != 0; +	} + +	if (flags & (1 << 15)) { +		debug(5, "Maxtrax: Song has microtonal"); +#ifdef MAXTRAX_HAS_MICROTONAL +		if (loadScores) { +			for (int i = 0; i < ARRAYSIZE(_microtonal); ++i) +				_microtonal[i] = musicData.readUint16BE(); +		} else +			musicData.skip(128 * 2); +#else +		musicData.skip(128 * 2); +#endif +	} + +	int scoresLoaded = 0; +	// uint16 number of Scores +	const uint16 scoresInFile = musicData.readUint16BE(); + +	if (musicData.err() || musicData.eos()) +		goto ioError; + +	if (loadScores) { +		const uint16 tempScores = MIN(scoresInFile, _playerCtx.maxScoreNum); +		Score *curScore = new Score[tempScores]; +		if (!curScore) +			goto allocError; +		_scores = curScore; +		 +		for (scoresLoaded = 0; scoresLoaded < tempScores; ++scoresLoaded, ++curScore) { +			const uint32 numEvents = musicData.readUint32BE(); +			Event *curEvent = new Event[numEvents]; +			if (!curEvent) +				goto allocError; +			curScore->events = curEvent; +			for (int j = numEvents; j > 0; --j, ++curEvent) { +				curEvent->command = musicData.readByte(); +				curEvent->parameter = musicData.readByte(); +				curEvent->startTime = musicData.readUint16BE(); +				curEvent->stopTime = musicData.readUint16BE(); +			} +			curScore->numEvents = numEvents; +		} +		_numScores = scoresLoaded; +	} + +	if (loadSamples) { +		// skip over remaining scores in file +		for (int i = scoresInFile - scoresLoaded; i > 0; --i) +			musicData.skip(musicData.readUint32BE() * 6); + +		// uint16 number of Samples +		const uint16 wavesInFile = musicData.readUint16BE(); +		for (int i = wavesInFile; i > 0; --i) { +			// load disksample structure +			const uint16 number = musicData.readUint16BE(); +			assert(number < ARRAYSIZE(_patch)); + +			Patch &curPatch = _patch[number]; +			if (curPatch.attackPtr || curPatch.samplePtr) { +				delete curPatch.attackPtr; +				curPatch.attackPtr = 0; +				delete curPatch.samplePtr; +				curPatch.samplePtr = 0; +			} +			curPatch.tune = musicData.readSint16BE(); +			curPatch.volume = musicData.readUint16BE(); +			curPatch.sampleOctaves = musicData.readUint16BE(); +			curPatch.sampleAttackLen = musicData.readUint32BE(); +			const uint32 sustainLen = musicData.readUint32BE(); +			curPatch.sampleTotalLen = curPatch.sampleAttackLen + sustainLen; +			// each octave the number of samples doubles. +			const uint32 totalSamples = curPatch.sampleTotalLen * ((1 << curPatch.sampleOctaves) - 1); +			curPatch.attackLen = musicData.readUint16BE(); +			curPatch.releaseLen = musicData.readUint16BE(); +			const uint32 totalEnvs = curPatch.attackLen + curPatch.releaseLen; + +			// Allocate space for both attack and release Segment. +			Envelope *envPtr = new Envelope[totalEnvs]; +			if (!envPtr) +				goto allocError; +			// Attack Segment +			curPatch.attackPtr = envPtr; +			// Release Segment +			// curPatch.releasePtr = envPtr + curPatch.attackLen; + +			// Read Attack and Release Segments +			for (int j = totalEnvs; j > 0; --j, ++envPtr) { +				envPtr->duration = musicData.readUint16BE(); +				envPtr->volume = musicData.readUint16BE(); +			} + +			// read Samples +			int8 *allocSamples = new int8[totalSamples]; +			if (!allocSamples) +				goto allocError; +			curPatch.samplePtr = allocSamples; +			musicData.read(allocSamples, totalSamples); +		} +	} +	if (!musicData.err() && !musicData.eos()) +		return true; +ioError: +	errorMsg = "Maxtrax: Encountered IO-Error"; +allocError: +	if (!errorMsg) +		errorMsg = "Maxtrax: Could not allocate Memory"; + +	warning(errorMsg); +	if (loadSamples) +		freePatches(); +	if (loadScores) +		freeScores(); +	return false; +} + +#ifndef NDEBUG +void MaxTrax::outPutEvent(const Event &ev, int num) { +	struct { +		byte cmd; +		const char *name; +		const char *param; +	} COMMANDS[] = { +		{0x80, "TEMPO   ", "TEMPO, N/A      "}, +		{0xa0, "SPECIAL ", "CHAN, SPEC # | VAL"}, +		{0xb0, "CONTROL ", "CHAN, CTRL # | VAL"}, +		{0xc0, "PROGRAM ", "CHANNEL, PROG # "}, +		{0xe0, "BEND    ", "CHANNEL, BEND VALUE"}, +		{0xf0, "SYSEX   ", "TYPE, SIZE      "}, +		{0xf8, "REALTIME", "REALTIME, N/A   "}, +		{0xff, "END     ", "N/A, N/A        "}, +		{0xff, "NOTE    ", "VOL | CHAN, STOP"}, +	}; + +	int i = 0; +	for (; i < ARRAYSIZE(COMMANDS) - 1 && ev.command != COMMANDS[i].cmd; ++i) +		; + +	if (num == -1) +		debug("Event    : %02X %s %s %02X %04X %04X", ev.command, COMMANDS[i].name, COMMANDS[i].param, ev.parameter, ev.startTime, ev.stopTime); +	else +		debug("Event %3d: %02X %s %s %02X %04X %04X", num, ev.command, COMMANDS[i].name, COMMANDS[i].param, ev.parameter, ev.startTime, ev.stopTime); +} + +void MaxTrax::outPutScore(const Score &sc, int num) { +	if (num == -1) +		debug("score   : %i Events", sc.numEvents); +	else +		debug("score %2d: %i Events", num, sc.numEvents); +	for (uint i = 0; i < sc.numEvents; ++i) +		outPutEvent(sc.events[i], i); +	debug(""); +} +#else +void MaxTrax::outPutEvent(const Event &ev, int num) {} +void MaxTrax::outPutScore(const Score &sc, int num) {} +#endif	// #ifndef NDEBUG + +}	// End of namespace Audio + +#endif // #if defined(SOUND_MODS_MAXTRAX_H) diff --git a/sound/mods/maxtrax.h b/sound/mods/maxtrax.h new file mode 100644 index 0000000000..09016b08e7 --- /dev/null +++ b/sound/mods/maxtrax.h @@ -0,0 +1,224 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// see if all engines using this class are DISABLED +#if !defined(ENABLE_KYRA) + +// normal Header Guard +#elif !defined SOUND_MODS_MAXTRAX_H +#define SOUND_MODS_MAXTRAX_H + +// #define MAXTRAX_HAS_MODULATION +// #define MAXTRAX_HAS_MICROTONAL + +#include "sound/mods/paula.h" + +namespace Audio { + +class MaxTrax : public Paula { +public: +	MaxTrax(int rate, bool stereo, uint16 vBlankFreq = 50, uint16 maxScores = 128); +	virtual ~MaxTrax(); + +	bool load(Common::SeekableReadStream &musicData, bool loadScores = true, bool loadSamples = true); +	bool playSong(int songIndex, bool loop = false); +	void advanceSong(int advance = 1); +	int playNote(byte note, byte patch, uint16 duration, uint16 volume, bool rightSide); +	void setVolume(const byte volume) { Common::StackLock lock(_mutex); _playerCtx.volume = volume; } +	void setTempo(const uint16 tempo); +	void stopMusic(); +	/** +	 * Set a callback function for sync-events. +	 * @param callback Callback function, will be called synchronously, so DONT modify the player  +	 *		directly in response +	 */ +	void setSignalCallback(void (*callback) (int)); + +protected: +	void interrupt(); + +private: +	enum { kNumPatches = 64, kNumVoices = 4, kNumChannels = 16, kNumExtraChannels = 1 }; +	enum { kPriorityScore, kPriorityNote, kPrioritySound }; + +#ifdef MAXTRAX_HAS_MICROTONAL +	int16	_microtonal[128]; +#endif + +	struct Event { +		uint16	startTime; +		uint16	stopTime; +		byte	command; +		byte	parameter; +	}; + +	const struct Score { +		const Event	*events; +		uint32	numEvents; +	} *_scores; + +	int _numScores; + +	struct { +		uint32	sineValue; +		uint16	vBlankFreq; +		int32	ticks; +		int32	tickUnit; +		uint16	frameUnit; + +		uint16	maxScoreNum; +		uint16	tempo; +		uint16	tempoInitial; +		uint16	tempoStart; +		int16	tempoDelta; +		int32	tempoTime; +		int32	tempoTicks; + +		byte	volume; + +		bool	filterOn; +		bool	handleVolume; +		bool	musicLoop; + +		int		scoreIndex; +		const Event	*nextEvent; +		int32	nextEventTime; + +		void (*syncCallBack) (int); +		const Event	*repeatPoint[4]; +		byte	repeatCount[4]; +	} _playerCtx; + +	struct Envelope { +		uint16	duration; +		uint16	volume; +	}; + +	struct Patch { +		const Envelope *attackPtr; +		//Envelope *releasePtr; +		uint16	attackLen; +		uint16	releaseLen; + +		int16	tune; +		uint16	volume; + +		// this was the SampleData struct in the assembler source +		const int8	*samplePtr; +		uint32	sampleTotalLen; +		uint32	sampleAttackLen; +		uint16	sampleOctaves; +	} _patch[kNumPatches]; + +	struct ChannelContext { +		const Patch	*patch; +		uint16	regParamNumber; + +		uint16	modulation; +		uint16	modulationTime; + +		int16	microtonal; + +		uint16	portamentoTime; + +		int16	pitchBend; +		int16	pitchReal; +		int8	pitchBendRange; + +		uint8	volume; +//		uint8	voicesActive; + +		enum { +			kFlagRightChannel = 1 << 0, +			kFlagPortamento = 1 << 1, +			kFlagDamper = 1 << 2, +			kFlagMono = 1 << 3, +			kFlagMicrotonal = 1 << 4, +			kFlagModVolume = 1 << 5 +		}; +		byte	flags; +		bool	isAltered; + +		uint8	lastNote; +//		uint8	program; + +	} _channelCtx[kNumChannels + kNumExtraChannels]; + +	struct VoiceContext { +		ChannelContext *channel; +		const Patch	*patch; +		const Envelope *envelope; +//		uint32	uinqueId; +		int32	preCalcNote; +		uint32	ticksLeft; +		int32	portaTicks; +		int32	incrVolume; +//		int32	periodOffset; +		uint16	envelopeLeft; +		uint16	noteVolume; +		uint16	baseVolume; +		uint16	lastPeriod; +		byte	baseNote; +		byte	endNote; +		byte	octave; +//		byte	number; +//		byte	link; +		byte	priority; +		enum { +			kStatusFree, +			kStatusHalt, +			kStatusDecay, +			kStatusRelease, +			kStatusSustain, +			kStatusAttack, +			kStatusStart +		}; +		byte	status; +		bool	hasDamper; +		bool	isBlocked; +		byte	lastVolume; +		bool	hasPortamento; +		byte	dmaOff; + +		int32	stopEventTime; +	} _voiceCtx[kNumVoices]; + +	void controlCh(ChannelContext &channel, byte command, byte data); +	void freePatches(); +	void freeScores(); +	void resetChannel(ChannelContext &chan, bool rightChannel); +	void resetPlayer(); + +	static int8 pickvoice(const VoiceContext voice[4], uint pick, int16 pri); +	uint16 calcNote(const VoiceContext &voice); +	int8 noteOn(ChannelContext &channel, byte note, uint16 volume, uint16 pri); +	void killVoice(byte num); + +	static void outPutEvent(const Event &ev, int num = -1); +	static void outPutScore(const Score &sc, int num = -1); +}; +}	// End of namespace Audio + +#endif // !defined SOUND_MODS_MAXTRAX_H diff --git a/sound/mods/paula.cpp b/sound/mods/paula.cpp index 545390ff93..1f557e0ece 100644 --- a/sound/mods/paula.cpp +++ b/sound/mods/paula.cpp @@ -27,19 +27,20 @@  namespace Audio { -Paula::Paula(bool stereo, int rate, int interruptFreq) : -		_stereo(stereo), _rate(rate), _intFreq(interruptFreq) { +Paula::Paula(bool stereo, int rate, uint interruptFreq) : +		_stereo(stereo), _rate(rate), _periodScale((double)kPalPaulaClock / rate), _intFreq(interruptFreq) {  	clearVoices(); -	_voice[0].panning = 63; -	_voice[1].panning = 191; -	_voice[2].panning = 191; -	_voice[3].panning = 63; +	_voice[0].panning = 191; +	_voice[1].panning = 63; +	_voice[2].panning = 63; +	_voice[3].panning = 191; -	if (_intFreq <= 0) +	if (_intFreq == 0)  		_intFreq = _rate; -	_curInt = _intFreq; +	_curInt = 0; +	_timerBase = 1;  	_playing = false;  	_end = true;  } @@ -57,6 +58,7 @@ void Paula::clearVoice(byte voice) {  	_voice[voice].period = 0;  	_voice[voice].volume = 0;  	_voice[voice].offset = 0; +	_voice[voice].dmaCount = 0;  }  int Paula::readBuffer(int16 *buffer, const int numSamples) { @@ -95,18 +97,17 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) {  		// Handle 'interrupts'. This gives subclasses the chance to adjust the channel data  		// (e.g. insert new samples, do pitch bending, whatever). -		if (_curInt == _intFreq) { +		if (_curInt == 0) { +			_curInt = _intFreq;  			interrupt(); -			_curInt = 0;  		}  		// Compute how many samples to generate: at most the requested number of samples,  		// of course, but we may stop earlier when an 'interrupt' is expected. -		const int nSamples = MIN(samples, _intFreq - _curInt); +		const uint nSamples = MIN((uint)samples, _curInt);  		// Loop over the four channels of the emulated Paula chip  		for (int voice = 0; voice < NUM_VOICES; voice++) { -  			// No data, or paused -> skip channel  			if (!_voice[voice].data || (_voice[voice].period <= 0))  				continue; @@ -115,8 +116,7 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) {  			// the requested output sampling rate (typicall 44.1 kHz or 22.05 kHz)  			// as well as the "period" of the channel we are processing right now,  			// to compute the correct output 'rate'. -			const double frequency = (7093789.2 / 2.0) / _voice[voice].period; -			frac_t rate = doubleToFrac(frequency / _rate); +			frac_t rate = doubleToFrac(_periodScale / _voice[voice].period);  			// Cap the volume  			_voice[voice].volume = MIN((byte) 0x40, _voice[voice].volume); @@ -126,50 +126,67 @@ int Paula::readBufferIntern(int16 *buffer, const int numSamples) {  			frac_t offset = _voice[voice].offset;  			frac_t sLen = intToFrac(_voice[voice].length);  			const int8 *data = _voice[voice].data; +			int dmaCount = _voice[voice].dmaCount;  			int16 *p = buffer;  			int end = 0;  			int neededSamples = nSamples; +			assert(offset < sLen);  			// Compute the number of samples to generate; that is, either generate  			// just as many as were requested, or until the buffer is used up.  			// Note that dividing two frac_t yields an integer (as the denominators  			// cancel out each other).  			// Note that 'end' could be 0 here. No harm in that :-). -			end = MIN(neededSamples, (int)((sLen - offset + rate - 1) / rate)); +			const int leftSamples = (int)((sLen - offset + rate - 1) / rate); +			end = MIN(neededSamples, leftSamples);  			mixBuffer<stereo>(p, data, offset, rate, end, _voice[voice].volume, _voice[voice].panning);  			neededSamples -= end; -			// If we have not yet generated enough samples, and looping is active: loop! -			if (neededSamples > 0 && _voice[voice].lengthRepeat > 2) { - -				// At this point we know that we have used up all samples in the buffer, so reset it. -				_voice[voice].data = data = _voice[voice].dataRepeat; +			if (leftSamples > 0 && end == leftSamples) { +				dmaCount++; +				data = _voice[voice].data = _voice[voice].dataRepeat;  				_voice[voice].length = _voice[voice].lengthRepeat; +				// TODO: offset -= sLen; but make sure there is no way offset >= 2*sLen +				offset &= FRAC_LO_MASK; +			} + +			// If we have not yet generated enough samples, and looping is active: loop! +			if (neededSamples > 0 && _voice[voice].length > 2) {  				sLen = intToFrac(_voice[voice].length);  				// If the "rate" exceeds the sample rate, we would have to perform constant  				// wrap arounds. So, apply the first step of the euclidean algorithm to  				// achieve the same more efficiently: Take rate modulo sLen +				// TODO: This messes up dmaCount and shouldnt happen?  				if (sLen < rate) -					rate %= sLen; +					warning("Paula: length %d is lesser than rate", _voice[voice].length); +//					rate %= sLen;  				// Repeat as long as necessary.  				while (neededSamples > 0) { -					offset = 0; - +					// TODO: offset -= sLen; but make sure there is no way offset >= 2*sLen +					offset &= FRAC_LO_MASK; +					dmaCount++;  					// Compute the number of samples to generate (see above) and mix 'em.  					end = MIN(neededSamples, (int)((sLen - offset + rate - 1) / rate));  					mixBuffer<stereo>(p, data, offset, rate, end, _voice[voice].volume, _voice[voice].panning);  					neededSamples -= end;  				} + +				if (offset < sLen) +					dmaCount--; +				else +					offset &= FRAC_LO_MASK; +  			}  			// Write back the cached data  			_voice[voice].offset = offset; +			_voice[voice].dmaCount = dmaCount;  		}  		buffer += _stereo ? nSamples * 2 : nSamples; -		_curInt += nSamples; +		_curInt -= nSamples;  		samples -= nSamples;  	}  	return numSamples; diff --git a/sound/mods/paula.h b/sound/mods/paula.h index e3c6002451..647dc5fe56 100644 --- a/sound/mods/paula.h +++ b/sound/mods/paula.h @@ -40,12 +40,29 @@ namespace Audio {  class Paula : public AudioStream {  public:  	static const int NUM_VOICES = 4; +	enum { +		kPalSystemClock  = 7093790, +		kNtscSystemClock = 7159090, +		kPalCiaClock     = kPalSystemClock / 10, +		kNtscCiaClock    = kNtscSystemClock / 10, +		kPalPaulaClock   = kPalSystemClock / 2, +		kNtscPauleClock  = kNtscSystemClock / 2 +	}; -	Paula(bool stereo = false, int rate = 44100, int interruptFreq = 0); +	Paula(bool stereo = false, int rate = 44100, uint interruptFreq = 0);  	~Paula();  	bool playing() const { return _playing; } -	void setInterruptFreq(int freq) { _curInt = _intFreq = freq; } +	void setTimerBaseValue( uint32 ticksPerSecond ) { _timerBase = ticksPerSecond; } +	uint32 getTimerBaseValue() { return _timerBase; } +	void setSingleInterrupt(uint sampleDelay) { assert(sampleDelay < _intFreq); _curInt = sampleDelay; } +	void setSingleInterruptUnscaled(uint timerDelay) {  +		setSingleInterrupt((uint)(((double)timerDelay * getRate()) / _timerBase)); +	} +	void setInterruptFreq(uint sampleDelay) { _intFreq = sampleDelay; _curInt = 0; } +	void setInterruptFreqUnscaled(uint timerDelay) {  +		setInterruptFreq((uint)(((double)timerDelay * getRate()) / _timerBase)); +	}  	void clearVoice(byte voice);  	void clearVoices() { for (int i = 0; i < NUM_VOICES; ++i) clearVoice(i); }  	void startPlay(void) { _playing = true; } @@ -68,6 +85,7 @@ protected:  		byte volume;  		frac_t offset;  		byte panning; // For stereo mixing: 0 = far left, 255 = far right +		int dmaCount;  	};  	bool _end; @@ -90,6 +108,21 @@ protected:  		_voice[channel].panning = panning;  	} +	void disableChannel(byte channel) { +		assert(channel < NUM_VOICES); +		_voice[channel].data = 0; +	} + +	void enableChannel(byte channel) { +		assert(channel < NUM_VOICES); +		Channel &ch = _voice[channel]; +		ch.data = ch.dataRepeat; +		ch.length = ch.lengthRepeat; +		// actually first 2 bytes are dropped? +		ch.offset = intToFrac(0); +		// ch.period = ch.periodRepeat; +	} +  	void setChannelPeriod(byte channel, int16 period) {  		assert(channel < NUM_VOICES);  		_voice[channel].period = period; @@ -100,6 +133,17 @@ protected:  		_voice[channel].volume = volume;  	} +	void setChannelSampleStart(byte channel, const int8 *data) { +		assert(channel < NUM_VOICES); +		_voice[channel].dataRepeat = data; +	} + +	void setChannelSampleLen(byte channel, uint32 length) { +		assert(channel < NUM_VOICES); +		assert(length < 32768/2); +		_voice[channel].lengthRepeat = 2 * length; +	} +  	void setChannelData(uint8 channel, const int8 *data, const int8 *dataRepeat, uint32 length, uint32 lengthRepeat, int32 offset = 0) {  		assert(channel < NUM_VOICES); @@ -110,11 +154,14 @@ protected:  		assert(lengthRepeat < 32768);  		Channel &ch = _voice[channel]; -		ch.data = data; + +		ch.dataRepeat = data; +		ch.lengthRepeat = length; +		enableChannel(channel); +		ch.offset = intToFrac(offset); +  		ch.dataRepeat = dataRepeat; -		ch.length = length;  		ch.lengthRepeat = lengthRepeat; -		ch.offset = intToFrac(offset);  	}  	void setChannelOffset(byte channel, frac_t offset) { @@ -128,13 +175,29 @@ protected:  		return _voice[channel].offset;  	} +	int getChannelDmaCount(byte channel) { +		assert(channel < NUM_VOICES); +		return _voice[channel].dmaCount; +	} + +	void setChannelDmaCount(byte channel, int dmaVal = 0) { +		assert(channel < NUM_VOICES); +		_voice[channel].dmaCount = dmaVal; +	} + +	void setAudioFilter(bool enable) { +		// TODO: implement +	} +  private:  	Channel _voice[NUM_VOICES];  	const bool _stereo;  	const int _rate; -	int _intFreq; -	int _curInt; +	const double _periodScale; +	uint _intFreq; +	uint _curInt; +	uint32 _timerBase;  	bool _playing;  	template<bool stereo> diff --git a/sound/mods/soundfx.cpp b/sound/mods/soundfx.cpp index 101d8a077d..3af8ca19c6 100644 --- a/sound/mods/soundfx.cpp +++ b/sound/mods/soundfx.cpp @@ -46,8 +46,7 @@ public:  	enum {  		NUM_CHANNELS = 4, -		NUM_INSTRUMENTS = 15, -		CIA_FREQ = 715909 +		NUM_INSTRUMENTS = 15  	};  	SoundFx(int rate, bool stereo); @@ -75,12 +74,12 @@ protected:  	uint16 _curPos;  	uint8 _ordersTable[128];  	uint8 *_patternData; -	int _eventsFreq;  	uint16 _effects[NUM_CHANNELS];  };  SoundFx::SoundFx(int rate, bool stereo)  	: Paula(stereo, rate) { +	setTimerBaseValue(kPalCiaClock);  	_ticks = 0;  	_delay = 0;  	memset(_instruments, 0, sizeof(_instruments)); @@ -89,7 +88,6 @@ SoundFx::SoundFx(int rate, bool stereo)  	_curPos = 0;  	memset(_ordersTable, 0, sizeof(_ordersTable));  	_patternData = 0; -	_eventsFreq = 0;  	memset(_effects, 0, sizeof(_effects));  } @@ -167,8 +165,7 @@ void SoundFx::play() {  	_curPos = 0;  	_curOrder = 0;  	_ticks = 0; -	_eventsFreq = CIA_FREQ / _delay; -	setInterruptFreq(getRate() / _eventsFreq); +	setInterruptFreqUnscaled(_delay);  	startPaula();  } @@ -252,7 +249,7 @@ void SoundFx::handleTick() {  }  void SoundFx::disablePaulaChannel(uint8 channel) { -	setChannelPeriod(channel, 0); +	disableChannel(channel);  }  void SoundFx::setupPaulaChannel(uint8 channel, const int8 *data, uint16 len, uint16 repeatPos, uint16 repeatLen) { diff --git a/sound/mods/tfmx.cpp b/sound/mods/tfmx.cpp new file mode 100644 index 0000000000..ca6c277736 --- /dev/null +++ b/sound/mods/tfmx.cpp @@ -0,0 +1,1189 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/scummsys.h" +#include "common/endian.h" +#include "common/stream.h" +#include "common/util.h" +#include "common/debug.h" + +#include "sound/mods/tfmx.h" + +// test for engines using this class. +#if defined(SOUND_MODS_TFMX_H) + +// couple debug-functions +namespace { +	void displayPatternstep(const void *const vptr); +	void displayMacroStep(const void *const vptr); + +	const uint16 noteIntervalls[64] = { +	1710, 1614, 1524, 1438, 1357, 1281, 1209, 1141, 1077, 1017,  960,  908, +	 856,  810,  764,  720,  680,  642,  606,  571,  539,  509,  480,  454, +	 428,  404,  381,  360,  340,  320,  303,  286,  270,  254,  240,  227, +	 214,  202,  191,  180,  170,  160,  151,  143,  135,  127,  120,  113, +	 214,  202,  191,  180,  170,  160,  151,  143,  135,  127,  120,  113, +	 214,  202,  191,  180 }; +} + +namespace Audio { + +Tfmx::Tfmx(int rate, bool stereo) +	: Paula(stereo, rate),  +	  _resource(),  +	  _resourceSample(),  +	  _playerCtx(), +	  _deleteResource(false) { + +	_playerCtx.stopWithLastPattern = false; + +	for (int i = 0; i < kNumVoices; ++i)  +		_channelCtx[i].paulaChannel = (byte)i; + +	_playerCtx.volume = 0x40; +	_playerCtx.patternSkip = 6; +	stopSongImpl(); + +	setTimerBaseValue(kPalCiaClock); +	setInterruptFreqUnscaled(kPalDefaultCiaVal); +} + +Tfmx::~Tfmx() { +	freeResourceDataImpl(); +} + +void Tfmx::interrupt() { +	assert(!_end); +	++_playerCtx.tickCount; + +	for (int i = 0; i < kNumVoices; ++i) { +		if (_channelCtx[i].dmaIntCount) { +			// wait for DMA Interupts to happen +			int doneDma = getChannelDmaCount(i); +			if (doneDma >= _channelCtx[i].dmaIntCount) { +				_channelCtx[i].dmaIntCount = 0; +				_channelCtx[i].macroRun = true; +			} +		} +	} + +	for (int i = 0; i < kNumVoices; ++i) { +		ChannelContext &channel = _channelCtx[i]; + +		if (channel.sfxLockTime >= 0) +			--channel.sfxLockTime; +		else { +			channel.sfxLocked = false; +			channel.customMacroPrio = 0; +		} + +		// externally queued macros +		if (channel.customMacro) { +			const byte *const noteCmd = (const byte *)&channel.customMacro; +			channel.sfxLocked = false; +			noteCommand(noteCmd[0], noteCmd[1], (noteCmd[2] & 0xF0) | (uint8)i, noteCmd[3]); +			channel.customMacro = 0; +			channel.sfxLocked = (channel.customMacroPrio != 0); +		} + +		// apply timebased effects on Parameters +		if (channel.macroSfxRun > 0) +			effects(channel); + +		// see if we have to run the macro-program +		if (channel.macroRun) { +			if (!channel.macroWait) +				macroRun(channel); +			else +				--channel.macroWait; +		} + +		Paula::setChannelPeriod(i, channel.period); +		if (channel.macroSfxRun >= 0) +			channel.macroSfxRun = 1; + +		// TODO: handling pending DMAOff? +	} + +	// Patterns are only processed each _playerCtx.timerCount + 1 tick +	if (_playerCtx.song >= 0 && !_playerCtx.patternCount--) { +		_playerCtx.patternCount = _playerCtx.patternSkip; +		advancePatterns(); +	} +} + +void Tfmx::effects(ChannelContext &channel) { +	// addBegin +	if (channel.addBeginLength) { +		channel.sampleStart += channel.addBeginDelta; +		Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); +		if (!(--channel.addBeginCount)) { +			channel.addBeginCount = channel.addBeginLength; +			channel.addBeginDelta = -channel.addBeginDelta; +		} +	} + +	// vibrato +	if (channel.vibLength) { +		channel.vibValue += channel.vibDelta; +		if (--channel.vibCount == 0) { +			channel.vibCount = channel.vibLength; +			channel.vibDelta = -channel.vibDelta; +		} +		if (!channel.portaDelta) { +			// 16x16 bit multiplication, casts needed for the right results +			channel.period = (uint16)(((uint32)channel.refPeriod * (uint16)((1 << 11) + channel.vibValue)) >> 11); +		} +	} + +	// portamento +	if (channel.portaDelta && !(--channel.portaCount)) { +		channel.portaCount = channel.portaSkip; + +		bool resetPorta = true; +		const uint16 period = channel.refPeriod; +		uint16 portaVal = channel.portaValue; + +		if (period > portaVal) { +			portaVal = ((uint32)portaVal * (uint16)((1 << 8) + channel.portaDelta)) >> 8; +			resetPorta = (period <= portaVal); + +		} else if (period < portaVal) { +			portaVal = ((uint32)portaVal * (uint16)((1 << 8) - channel.portaDelta)) >> 8; +			resetPorta = (period >= portaVal); +		} + +		if (resetPorta) { +			channel.portaDelta = 0; +			channel.portaValue = period & 0x7FF; +		} else +			channel.period = channel.portaValue = portaVal & 0x7FF; +	} + +	// envelope +	if (channel.envSkip && !channel.envCount--) { +		channel.envCount = channel.envSkip; + +		const int8 endVol = channel.envEndVolume; +		int8 volume = channel.volume; +		bool resetEnv = true; + +		if (endVol > volume) { +			volume += channel.envDelta; +			resetEnv = endVol <= volume; +		} else { +			volume -= channel.envDelta; +			resetEnv = volume <= 0 || endVol >= volume; +		} + +		if (resetEnv) { +			channel.envSkip = 0; +			volume = endVol; +		} +		channel.volume = volume; +	} + +	// Fade +	if (_playerCtx.fadeDelta && !(--_playerCtx.fadeCount)) { +		_playerCtx.fadeCount = _playerCtx.fadeSkip; + +		_playerCtx.volume += _playerCtx.fadeDelta; +		if (_playerCtx.volume == _playerCtx.fadeEndVolume) +			_playerCtx.fadeDelta = 0; +	} + +	// Volume +	const uint8 finVol = _playerCtx.volume * channel.volume >> 6; +	Paula::setChannelVolume(channel.paulaChannel, finVol); +} + +void Tfmx::macroRun(ChannelContext &channel) { +	bool deferWait = channel.deferWait; +	for (;;) { +		const byte *const macroPtr = (const byte *)(getMacroPtr(channel.macroOffset) + channel.macroStep); +		++channel.macroStep; + +		switch (macroPtr[0]) { +		case 0x00:	// Reset + DMA Off. Parameters: deferWait, addset, vol +			clearEffects(channel); +			// FT +		case 0x13:	// DMA Off. Parameters:  deferWait, addset, vol +			// TODO: implement PArameters +			Paula::disableChannel(channel.paulaChannel); +			channel.deferWait = deferWait = (macroPtr[1] != 0); +			if (deferWait) { +				// if set, then we expect a DMA On in the same tick. +				channel.period = 4; +				//Paula::setChannelPeriod(channel.paulaChannel, channel.period); +				Paula::setChannelSampleLen(channel.paulaChannel, 1); +				// in this state we then need to allow some commands that normally +				// would halt the macroprogamm to continue instead. +				// those commands are: Wait, WaitDMA, AddPrevNote, AddNote, SetNote, <unknown Cmd> +				// DMA On is affected aswell +				// TODO remember time disabled, remember pending dmaoff?. +			} + +			if (macroPtr[2] || macroPtr[3]) { +				channel.volume = (macroPtr[2] ? 0 : channel.relVol * 3) + macroPtr[3]; +				Paula::setChannelVolume(channel.paulaChannel, channel.volume); +			} +			continue; + +		case 0x01:	// DMA On +			// TODO: Parameter macroPtr[1] - en-/disable effects +			channel.dmaIntCount = 0; +			if (deferWait) { +				// TODO +				// there is actually a small delay in the player, but I think that +				// only allows to clear DMA-State on real Hardware +			} +			Paula::setChannelPeriod(channel.paulaChannel, channel.period); +			Paula::enableChannel(channel.paulaChannel); +			channel.deferWait = deferWait = false; +			continue; + +		case 0x02:	// Set Beginn. Parameters: SampleOffset(L) +			channel.addBeginLength = 0; +			channel.sampleStart = READ_BE_UINT32(macroPtr) & 0xFFFFFF; +			Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); +			continue; + +		case 0x03:	// SetLength. Parameters: SampleLength(W) +			channel.sampleLen = READ_BE_UINT16(¯oPtr[2]); +			Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen); +			continue; + +		case 0x04:	// Wait. Parameters: Ticks to wait(W). +			// TODO: some unkown Parameter? (macroPtr[1] & 1) +			channel.macroWait = READ_BE_UINT16(¯oPtr[2]); +			break; + +		case 0x10:	// Loop Key Up. Parameters: Loopcount, MacroStep(W) +			if (channel.keyUp) +				continue; +			// FT +		case 0x05:	// Loop. Parameters: Loopcount, MacroStep(W) +			if (channel.macroLoopCount != 0) { +				if (channel.macroLoopCount == 0xFF) +					channel.macroLoopCount = macroPtr[1]; +				channel.macroStep = READ_BE_UINT16(¯oPtr[2]); +			} +			--channel.macroLoopCount; +			continue; + +		case 0x06:	// Jump. Parameters: MacroIndex, MacroStep(W) +			// channel.macroIndex = macroPtr[1] & (kMaxMacroOffsets - 1); +			channel.macroOffset = _resource->macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)]; +			channel.macroStep = READ_BE_UINT16(¯oPtr[2]); +			channel.macroLoopCount = 0xFF; +			continue; + +		case 0x07:	// Stop Macro +			channel.macroRun = false; +			--channel.macroStep; +			return; + +		case 0x08:	// AddNote. Parameters: Note, Finetune(W) +			setNoteMacro(channel, channel.note + macroPtr[1], READ_BE_UINT16(¯oPtr[2])); +			break; + +		case 0x09:	// SetNote. Parameters: Note, Finetune(W) +			setNoteMacro(channel, macroPtr[1], READ_BE_UINT16(¯oPtr[2])); +			break; + +		case 0x0A:	// Clear Effects +			clearEffects(channel); +			continue; + +		case 0x0B:	// Portamento. Parameters: count, speed +			channel.portaSkip = macroPtr[1]; +			channel.portaCount = 1; +			// if porta is already running, then keep using old value +			if (!channel.portaDelta) +				channel.portaValue = channel.refPeriod; +			channel.portaDelta = READ_BE_UINT16(¯oPtr[2]); +			continue; + +		case 0x0C:	// Vibrato. Parameters: Speed, intensity +			channel.vibLength = macroPtr[1]; +			channel.vibCount = macroPtr[1] / 2; +			channel.vibDelta = macroPtr[3]; +			// TODO: Perhaps a bug, vibValue could be left uninitialised +			if (!channel.portaDelta) { +				channel.period = channel.refPeriod; +				channel.vibValue = 0; +			} +			continue; + +		case 0x0D:	// Add Volume. Parameters: note, addNoteFlag, volume +			if (macroPtr[2] == 0xFE) +				setNoteMacro(channel, channel.note + macroPtr[1], 0); +			channel.volume = channel.relVol * 3 + macroPtr[3]; +			continue; + +		case 0x0E:	// Set Volume. Parameters: note, addNoteFlag, volume +			if (macroPtr[2] == 0xFE) +				setNoteMacro(channel, channel.note + macroPtr[1], 0); +			channel.volume = macroPtr[3]; +			continue; + +		case 0x0F:	// Envelope. Parameters: speed, count, endvol +			channel.envDelta = macroPtr[1]; +			channel.envCount = channel.envSkip = macroPtr[2]; +			channel.envEndVolume = macroPtr[3]; +			continue; + +		case 0x11:	// Add Beginn. Parameters: times, Offset(W) +			channel.addBeginLength = channel.addBeginCount = macroPtr[1]; +			channel.addBeginDelta = (int16)READ_BE_UINT16(¯oPtr[2]); +			channel.sampleStart += channel.addBeginDelta; +			Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); +			continue; + +		case 0x12:	// Add Length. Parameters: added Length(W) +			channel.sampleLen += (int16)READ_BE_UINT16(¯oPtr[2]); +			Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen); +			continue; + +		case 0x14:	// Wait key up. Parameters: wait cycles +			if (channel.keyUp || channel.macroLoopCount == 0) { +				channel.macroLoopCount = 0xFF; +				continue; +			} else if (channel.macroLoopCount == 0xFF) +				channel.macroLoopCount = macroPtr[3]; +			--channel.macroLoopCount; +			--channel.macroStep; +			return; + +		case 0x15:	// Subroutine. Parameters: MacroIndex, Macrostep(W) +			channel.macroReturnOffset = channel.macroOffset; +			channel.macroReturnStep = channel.macroStep; + +			channel.macroOffset = _resource->macroOffset[macroPtr[1] & (kMaxMacroOffsets - 1)]; +			channel.macroStep = READ_BE_UINT16(¯oPtr[2]); +			// TODO: MI does some weird stuff there. Figure out which varioables need to be set +			continue; + +		case 0x16:	// Return from Sub. +			channel.macroOffset = channel.macroReturnOffset; +			channel.macroStep = channel.macroReturnStep; +			continue; + +		case 0x17:	// Set Period. Parameters: Period(W) +			channel.refPeriod = READ_BE_UINT16(¯oPtr[2]); +			if (!channel.portaDelta) { +				channel.period = channel.refPeriod; +				//Paula::setChannelPeriod(channel.paulaChannel, channel.period); +			} +			continue; + +		case 0x18: {	// Sampleloop. Parameters: Offset from Samplestart(W) +			// TODO: MI loads 24 bit, but thats useless? +			const uint16 temp = /* ((int8)macroPtr[1] << 16) | */ READ_BE_UINT16(¯oPtr[2]); +			if (macroPtr[1] || (temp & 1)) +				warning("Tfmx: Problematic value for sampleloop: %06X", (macroPtr[1] << 16) | temp); +			channel.sampleStart += temp & 0xFFFE; +			channel.sampleLen -= (temp / 2) /* & 0x7FFF */; +			Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(channel.sampleStart)); +			Paula::setChannelSampleLen(channel.paulaChannel, channel.sampleLen); +			continue; +		} +		case 0x19:	// Set One-Shot Sample +			channel.addBeginLength = 0; +			channel.sampleStart = 0; +			channel.sampleLen = 1; +			Paula::setChannelSampleStart(channel.paulaChannel, getSamplePtr(0)); +			Paula::setChannelSampleLen(channel.paulaChannel, 1); +			continue; + +		case 0x1A:	// Wait on DMA. Parameters: Cycles-1(W) to wait +			channel.dmaIntCount = READ_BE_UINT16(¯oPtr[2]) + 1; +			channel.macroRun = false; +			Paula::setChannelDmaCount(channel.paulaChannel); +			break; + +/*		case 0x1B:	// Random play. Parameters: macro/speed/mode +			warnMacroUnimplemented(macroPtr, 0); +			continue;*/ + +		case 0x1C:	// Branch on Note. Parameters: note/macrostep(W) +			if (channel.note > macroPtr[1]) +				channel.macroStep = READ_BE_UINT16(¯oPtr[2]); +			continue; + +		case 0x1D:	// Branch on Volume. Parameters: volume/macrostep(W) +			if (channel.volume > macroPtr[1]) +				channel.macroStep = READ_BE_UINT16(¯oPtr[2]); +			continue; + +/*		case 0x1E:	// Addvol+note. Parameters: note/CONST./volume +			warnMacroUnimplemented(macroPtr, 0); +			continue;*/ + +		case 0x1F:	// AddPrevNote. Parameters: Note, Finetune(W) +			setNoteMacro(channel, channel.prevNote + macroPtr[1], READ_BE_UINT16(¯oPtr[2])); +			break; + +		case 0x20:	// Signal. Parameters: signalnumber, value(W) +			if (_playerCtx.numSignals > macroPtr[1]) +				_playerCtx.signal[macroPtr[1]] = READ_BE_UINT16(¯oPtr[2]); +			continue; + +		case 0x21:	// Play macro. Parameters: macro, chan, detune +			noteCommand(channel.note, macroPtr[1], (channel.relVol << 4) | macroPtr[2], macroPtr[3]); +			continue; + +		// 0x22 - 0x29 are used by Gem`X +		// 0x30 - 0x34 are used by Carribean Disaster + +		default: +			debug(3, "Tfmx: Macro %02X not supported", macroPtr[0]); +		} +		if (!deferWait) +			return; +	} +} + +void Tfmx::advancePatterns() { +startPatterns: +	int runningPatterns = 0; + +	for (int i = 0; i < kNumChannels; ++i) { +		PatternContext &pattern = _patternCtx[i]; +		const uint8 pattCmd = pattern.command; +		if (pattCmd < 0x90) {	// execute Patternstep +			++runningPatterns; +			if (!pattern.wait) { +				// issue all Steps for this tick +				if (patternRun(pattern)) { +					// we load the next Trackstep Command and then process all Channels again +					if (trackRun(true)) +						goto startPatterns; +					else +						break; +				} + +			} else  +				--pattern.wait; + +		} else if (pattCmd == 0xFE) {	// Stop voice in pattern.expose +			pattern.command = 0xFF; +			ChannelContext &channel = _channelCtx[pattern.expose & (kNumVoices - 1)]; +			if (!channel.sfxLocked) { +				haltMacroProgramm(channel); +				Paula::disableChannel(channel.paulaChannel); +			} +		} // else this pattern-Channel is stopped +	} +	if (_playerCtx.stopWithLastPattern && !runningPatterns) { +		stopPaula(); +	} +} + +bool Tfmx::patternRun(PatternContext &pattern) { +	for (;;) { +		const byte *const patternPtr = (const byte *)(getPatternPtr(pattern.offset) + pattern.step); +		++pattern.step; +		const byte pattCmd = patternPtr[0]; + +		if (pattCmd < 0xF0) { // Playnote +			bool doWait = false; +			byte noteCmd = pattCmd + pattern.expose; +			byte param3  = patternPtr[3]; +			if (pattCmd < 0xC0) {	// Note +				if (pattCmd >= 0x80) {	// Wait +					pattern.wait = param3; +					param3 = 0; +					doWait = true; +				} +				noteCmd &= 0x3F; +			}	// else Portamento  +			noteCommand(noteCmd, patternPtr[1], patternPtr[2], param3); +			if (doWait) +				return false; + +		} else {	// Patterncommand +			switch (pattCmd & 0xF) { +			case 0: 	// End Pattern + Next Trackstep +				pattern.command = 0xFF; +				--pattern.step; +				return true; + +			case 1: 	// Loop Pattern. Parameters: Loopcount, PatternStep(W) +				if (pattern.loopCount != 0) { +					if (pattern.loopCount == 0xFF) +						pattern.loopCount = patternPtr[1]; +					pattern.step = READ_BE_UINT16(&patternPtr[2]); +				} +				--pattern.loopCount; +				continue; + +			case 2: 	// Jump. Parameters: PatternIndex, PatternStep(W) +				pattern.offset = _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]; +				pattern.step = READ_BE_UINT16(&patternPtr[2]); +				continue; + +			case 3: 	// Wait. Paramters: ticks to wait +				pattern.wait = patternPtr[1]; +				return false; + +			case 14: 	// Stop custompattern +				// TODO apparently toggles on/off pattern channel 7 +				debug(3, "Tfmx: Encountered 'Stop custompattern' command"); +				// FT +			case 4: 	// Stop this pattern +				pattern.command = 0xFF; +				--pattern.step; +				// TODO: try figuring out if this was the last Channel? +				return false; + +			case 5: 	// Key Up Signal. Paramters: channel +				if (!_channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked) +					_channelCtx[patternPtr[2] & (kNumVoices - 1)].keyUp = true; +				continue; + +			case 6: 	// Vibrato. Parameters: length, channel, rate +			case 7: 	// Envelope. Parameters: rate, tempo | channel, endVol +				noteCommand(pattCmd, patternPtr[1], patternPtr[2], patternPtr[3]); +				continue; + +			case 8: 	// Subroutine. Parameters: pattern, patternstep(W) +				pattern.savedOffset = pattern.offset; +				pattern.savedStep = pattern.step; + +				pattern.offset = _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]; +				pattern.step = READ_BE_UINT16(&patternPtr[2]); +				continue; + +			case 9: 	// Return from Subroutine +				pattern.offset = pattern.savedOffset; +				pattern.step = pattern.savedStep; +				continue; + +			case 10:	// fade. Parameters: tempo, endVol +				initFadeCommand((uint8)patternPtr[1], (int8)patternPtr[3]); +				continue; + +			case 11:	// play pattern. Parameters: patternCmd, channel, expose +				initPattern(_patternCtx[patternPtr[2] & (kNumChannels - 1)], patternPtr[1], patternPtr[3], _resource->patternOffset[patternPtr[1] & (kMaxPatternOffsets - 1)]); +				continue; + +			case 12: 	// Lock. Parameters: lockFlag, channel, lockTime +				_channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLocked = (patternPtr[1] != 0); +				_channelCtx[patternPtr[2] & (kNumVoices - 1)].sfxLockTime = patternPtr[3]; +				continue; + +			case 13: 	// Cue. Parameters: signalnumber, value(W) +				if (_playerCtx.numSignals > patternPtr[1]) +					_playerCtx.signal[patternPtr[1]] = READ_BE_UINT16(&patternPtr[2]); +				continue; + +			case 15: 	// NOP +				continue; +			} +		} +	} +} + +bool Tfmx::trackRun(const bool incStep) { +	assert(_playerCtx.song >= 0); +	if (incStep) { +		// TODO Optionally disable looping +		if (_trackCtx.posInd == _trackCtx.stopInd) +			_trackCtx.posInd = _trackCtx.startInd; +		else +			++_trackCtx.posInd; +	} +	for (;;) { +		const uint16 *const trackData = getTrackPtr(_trackCtx.posInd); + +		if (trackData[0] != FROM_BE_16(0xEFFE)) { +			// 8 commands for Patterns +			for (int i = 0; i < 8; ++i) { +				const uint8 *patCmd = (const uint8 *)&trackData[i]; +				// First byte is pattern number +				const uint8 patNum = patCmd[0]; +				// if highest bit is set then keep previous pattern +				if (patNum < 0x80) { +					initPattern(_patternCtx[i], patNum, patCmd[1], _resource->patternOffset[patNum]); +				} else { +					_patternCtx[i].command = patNum; +					_patternCtx[i].expose = (int8)patCmd[1]; +				} +			} +			return true; + +		} else { +			// 16 byte Trackstep Command +			switch (READ_BE_UINT16(&trackData[1])) { +			case 0:	// Stop Player. No Parameters +				stopPaula(); +				return false; + +			case 1:	// Branch/Loop section of tracksteps. Parameters: branch target, loopcount +				if (_trackCtx.loopCount != 0) { +					if (_trackCtx.loopCount < 0) +						_trackCtx.loopCount = READ_BE_UINT16(&trackData[3]); +					_trackCtx.posInd    = READ_BE_UINT16(&trackData[2]); +					continue; +				} +				--_trackCtx.loopCount; +				break; + +			case 2:	{ // Set Tempo. Parameters: tempo, divisor +				_playerCtx.patternCount = _playerCtx.patternSkip = READ_BE_UINT16(&trackData[2]); // tempo +				const uint16 temp = READ_BE_UINT16(&trackData[3]); // divisor + +				if (!(temp & 0x8000) && (temp & 0x1FF)) +					setInterruptFreqUnscaled(temp & 0x1FF); +				break; +			} +			case 4:	// Fade. Parameters: tempo, endVol +				// load the LSB of the 16bit words +				initFadeCommand(((const uint8 *)&trackData[2])[1], ((const int8 *)&trackData[3])[1]); +				break; + +			case 3:	// Unknown, stops player aswell +			default: +				debug(3, "Tfmx: Unknown Trackstep Command: %02X", READ_BE_UINT16(&trackData[1])); +				// MI-Player handles this by stopping the player, we just continue +			} +		} + +		if (_trackCtx.posInd == _trackCtx.stopInd) { +			warning("Tfmx: Reached invalid Song-Position"); +			return false; +		} +		++_trackCtx.posInd; +	} +} + +void Tfmx::noteCommand(const uint8 note, const uint8 param1, const uint8 param2, const uint8 param3) { +	ChannelContext &channel = _channelCtx[param2 & (kNumVoices - 1)]; + +	if (note == 0xFC) {	// Lock command +		channel.sfxLocked = (param1 != 0); +		channel.sfxLockTime = param3; // only 1 byte read!  + +	} else if (channel.sfxLocked) {	// Channel still locked, do nothing +	 +	} else if (note < 0xC0) {	// Play Note - Parameters: note, macro, relVol | channel, finetune + +		channel.prevNote = channel.note; +		channel.note = note; +		// channel.macroIndex = param1 & (kMaxMacroOffsets - 1); +		channel.macroOffset = _resource->macroOffset[param1 & (kMaxMacroOffsets - 1)]; +		channel.relVol = param2 >> 4; +		channel.fineTune = (int8)param3; + +		// TODO: the point where the channel gets initialised varies with the games, needs more research. +		initMacroProgramm(channel); +		channel.keyUp = false; // key down = playing a Note + +	} else if (note < 0xF0) {	// Portamento - Parameters: note, tempo, channel, rate +		channel.portaSkip = param1; +		channel.portaCount = 1; +		if (!channel.portaDelta) +			channel.portaValue = channel.refPeriod; +		channel.portaDelta = param3; + +		channel.note = note & 0x3F; +		channel.refPeriod = noteIntervalls[channel.note]; + +	} else switch (note) {	// Command + +		case 0xF5:	// Key Up Signal +			channel.keyUp = true; +			break; + +		case 0xF6:	// Vibratio - Parameters: length, channel, rate +			channel.vibLength = param1 & 0xFE; +			channel.vibCount = param1 / 2; +			channel.vibDelta = param3; +			channel.vibValue = 0; +			break; + +		case 0xF7:	// Envelope - Parameters: rate, tempo | channel, endVol +			channel.envDelta = param1; +			channel.envCount = channel.envSkip = (param2 >> 4) + 1; +			channel.envEndVolume = param3; +			break; +	} +} + +void Tfmx::initMacroProgramm(ChannelContext &channel) { +	channel.macroStep = 0; +	channel.macroWait = 0; +	channel.macroRun = true; +	channel.macroSfxRun = 0; +	channel.macroLoopCount = 0xFF; +	channel.dmaIntCount = 0; +	channel.deferWait = false; + +	channel.macroReturnOffset = 0; +	channel.macroReturnStep = 0; +} + +void Tfmx::clearEffects(ChannelContext &channel) { +	channel.addBeginLength = 0; +	channel.envSkip = 0; +	channel.vibLength = 0; +	channel.portaDelta = 0; +} + +void Tfmx::haltMacroProgramm(ChannelContext &channel) { +	channel.macroRun = false; +	channel.dmaIntCount = 0; +} + +void Tfmx::unlockMacroChannel(ChannelContext &channel) { +	channel.customMacro = 0; +	channel.customMacroIndex = 0; +	channel.customMacroPrio = 0; +	channel.sfxLocked = false; +	channel.sfxLockTime = -1; +} + +void Tfmx::initPattern(PatternContext &pattern, uint8 cmd, int8 expose, uint32 offset) { +	pattern.command = cmd; +	pattern.offset = offset; +	pattern.expose = expose; +	pattern.step = 0; +	pattern.wait = 0; +	pattern.loopCount = 0xFF; + +	pattern.savedOffset = 0; +	pattern.savedStep = 0; +} + +void Tfmx::stopSongImpl(bool stopAudio) { +	 _playerCtx.song = -1; +	for (int i = 0; i < kNumChannels; ++i) { +		_patternCtx[i].command = 0xFF; +		_patternCtx[i].expose = 0; +	} +	if (stopAudio) { +		stopPaula(); +		for (int i = 0; i < kNumVoices; ++i) { +			clearEffects(_channelCtx[i]); +			unlockMacroChannel(_channelCtx[i]); +			haltMacroProgramm(_channelCtx[i]); +			_channelCtx[i].note = 0; +			_channelCtx[i].volume = 0; +			_channelCtx[i].macroSfxRun = -1; +			_channelCtx[i].vibValue = 0; + +			_channelCtx[i].sampleStart = 0; +			_channelCtx[i].sampleLen = 2; +			_channelCtx[i].refPeriod = 4; +			_channelCtx[i].period = 4; +			Paula::disableChannel(i); +		} +	} +} + +void Tfmx::setNoteMacro(ChannelContext &channel, uint note, int fineTune) { +	const uint16 noteInt = noteIntervalls[note & 0x3F]; +	const uint16 finetune = (uint16)(fineTune + channel.fineTune + (1 << 8)); +	channel.refPeriod = ((uint32)noteInt * finetune >> 8); +	if (!channel.portaDelta) +		channel.period = channel.refPeriod; +} + +void Tfmx::initFadeCommand(const uint8 fadeTempo, const int8 endVol) { +	_playerCtx.fadeCount = _playerCtx.fadeSkip = fadeTempo; +	_playerCtx.fadeEndVolume = endVol; + +	if (fadeTempo) { +		const int diff = _playerCtx.fadeEndVolume - _playerCtx.volume; +		_playerCtx.fadeDelta = (diff != 0) ? ((diff > 0) ? 1 : -1) : 0; +	} else { +		_playerCtx.volume = endVol; +		_playerCtx.fadeDelta = 0; +	} +} + +void Tfmx::setModuleData(Tfmx &otherPlayer) { +	setModuleData(otherPlayer._resource, otherPlayer._resourceSample.sampleData, otherPlayer._resourceSample.sampleLen, false); +} + +bool Tfmx::load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData, bool autoDelete) { +	const MdatResource *mdat = loadMdatFile(musicData); +	if (mdat) { +		uint32 sampleLen = 0; +		const int8 *sampleDat = loadSampleFile(sampleLen, sampleData); +		if (sampleDat) { +			setModuleData(mdat, sampleDat, sampleLen, autoDelete); +			return true; +		} +		delete[] mdat->mdatAlloc; +		delete mdat; +	} +	return false; +} + +void Tfmx::freeResourceDataImpl() { +	if (_deleteResource) { +		if (_resource) { +			delete[] _resource->mdatAlloc; +			delete _resource; +		} +		delete[] _resourceSample.sampleData; +	} +	_resource = 0; +	_resourceSample.sampleData = 0; +	_resourceSample.sampleLen = 0; +	_deleteResource = false; +} + +void Tfmx::setModuleData(const MdatResource *resource, const int8 *sampleData, uint32 sampleLen, bool autoDelete) { +	Common::StackLock lock(_mutex); +	stopSongImpl(true); +	freeResourceDataImpl(); +	_resource = resource; +	_resourceSample.sampleData = sampleData; +	_resourceSample.sampleLen = sampleData ? sampleLen : 0; +	_deleteResource = autoDelete; +} + +const int8 *Tfmx::loadSampleFile(uint32 &sampleLen, Common::SeekableReadStream &sampleStream) { +	sampleLen = 0; + +	const int32 sampleSize = sampleStream.size(); +	if (sampleSize < 4) { +		warning("Tfmx: Cant load Samplefile"); +		return false; +	} + +	int8 *sampleAlloc = new int8[sampleSize]; +	if (!sampleAlloc) { +		warning("Tfmx: Could not allocate Memory: %dKB", sampleSize / 1024); +		return 0; +	} + +	if (sampleStream.read(sampleAlloc, sampleSize) == (uint32)sampleSize) { +		sampleAlloc[0] = sampleAlloc[1] = sampleAlloc[2] = sampleAlloc[3] = 0; +		sampleLen = sampleSize; +	} else { +		delete sampleAlloc; +		warning("Tfmx: Encountered IO-Error"); +		return 0; +	} +	return sampleAlloc; +} + +const Tfmx::MdatResource *Tfmx::loadMdatFile(Common::SeekableReadStream &musicData) { +	bool hasHeader = false; +	const int32 mdatSize = musicData.size(); +	if (mdatSize >= 0x200) { +		byte buf[16] = { 0 }; +		// 0x0000: 10 Bytes Header "TFMX-SONG " +		musicData.read(buf, 10); +		hasHeader = memcmp(buf, "TFMX-SONG ", 10) == 0; +	} +	 +	if (!hasHeader) { +		warning("Tfmx: File is not a Tfmx Module"); +		return 0; +	} + +	MdatResource *resource = new MdatResource; + +	resource->mdatAlloc = 0; +	resource->mdatData = 0; +	resource->mdatLen = 0; + +	// 0x000A: int16 flags +	resource->headerFlags = musicData.readUint16BE(); +	// 0x000C: int32 ? +	// 0x0010: 6*40 Textfield +	musicData.skip(4 + 6 * 40); + +	/* 0x0100: Songstart x 32*/ +	for (int i = 0; i < kNumSubsongs; ++i) +		resource->subsong[i].songstart = musicData.readUint16BE(); +	/* 0x0140: Songend x 32*/ +	for (int i = 0; i < kNumSubsongs; ++i) +		resource->subsong[i].songend = musicData.readUint16BE(); +	/* 0x0180: Tempo x 32*/ +	for (int i = 0; i < kNumSubsongs; ++i) +		resource->subsong[i].tempo  = musicData.readUint16BE(); + +	/* 0x01c0: unused ? */ +	musicData.skip(16); + +	/* 0x01d0: trackstep, pattern data p, macro data p */ +	const uint32 offTrackstep = musicData.readUint32BE(); +	uint32 offPatternP, offMacroP; + +	// This is how MI`s TFMX-Player tests for unpacked Modules. +	if (!offTrackstep) { // unpacked File +		resource->trackstepOffset = 0x600 + 0x200; +		offPatternP		= 0x200 + 0x200; +		offMacroP		= 0x400 + 0x200; +	} else { // packed File +		resource->trackstepOffset = offTrackstep; +		offPatternP = musicData.readUint32BE(); +		offMacroP = musicData.readUint32BE(); +	} + +	// End of basic header, check if everything worked ok +	if (musicData.err()) { +		warning("Tfmx: Encountered IO-Error"); +		delete resource; +		return 0; +	} + +	// TODO: if a File is packed it could have for Ex only 2 Patterns/Macros +	// the following loops could then read beyond EOF.  +	// To correctly handle this it would be necessary to sort the pointers and +	// figure out the number of Macros/Patterns +	// We could also analyze pointers if they are correct offsets, +	// so that accesses can be unchecked later + +	// Read in pattern starting offsets +	musicData.seek(offPatternP); +	for (int i = 0; i < kMaxPatternOffsets; ++i) +		resource->patternOffset[i] = musicData.readUint32BE(); + +	// use last PatternOffset (stored at 0x5FC in mdat) if unpacked File +	// or fixed offset 0x200 if packed +	resource->sfxTableOffset = !offTrackstep ? resource->patternOffset[127] : 0x200; + +	// Read in macro starting offsets +	musicData.seek(offMacroP); +	for (int i = 0; i < kMaxMacroOffsets; ++i) +		resource->macroOffset[i] = musicData.readUint32BE(); + +	// Read in mdat-file +	// TODO: we can skip everything thats already stored in the resource-structure. +	const int32 mdatOffset = 0x200;	// 0x200 is very conservative +	const uint32 allocSize = (uint32)mdatSize - mdatOffset; +	 +	byte *mdatAlloc = new byte[allocSize]; +	if (!mdatAlloc) { +		warning("Tfmx: Could not allocate Memory: %dKB", allocSize / 1024); +		delete resource; +		return 0; +	} +	musicData.seek(mdatOffset); +	if (musicData.read(mdatAlloc, allocSize) == allocSize) { +		resource->mdatAlloc = mdatAlloc; +		resource->mdatData = mdatAlloc - mdatOffset; +		resource->mdatLen = mdatSize; +	} else { +		delete mdatAlloc; +		warning("Tfmx: Encountered IO-Error"); +		delete resource; +		return 0; +	} + +	return resource; +} + +void Tfmx::doMacro(int note, int macro, int relVol, int finetune, int channelNo) { +	assert(0 <= macro && macro < kMaxMacroOffsets); +	assert(0 <= note && note < 0xC0); +	Common::StackLock lock(_mutex); + +	if (!hasResources()) +		return; +	channelNo &= (kNumVoices - 1); +	ChannelContext &channel = _channelCtx[channelNo]; +	unlockMacroChannel(channel); + +	noteCommand((uint8)note, (uint8)macro, (uint8)((relVol << 4) | channelNo), (uint8)finetune); +	startPaula(); +} + +void Tfmx::stopMacroEffect(int channel) { +	assert(0 <= channel && channel < kNumVoices); +	Common::StackLock lock(_mutex); +	unlockMacroChannel(_channelCtx[channel]); +	haltMacroProgramm(_channelCtx[channel]); +	Paula::disableChannel(_channelCtx[channel].paulaChannel); +} + +void Tfmx::doSong(int songPos, bool stopAudio) { +	assert(0 <= songPos && songPos < kNumSubsongs); +	Common::StackLock lock(_mutex); + +	stopSongImpl(stopAudio); + +	if (!hasResources()) +		return; + +	_trackCtx.loopCount = -1; +	_trackCtx.startInd = _trackCtx.posInd = _resource->subsong[songPos].songstart; +	_trackCtx.stopInd = _resource->subsong[songPos].songend; +	_playerCtx.song = (int8)songPos; + +	const bool palFlag = (_resource->headerFlags & 2) != 0; +	const uint16 tempo = _resource->subsong[songPos].tempo; +	uint16 ciaIntervall; +	if (tempo >= 0x10) { +		ciaIntervall = (uint16)(kCiaBaseInterval / tempo); +		_playerCtx.patternSkip = 0; +	} else { +		ciaIntervall = palFlag ? (uint16)kPalDefaultCiaVal : (uint16)kNtscDefaultCiaVal; +		_playerCtx.patternSkip = tempo; +	} +	setInterruptFreqUnscaled(ciaIntervall); +	Paula::setAudioFilter(true); + +	_playerCtx.patternCount = 0; +	if (trackRun()) +		startPaula(); +} + +int Tfmx::doSfx(uint16 sfxIndex, bool unlockChannel) { +	assert(sfxIndex < 128); +	Common::StackLock lock(_mutex); + +	if (!hasResources()) +		return -1; +	const byte *sfxEntry = getSfxPtr(sfxIndex); +	if (sfxEntry[0] == 0xFB) { +		warning("Tfmx: custom patterns are not supported"); +		// custompattern +		/* const uint8 patCmd = sfxEntry[2]; +		const int8 patExp = (int8)sfxEntry[3]; */ +	} else { +		// custommacro +		const byte channelNo = ((_playerCtx.song >= 0) ? sfxEntry[2] : sfxEntry[4]) & (kNumVoices - 1); +		const byte priority = sfxEntry[5] & 0x7F; + +		ChannelContext &channel = _channelCtx[channelNo]; +		if (unlockChannel) +			unlockMacroChannel(channel); + +		const int16 sfxLocktime = channel.sfxLockTime; +		if (priority >= channel.customMacroPrio || sfxLocktime < 0) { +			if (sfxIndex != channel.customMacroIndex || sfxLocktime < 0 || (sfxEntry[5] < 0x80)) { +				channel.customMacro = READ_UINT32(sfxEntry); // intentionally not "endian-correct" +				channel.customMacroPrio = priority; +				channel.customMacroIndex = (uint8)sfxIndex; +				debug(3, "Tfmx: running Macro %08X on channel %i - priority: %02X", TO_BE_32(channel.customMacro), channelNo, priority); +				return channelNo; +			} +		} +	} +	return -1; +} + +}	// End of namespace Audio + +// some debugging functions +namespace { +#ifndef NDEBUG +void displayMacroStep(const void *const vptr) { +	const char *tableMacros[] = { +		"DMAoff+Resetxx/xx/xx flag/addset/vol   ", +		"DMAon (start sample at selected begin) ", +		"SetBegin    xxxxxx   sample-startadress", +		"SetLen      ..xxxx   sample-length     ", +		"Wait        ..xxxx   count (VBI''s)    ", +		"Loop        xx/xxxx  count/step        ", +		"Cont        xx/xxxx  macro-number/step ", +		"-------------STOP----------------------", +		"AddNote     xx/xxxx  note/detune       ", +		"SetNote     xx/xxxx  note/detune       ", +		"Reset   Vibrato-Portamento-Envelope    ", +		"Portamento  xx/../xx count/speed       ", +		"Vibrato     xx/../xx speed/intensity   ", +		"AddVolume   ....xx   volume 00-3F      ", +		"SetVolume   ....xx   volume 00-3F      ", +		"Envelope    xx/xx/xx speed/count/endvol", +		"Loop key up xx/xxxx  count/step        ", +		"AddBegin    xx/xxxx  count/add to start", +		"AddLen      ..xxxx   add to sample-len ", +		"DMAoff stop sample but no clear        ", +		"Wait key up ....xx   count (VBI''s)    ", +		"Go submacro xx/xxxx  macro-number/step ", +		"--------Return to old macro------------", +		"Setperiod   ..xxxx   DMA period        ", +		"Sampleloop  ..xxxx   relative adress   ", +		"-------Set one shot sample-------------", +		"Wait on DMA ..xxxx   count (Wavecycles)", +		"Random play xx/xx/xx macro/speed/mode  ", +		"Splitkey    xx/xxxx  key/macrostep     ", +		"Splitvolume xx/xxxx  volume/macrostep  ", +		"Addvol+note xx/fe/xx note/CONST./volume", +		"SetPrevNote xx/xxxx  note/detune       ", +		"Signal      xx/xxxx  signalnumber/value", +		"Play macro  xx/.x/xx macro/chan/detune ", +		"SID setbeg  xxxxxx   sample-startadress", +		"SID setlen  xx/xxxx  buflen/sourcelen  ", +		"SID op3 ofs xxxxxx   offset            ", +		"SID op3 frq xx/xxxx  speed/amplitude   ", +		"SID op2 ofs xxxxxx   offset            ", +		"SID op2 frq xx/xxxx  speed/amplitude   ", +		"SID op1     xx/xx/xx speed/amplitude/TC", +		"SID stop    xx....   flag (1=clear all)" +	}; + +	const byte *const macroData = (const byte *const)vptr; +	if (macroData[0] < ARRAYSIZE(tableMacros)) +		debug("%s %02X%02X%02X", tableMacros[macroData[0]], macroData[1], macroData[2], macroData[3]); +	else +		debug("Unkown Macro #%02X %02X%02X%02X", macroData[0], macroData[1], macroData[2], macroData[3]); +} + +void displayPatternstep(const void *const vptr) { +	const char *tablePatterns[] = { +		"End --Next track  step--", +		"Loop[count     / step.w]", +		"Cont[patternno./ step.w]", +		"Wait[count 00-FF--------", +		"Stop--Stop this pattern-", +		"Kup^-Set key up/channel]", +		"Vibr[speed     / rate.b]", +		"Enve[speed /endvolume.b]", +		"GsPt[patternno./ step.w]", +		"RoPt-Return old pattern-", +		"Fade[speed /endvolume.b]", +		"PPat[patt./track+transp]", +		"Lock---------ch./time.b]", +		"Cue [number.b/  value.w]", +		"Stop-Stop custompattern-", +		"NOP!-no operation-------" +	}; + +	const byte *const patData = (const byte *const)vptr; +	const byte command = patData[0]; +	if (command < 0xF0) { // Playnote +		const byte flags = command >> 6; // 0-1 means note+detune, 2 means wait, 3 means portamento? +		const char *flagsSt[] = { "Note ", "Note ", "Wait ", "Porta" }; +		debug("%s %02X%02X%02X%02X", flagsSt[flags], patData[0], patData[1], patData[2], patData[3]); +	} else +		debug("%s %02X%02X%02X",tablePatterns[command & 0xF], patData[1], patData[2], patData[3]); +} +#else +void displayMacroStep(const void *const vptr, int chan, int index) {} +void displayPatternstep(const void *const vptr) {} +#endif +}	// End of namespace + +#endif // #if defined(SOUND_MODS_TFMX_H) diff --git a/sound/mods/tfmx.h b/sound/mods/tfmx.h new file mode 100644 index 0000000000..26018d9466 --- /dev/null +++ b/sound/mods/tfmx.h @@ -0,0 +1,284 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// see if all engines using this class are DISABLED +#if !defined(ENABLE_SCUMM) + +// normal Header Guard +#elif !defined(SOUND_MODS_TFMX_H) +#define SOUND_MODS_TFMX_H + +#include "sound/mods/paula.h" + +namespace Audio { + +class Tfmx : public Paula { +public: +	Tfmx(int rate, bool stereo); +	virtual ~Tfmx(); + +	/** +	 * Stops a playing Song (but leaves macros running) and optionally also stops the player +	 * +	 * @param stopAudio	stops player and audio output +	 * @param dataSize	number of bytes to be written +	 * @return the number of bytes which were actually written. +	 */ +	void stopSong(bool stopAudio = true) { Common::StackLock lock(_mutex); stopSongImpl(stopAudio); } +	/** +	 * Stops currently playing Song (if any) and cues up a new one. +	 * if stopAudio is specified, the player gets reset before starting the new song +	 * +	 * @param songPos	index of Song to play +	 * @param stopAudio	stops player and audio output +	 * @param dataSize	number of bytes to be written +	 * @return the number of bytes which were actually written. +	 */ +	void doSong(int songPos, bool stopAudio = false); +	/** +	 * plays an effect from the sfx-table, does not start audio-playback.  +	 * +	 * @param sfxIndex	index of effect to play +	 * @param unlockChannel	overwrite higher priority effects +	 * @return	index of the channel which now queued up the effect. +	 *			-1 in case the effect couldnt be queued up +	 */ +	int doSfx(uint16 sfxIndex, bool unlockChannel = false); +	/** +	 * stop a running macro channel +	 * +	 * @param channel	index of effect to stop +	 */ +	void stopMacroEffect(int channel); + +	void doMacro(int note, int macro, int relVol = 0, int finetune = 0, int channelNo = 0); +	int getTicks() const { return _playerCtx.tickCount; }  +	int getSongIndex() const { return _playerCtx.song; } +	void setSignalPtr(uint16 *ptr, uint16 numSignals) { _playerCtx.signal = ptr; _playerCtx.numSignals = numSignals; } +	void freeResources() { _deleteResource = true; freeResourceDataImpl(); } +	bool load(Common::SeekableReadStream &musicData, Common::SeekableReadStream &sampleData, bool autoDelete = true); +	void setModuleData(Tfmx &otherPlayer); + +protected: +	void interrupt(); + +private: +	enum { kPalDefaultCiaVal = 11822, kNtscDefaultCiaVal = 14320, kCiaBaseInterval = 0x1B51F8 }; +	enum { kNumVoices = 4, kNumChannels = 8, kNumSubsongs = 32, kMaxPatternOffsets = 128, kMaxMacroOffsets = 128 }; + +	struct MdatResource { +		const byte *mdatAlloc;	//!< allocated Block of Memory +		const byte *mdatData;  	//!< Start of mdat-File, might point before mdatAlloc to correct Offset +		uint32 mdatLen; + +		uint16 headerFlags; +//		uint32 headerUnknown; +//		char textField[6 * 40]; + +		struct Subsong { +			uint16 songstart;	//!< Index in Trackstep-Table +			uint16 songend;		//!< Last index in Trackstep-Table +			uint16 tempo; +		} subsong[kNumSubsongs]; + +		uint32 trackstepOffset;	//!< Offset in mdat +		uint32 sfxTableOffset; + +		uint32 patternOffset[kMaxPatternOffsets];	//!< Offset in mdat +		uint32 macroOffset[kMaxMacroOffsets];	//!< Offset in mdat + +		void boundaryCheck(const void *address, size_t accessLen = 1) const { +			assert(mdatAlloc <= address && (const byte *)address + accessLen <= (const byte *)mdatData + mdatLen); +		} +	} const *_resource; + +	struct SampleResource { +		const int8 *sampleData;	//!< The whole sample-File +		uint32 sampleLen; + +		void boundaryCheck(const void *address, size_t accessLen = 2) const { +			assert(sampleData <= address && (const byte *)address + accessLen <= (const byte *)sampleData + sampleLen); +		} +	} _resourceSample; + +	bool _deleteResource; + +	bool hasResources() { +		return _resource && _resource->mdatLen && _resourceSample.sampleLen; +	} + +	struct ChannelContext { +		byte	paulaChannel; + +//		byte	macroIndex; +		uint16	macroWait; +		uint32	macroOffset; +		uint32	macroReturnOffset; +		uint16	macroStep; +		uint16	macroReturnStep; +		uint8	macroLoopCount; +		bool	macroRun; +		int8	macroSfxRun;	//!< values are the folowing: -1 macro disabled, 0 macro init, 1 macro running + +		uint32	customMacro; +		uint8	customMacroIndex; +		uint8	customMacroPrio; + +		bool	sfxLocked; +		int16	sfxLockTime; +		bool	keyUp; + +		bool	deferWait; +		uint16	dmaIntCount; + +		uint32	sampleStart; +		uint16	sampleLen; +		uint16	refPeriod; +		uint16	period; + +		int8	volume; +		uint8	relVol; +		uint8	note; +		uint8	prevNote; +		int16	fineTune; // always a signextended byte + +		uint8	portaSkip; +		uint8	portaCount; +		uint16	portaDelta; +		uint16	portaValue; + +		uint8	envSkip; +		uint8	envCount; +		uint8	envDelta; +		int8	envEndVolume; + +		uint8	vibLength; +		uint8	vibCount; +		int16	vibValue; +		int8	vibDelta; + +		uint8	addBeginLength; +		uint8	addBeginCount; +		int32	addBeginDelta; +	} _channelCtx[kNumVoices]; + +	struct PatternContext { +		uint32	offset; // patternStart, Offset from mdat +		uint32	savedOffset;	// for subroutine calls +		uint16	step;	// distance from patternStart +		uint16	savedStep; + +		uint8	command; +		int8	expose;  +		uint8	loopCount; +		uint8	wait;	//!< how many ticks to wait before next Command +	} _patternCtx[kNumChannels]; + +	struct TrackStepContext { +		uint16	startInd; +		uint16	stopInd; +		uint16	posInd; +		int16	loopCount; +	} _trackCtx; + +	struct PlayerContext { +		int8	song;	//!< >= 0 if Song is running (means process Patterns) + +		uint16	patternCount; +		uint16	patternSkip;	//!< skip that amount of CIA-Interrupts + +		int8	volume;	//!< Master Volume + +		uint8	fadeSkip; +		uint8	fadeCount; +		int8	fadeEndVolume; +		int8	fadeDelta; + +		int		tickCount; + +		uint16	*signal; +		uint16	numSignals; + +		bool	stopWithLastPattern; //!< hack to automatically stop the whole player if no Pattern is running +	} _playerCtx; + +	const byte *getSfxPtr(uint16 index = 0) const { +		const byte *sfxPtr = (const byte *)(_resource->mdatData + _resource->sfxTableOffset + index * 8); + +		_resource->boundaryCheck(sfxPtr, 8); +		return sfxPtr; +	} + +	const uint16 *getTrackPtr(uint16 trackstep = 0) const { +		const uint16 *trackData = (const uint16 *)(_resource->mdatData + _resource->trackstepOffset + 16 * trackstep); + +		_resource->boundaryCheck(trackData, 16); +		return trackData; +	} + +	const uint32 *getPatternPtr(uint32 offset) const { +		const uint32 *pattData = (const uint32 *)(_resource->mdatData + offset); + +		_resource->boundaryCheck(pattData, 4); +		return pattData; +	} + +	const uint32 *getMacroPtr(uint32 offset) const { +		const uint32 *macroData = (const uint32 *)(_resource->mdatData + offset); + +		_resource->boundaryCheck(macroData, 4); +		return macroData; +	} + +	const int8 *getSamplePtr(const uint32 offset) const { +		const int8 *sample = _resourceSample.sampleData + offset; + +		_resourceSample.boundaryCheck(sample, 2); +		return sample; +	} + +	static inline void initMacroProgramm(ChannelContext &channel); +	static inline void clearEffects(ChannelContext &channel); +	static inline void haltMacroProgramm(ChannelContext &channel); +	static inline void unlockMacroChannel(ChannelContext &channel); +	static inline void initPattern(PatternContext &pattern, uint8 cmd, int8 expose, uint32 offset); +	void stopSongImpl(bool stopAudio = true); +	static void inline setNoteMacro(ChannelContext &channel, uint note, int fineTune); +	void initFadeCommand(const uint8 fadeTempo, const int8 endVol); +	void setModuleData(const MdatResource *resource, const int8 *sampleData, uint32 sampleLen, bool autoDelete = true); +	static const MdatResource *loadMdatFile(Common::SeekableReadStream &musicData); +	static const int8 *loadSampleFile(uint32 &sampleLen, Common::SeekableReadStream &sampleStream);	 +	void freeResourceDataImpl(); +	void effects(ChannelContext &channel); +	void macroRun(ChannelContext &channel); +	void advancePatterns(); +	bool patternRun(PatternContext &pattern); +	bool trackRun(bool incStep = false); +	void noteCommand(uint8 note, uint8 param1, uint8 param2, uint8 param3); +}; + +}	// End of namespace Audio + +#endif // !defined(SOUND_MODS_TFMX_H) diff --git a/sound/module.mk b/sound/module.mk index 3bcdd47c56..aabe7fe729 100644 --- a/sound/module.mk +++ b/sound/module.mk @@ -24,11 +24,13 @@ MODULE_OBJS := \  	vorbis.o \  	wave.o \  	mods/infogrames.o \ +	mods/maxtrax.o \  	mods/module.o \  	mods/protracker.o \  	mods/paula.o \  	mods/rjp1.o \  	mods/soundfx.o \ +	mods/tfmx.o \  	softsynth/adlib.o \  	softsynth/opl/dosbox.o \  	softsynth/opl/mame.o \  | 
