diff options
| -rw-r--r-- | engines/sci/detection_tables.h | 65 | ||||
| -rw-r--r-- | engines/sci/engine/features.cpp | 74 | ||||
| -rw-r--r-- | engines/sci/engine/features.h | 44 | ||||
| -rw-r--r-- | engines/sci/engine/guest_additions.cpp | 866 | ||||
| -rw-r--r-- | engines/sci/engine/guest_additions.h | 245 | ||||
| -rw-r--r-- | engines/sci/engine/ksound.cpp | 18 | ||||
| -rw-r--r-- | engines/sci/engine/object.h | 4 | ||||
| -rw-r--r-- | engines/sci/engine/script_patches.cpp | 203 | ||||
| -rw-r--r-- | engines/sci/engine/selector.cpp | 14 | ||||
| -rw-r--r-- | engines/sci/engine/selector.h | 14 | ||||
| -rw-r--r-- | engines/sci/engine/state.cpp | 7 | ||||
| -rw-r--r-- | engines/sci/engine/state.h | 1 | ||||
| -rw-r--r-- | engines/sci/engine/vm.cpp | 41 | ||||
| -rw-r--r-- | engines/sci/engine/vm.h | 20 | ||||
| -rw-r--r-- | engines/sci/module.mk | 1 | ||||
| -rw-r--r-- | engines/sci/sci.cpp | 160 | ||||
| -rw-r--r-- | engines/sci/sci.h | 24 | ||||
| -rw-r--r-- | engines/sci/sound/audio32.cpp | 14 | ||||
| -rw-r--r-- | engines/sci/sound/audio32.h | 7 | ||||
| -rw-r--r-- | engines/sci/sound/soundcmd.cpp | 131 | ||||
| -rw-r--r-- | engines/sci/sound/soundcmd.h | 51 | 
21 files changed, 1677 insertions, 327 deletions
| diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index 02ec9aa55b..c37dd4fffb 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -724,7 +724,8 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #ifdef ENABLE_SCI32  #define GUIO_GK1_FLOPPY GUIO2(GUIO_NOSPEECH, \                                GAMEOPTION_ORIGINAL_SAVELOAD) -#define GUIO_GK1_CD     GUIO2(GAMEOPTION_ORIGINAL_SAVELOAD, \ +#define GUIO_GK1_CD     GUIO3(GUIO_LINKSPEECHTOSFX, \ +                              GAMEOPTION_ORIGINAL_SAVELOAD, \                                GAMEOPTION_HIGH_RESOLUTION_GRAPHICS)  #define GUIO_GK1_MAC    GUIO_GK1_FLOPPY @@ -840,13 +841,16 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #undef GUIO_GK1_CD  #undef GUIO_GK1_MAC -#define GUIO_GK2_DEMO GUIO6(GUIO_NOSUBTITLES, \ +#define GUIO_GK2_DEMO GUIO7(GUIO_NOSUBTITLES, \                              GUIO_NOMUSIC, \ +                            GUIO_NOSFX, \                              GUIO_NOSPEECH, \                              GUIO_NOMIDI, \                              GUIO_NOLAUNCHLOAD, \                              GUIO_NOASPECT) -#define GUIO_GK2      GUIO5(GUIO_NOSUBTITLES, \ +#define GUIO_GK2      GUIO7(GUIO_NOSUBTITLES, \ +                            GUIO_NOSFX, \ +                            GUIO_NOSPEECHVOLUME, \                              GUIO_NOMIDI, \                              GUIO_NOASPECT, \                              GAMEOPTION_ORIGINAL_SAVELOAD, \ @@ -1134,8 +1138,10 @@ static const struct ADGameDescription SciGameDescriptions[] = {  		Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI)	},  #ifdef ENABLE_SCI32 -#define GUIO_HOYLE5 GUIO3(GUIO_NOMIDI, \ +#define GUIO_HOYLE5 GUIO5(GUIO_NOMIDI, \                            GUIO_NOLAUNCHLOAD, \ +                          GUIO_LINKMUSICTOSFX, \ +                          GUIO_LINKSPEECHTOSFX, \                            GUIO_NOASPECT)  	// Hoyle 5 (Hoyle Classic Games) - Windows demo @@ -1803,11 +1809,14 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #ifdef ENABLE_SCI32 -#define GUIO_KQ7_DEMO GUIO4(GUIO_NOSUBTITLES, \ -                            GUIO_NOSPEECH, \ +#define GUIO_KQ7_DEMO GUIO5(GUIO_NOSUBTITLES, \                              GUIO_NOLAUNCHLOAD, \ +                            GUIO_LINKMUSICTOSFX, \ +                            GUIO_LINKSPEECHTOSFX, \                              GUIO_NOASPECT) -#define GUIO_KQ7      GUIO2(GUIO_NOASPECT, \ +#define GUIO_KQ7      GUIO4(GUIO_NOASPECT, \ +                            GUIO_LINKMUSICTOSFX, \ +                            GUIO_LINKSPEECHTOSFX, \                              GAMEOPTION_ORIGINAL_SAVELOAD)  	// King's Quest 7 - English Windows (from the King's Quest Collection) @@ -2591,7 +2600,8 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #ifdef ENABLE_SCI32 -#define GUIO_LSL6HIRES GUIO2(GUIO_NOASPECT, \ +#define GUIO_LSL6HIRES GUIO3(GUIO_NOASPECT, \ +                             GUIO_LINKSPEECHTOSFX, \                               GAMEOPTION_ORIGINAL_SAVELOAD)  	// Larry 6 - English/German DOS CD - HIRES @@ -2874,7 +2884,9 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #ifdef ENABLE_SCI32 -#define GUIO_MOTHERGOOSEHIRES GUIO2(GUIO_NOASPECT, \ +#define GUIO_MOTHERGOOSEHIRES GUIO4(GUIO_NOSUBTITLES, \ +                                    GUIO_NOASPECT, \ +                                    GUIO_LINKSPEECHTOSFX, \                                      GAMEOPTION_ORIGINAL_SAVELOAD)  	// Mixed-Up Mother Goose Deluxe - English Windows/DOS CD (supplied by markcoolio in bug report #2723810) @@ -2907,9 +2919,10 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #ifdef ENABLE_SCI32 -#define GUIO_PHANTASMAGORIA_DEMO GUIO4(GUIO_NOSUBTITLES, \ +#define GUIO_PHANTASMAGORIA_DEMO GUIO5(GUIO_NOSUBTITLES, \                                         GUIO_NOASPECT, \                                         GUIO_NOLAUNCHLOAD, \ +                                       GUIO_LINKSPEECHTOSFX, \                                         GAMEOPTION_ENABLE_BLACK_LINED_VIDEO)  #define GUIO_PHANTASMAGORIA      GUIO_PHANTASMAGORIA_DEMO  #define GUIO_PHANTASMAGORIA_MAC  GUIO_PHANTASMAGORIA_DEMO @@ -3348,7 +3361,8 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #define GUIO_PQ4_FLOPPY GUIO2(GUIO_NOSPEECH, \                                GAMEOPTION_ORIGINAL_SAVELOAD) -#define GUIO_PQ4_CD     GUIO2(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \ +#define GUIO_PQ4_CD     GUIO3(GUIO_LINKSPEECHTOSFX, \ +                              GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \                                GAMEOPTION_ORIGINAL_SAVELOAD)  	// Police Quest 4 - English DOS CD (from the Police Quest Collection) @@ -3394,11 +3408,16 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #undef GUIO_PQ4_FLOPPY  #undef GUIO_PQ4_CD -#define GUIO_PQSWAT_DEMO GUIO4(GUIO_NOSUBTITLES, \ -                               GUIO_NOSPEECH, \ +#define GUIO_PQSWAT_DEMO GUIO6(GUIO_NOSUBTITLES, \ +                               GUIO_NOMIDI, \ +                               GUIO_LINKMUSICTOSFX, \ +                               GUIO_LINKSPEECHTOSFX, \                                 GUIO_NOASPECT, \                                 GUIO_NOLAUNCHLOAD) -#define GUIO_PQSWAT      GUIO4(GUIO_NOMIDI, \ +#define GUIO_PQSWAT      GUIO7(GUIO_NOSUBTITLES, \ +                               GUIO_NOMIDI, \ +                               GUIO_LINKMUSICTOSFX, \ +                               GUIO_LINKSPEECHTOSFX, \                                 GUIO_NOASPECT, \                                 GAMEOPTION_ORIGINAL_SAVELOAD, \                                 GAMEOPTION_ENABLE_BLACK_LINED_VIDEO) @@ -3797,7 +3816,8 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #define GUIO_QFG4_FLOPPY GUIO2(GUIO_NOSPEECH, \                                 GAMEOPTION_ORIGINAL_SAVELOAD) -#define GUIO_QFG4_CD     GUIO1(GAMEOPTION_ORIGINAL_SAVELOAD) +#define GUIO_QFG4_CD     GUIO2(GUIO_LINKSPEECHTOSFX, \ +                               GAMEOPTION_ORIGINAL_SAVELOAD)  	// Quest for Glory 4 1.1 Floppy - English DOS (supplied by markcool in bug report #2723852)  	// SCI interpreter version 2.000.000 (a guess?) @@ -3920,12 +3940,15 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #endif	// ENABLE_SCI3_GAMES -#define GUIO_SHIVERS_DEMO GUIO5(GUIO_NOSUBTITLES, \ -                                GUIO_NOSPEECH, \ +#define GUIO_SHIVERS_DEMO GUIO6(GUIO_NOSUBTITLES, \                                  GUIO_NOMIDI, \                                  GUIO_NOLAUNCHLOAD, \ +                                GUIO_LINKSPEECHTOSFX, \ +                                GUIO_LINKMUSICTOSFX, \                                  GUIO_NOASPECT) -#define GUIO_SHIVERS      GUIO4(GUIO_NOMIDI, \ +#define GUIO_SHIVERS      GUIO6(GUIO_NOMIDI, \ +                                GUIO_LINKSPEECHTOSFX, \ +                                GUIO_LINKMUSICTOSFX, \                                  GUIO_NOASPECT, \                                  GAMEOPTION_ORIGINAL_SAVELOAD, \                                  GAMEOPTION_ENABLE_BLACK_LINED_VIDEO) @@ -4528,9 +4551,11 @@ static const struct ADGameDescription SciGameDescriptions[] = {  #ifdef ENABLE_SCI32 -#define GUIO_SQ6_DEMO GUIO2(GUIO_NOLAUNCHLOAD, \ +#define GUIO_SQ6_DEMO GUIO3(GUIO_NOLAUNCHLOAD, \ +                            GUIO_LINKSPEECHTOSFX, \                              GUIO_NOASPECT) -#define GUIO_SQ6      GUIO3(GUIO_NOASPECT, \ +#define GUIO_SQ6      GUIO4(GUIO_LINKSPEECHTOSFX, \ +                            GUIO_NOASPECT, \                              GAMEOPTION_ORIGINAL_SAVELOAD, \                              GAMEOPTION_ENABLE_BLACK_LINED_VIDEO) diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp index 40d380195d..1085fec85b 100644 --- a/engines/sci/engine/features.cpp +++ b/engines/sci/engine/features.cpp @@ -534,6 +534,80 @@ SciVersion GameFeatures::detectSci21KernelType() {  }  #endif +bool GameFeatures::supportsSpeechWithSubtitles() const { +	switch (g_sci->getGameId()) { +	case GID_SQ4: +	case GID_FREDDYPHARKAS: +	case GID_ECOQUEST: +	case GID_LSL6: +	case GID_LAURABOW2: +	case GID_KQ6: +#ifdef ENABLE_SCI32 +	// TODO: Hoyle5, SCI3 +	case GID_GK1: +	case GID_KQ7: +	case GID_LSL6HIRES: +	case GID_PQ4: +	case GID_QFG4: +	case GID_SQ6: +	case GID_TORIN: +#endif +		return true; + +	default: +		return false; +	} +} + +bool GameFeatures::audioVolumeSyncUsesGlobals() const { +	switch (g_sci->getGameId()) { +	case GID_GK1: +	case GID_GK2: +	case GID_LSL6HIRES: +	case GID_PHANTASMAGORIA: +	case GID_TORIN: +		// TODO: SCI3 +		return true; +	default: +		return false; +	} +} + +MessageTypeSyncStrategy GameFeatures::getMessageTypeSyncStrategy() const { +	if (getSciVersion() < SCI_VERSION_1_1) { +		return kMessageTypeSyncStrategyNone; +	} + +	if (getSciVersion() == SCI_VERSION_1_1 && g_sci->isCD()) { +		return kMessageTypeSyncStrategyDefault; +	} + +#ifdef ENABLE_SCI32 +	switch (g_sci->getGameId()) { +	// TODO: Hoyle5, SCI3 +	case GID_GK1: +	case GID_KQ7: +	case GID_MOTHERGOOSEHIRES: +	case GID_PHANTASMAGORIA: +	case GID_PQ4: +	case GID_QFG4: +	case GID_TORIN: +		return kMessageTypeSyncStrategyDefault; + +	case GID_LSL6HIRES: +		return kMessageTypeSyncStrategyLSL6Hires; + +	case GID_SHIVERS: +		return kMessageTypeSyncStrategyShivers; + +	default: +		break; +	} +#endif + +	return kMessageTypeSyncStrategyNone; +} +  bool GameFeatures::autoDetectMoveCountType() {  	// Look up the script address  	reg_t addr = getDetectionAddr("Motion", SELECTOR(doit)); diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h index 8f84bbacad..044111a43e 100644 --- a/engines/sci/engine/features.h +++ b/engines/sci/engine/features.h @@ -40,6 +40,16 @@ enum PseudoMouseAbilityType {  	kPseudoMouseAbilityTrue  }; +enum MessageTypeSyncStrategy { +	kMessageTypeSyncStrategyNone, +	kMessageTypeSyncStrategyDefault +#ifdef ENABLE_SCI32 +	, +	kMessageTypeSyncStrategyLSL6Hires, +	kMessageTypeSyncStrategyShivers +#endif +}; +  class GameFeatures {  public:  	GameFeatures(SegManager *segMan, Kernel *kernel); @@ -148,6 +158,40 @@ public:  #endif  	/** +	 * If true, the current game supports simultaneous speech & subtitles. +	 */ +	bool supportsSpeechWithSubtitles() const; + +	/** +	 * If true, the game supports changing text speed. +	 */ +	bool supportsTextSpeed() const { +		switch (g_sci->getGameId()) { +#ifdef ENABLE_SCI32 +		case GID_GK1: +		case GID_SQ6: +			return true; +#endif +		default: +			break; +		} + +		return false; +	} + +	/** +	 * If true, audio volume sync between the game and ScummVM is done by +	 * monitoring and setting game global variables. +	 */ +	bool audioVolumeSyncUsesGlobals() const; + +	/** +	 * The strategy that should be used when synchronising the message type +	 * (text/speech/text+speech) between the game and ScummVM. +	 */ +	MessageTypeSyncStrategy getMessageTypeSyncStrategy() const; + +	/**  	 * Applies to all versions before 0.000.502  	 * Old SCI versions used to interpret the third DrawPic() parameter inversely,  	 * with the opposite default value (obviously). diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp new file mode 100644 index 0000000000..a2c64239a5 --- /dev/null +++ b/engines/sci/engine/guest_additions.cpp @@ -0,0 +1,866 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "audio/mixer.h" +#include "common/config-manager.h" +#include "common/gui_options.h" +#include "sci/engine/features.h" +#include "sci/engine/guest_additions.h" +#include "sci/engine/kernel.h" +#include "sci/engine/state.h" +#include "sci/engine/vm.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/frameout.h" +#endif +#include "sci/sound/music.h" +#include "sci/sci.h" + +namespace Sci { + +enum { +	kSoundsMusicType = 0, +	kSoundsSoundType = 1 +}; + +enum { +	kMessageTypeSubtitles = 1, +	kMessageTypeSpeech    = 2 +}; + +enum { +	kLSL6HiresUIVolumeMax  = 13, +	kLSL6HiresSubtitleFlag = 105 +}; + + +GuestAdditions::GuestAdditions(EngineState *state, GameFeatures *features) : +	_state(state), +	_features(features), +	_segMan(state->_segMan), +	_messageTypeSynced(false) {} + +#pragma mark - + +void GuestAdditions::syncSoundSettings() const { +#ifdef ENABLE_SCI32 +	if (_features->audioVolumeSyncUsesGlobals()) +		syncAudioVolumeGlobalsFromScummVM(); +	else +#endif +		syncMasterVolumeFromScummVM(); +} + +void GuestAdditions::syncAudioOptionsFromScummVM() const { +#ifdef ENABLE_SCI32 +	if (_features->supportsTextSpeed()) { +		syncTextSpeedFromScummVM(); +	} +#endif +	syncMessageTypeFromScummVM(); +} + +void GuestAdditions::reset() { +	_messageTypeSynced = false; +} + +void GuestAdditions::invokeSelector(const reg_t objId, const Selector selector, const int argc, const StackPtr argv) const { +	::Sci::invokeSelector(_state, objId, selector, 0, _state->_executionStack.back().sp, argc, argv); +} + +bool GuestAdditions::shouldSyncAudio() const { +	const SciGameId gameId = g_sci->getGameId(); +	Common::List<ExecStack>::const_iterator it; +	for (it = _state->_executionStack.begin(); it != _state->_executionStack.end(); ++it) { +		const ExecStack &call = *it; +		const Common::String objName = _segMan->getObjectName(call.sendp); + +		if (getSciVersion() < SCI_VERSION_2 && (objName == "TheMenuBar" || +												objName == "MenuBar")) { +			// SCI16 with menu bar +			return true; +		} else if (objName == "volumeSlider") { +			// SCI16 with icon bar, QFG4, Hoyle5 +			return true; +		} else if (gameId == GID_MOTHERGOOSE256 && objName == "soundBut") { +			return true; +		} else if (gameId == GID_SLATER && objName == "volButton") { +			return true; +		} else if (gameId == GID_LSL6 && objName == "menuBar") { +			return true; +#ifdef ENABLE_SCI32 +		} else if ((gameId == GID_GK1 || gameId == GID_SQ6) && (objName == "musicBar" || +																objName == "soundBar")) { +			return true; +		} else if (gameId == GID_PQ4 && (objName == "increaseVolume" || +										 objName == "decreaseVolume")) { +			return true; +		} else if (gameId == GID_KQ7 && (objName == "volumeUp" || +										 objName == "volumeDown")) { +			return true; +		} else if (gameId == GID_LSL6HIRES && (objName == "hiResMenu" || +											   objName == "volumeDial")) { +			return true; +		} else if (gameId == GID_MOTHERGOOSEHIRES && objName == "MgButtonBar") { +			return true; +		} else if (gameId == GID_PQSWAT && (objName == "volumeDownButn" || +											objName == "volumeUpButn")) { +			return true; +		} else if (gameId == GID_SHIVERS && objName == "spVolume") { +			return true; +		} else if (gameId == GID_GK2 && objName == "soundSlider") { +			return true; +		} else if (gameId == GID_PHANTASMAGORIA && (objName == "midiVolDown" || +													objName == "midiVolUp" || +													objName == "dacVolDown" || +													objName == "dacVolUp")) { +			return true; +		} else if (gameId == GID_TORIN && (objName == "oMusicScroll" || +										   objName == "oSFXScroll" || +										   objName == "oAudioScroll")) { +			return true; +#endif +		} +	} + +	return false; +} + +#pragma mark - +#pragma mark Hooks + +void GuestAdditions::sciEngineRunGameHook() { +	_messageTypeSynced = true; +} + +void GuestAdditions::writeVarHook(const int type, const int index, const reg_t value) { +	if (type == VAR_GLOBAL) { +#ifdef ENABLE_SCI32 +		if (getSciVersion() >= SCI_VERSION_2) { +			if (_features->audioVolumeSyncUsesGlobals() && shouldSyncAudio()) { +				syncAudioVolumeGlobalsToScummVM(index, value); +			} else if (g_sci->getGameId() == GID_GK1) { +				syncGK1StartupVolumeFromScummVM(index, value); +			} + +			if (_features->supportsTextSpeed()) { +				syncTextSpeedToScummVM(index, value); +			} +		} +#endif +		syncMessageTypeToScummVM(index, value); +	} +} + +bool GuestAdditions::kDoSoundMasterVolumeHook(const int volume) const { +	if (!_features->audioVolumeSyncUsesGlobals() && shouldSyncAudio()) { +		syncMasterVolumeToScummVM(volume); +		return true; +	} +	return false; +} + +#ifdef ENABLE_SCI32 +void GuestAdditions::sendSelectorHook(const reg_t sendObj, Selector &selector, reg_t *argp) { +	if (_features->getMessageTypeSyncStrategy() == kMessageTypeSyncStrategyLSL6Hires) { +		syncMessageTypeToScummVMUsingLSL6HiresStrategy(sendObj, selector, argp); +	} +} + +bool GuestAdditions::audio32SetVolumeHook(const int16 channelIndex, int16 volume) const { +	if (!_features->audioVolumeSyncUsesGlobals() && shouldSyncAudio()) { +		volume = volume * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume; +		if (Common::checkGameGUIOption(GUIO_LINKMUSICTOSFX, ConfMan.get("guioptions"))) { +			ConfMan.setInt("music_volume", volume); +		} +		ConfMan.setInt("sfx_volume", volume); +		ConfMan.setInt("speech_volume", volume); +		g_engine->syncSoundSettings(); +		return true; +	} + +	return false; +} + +void GuestAdditions::kDoSoundSetVolumeHook(const reg_t soundObj, const int16 volume) const { +	if (g_sci->getGameId() == GID_GK1 && shouldSyncAudio()) { +		syncGK1AudioVolumeToScummVM(soundObj, volume); +	} +} +#endif + +#pragma mark - +#pragma mark Message type sync + +void GuestAdditions::syncMessageTypeFromScummVM() const { +	switch (_features->getMessageTypeSyncStrategy()) { +	case kMessageTypeSyncStrategyDefault: +		syncMessageTypeFromScummVMUsingDefaultStrategy(); +		break; + +#ifdef ENABLE_SCI32 +	case kMessageTypeSyncStrategyShivers: +		syncMessageTypeFromScummVMUsingShiversStrategy(); +		break; + +	case kMessageTypeSyncStrategyLSL6Hires: +		syncMessageTypeFromScummVMUsingLSL6HiresStrategy(); +		break; +#endif +	case kMessageTypeSyncStrategyNone: +		break; +	} +} + +void GuestAdditions::syncMessageTypeFromScummVMUsingDefaultStrategy() const { +	uint8 value = 0; +	if (ConfMan.getBool("subtitles")) { +		value |= kMessageTypeSubtitles; +	} +	if (!ConfMan.getBool(("speech_mute"))) { +		value |= kMessageTypeSpeech; +	} + +	if (value == kMessageTypeSubtitles + kMessageTypeSpeech && !_features->supportsSpeechWithSubtitles()) { +		value &= ~kMessageTypeSubtitles; +	} + +	if (value) { +		_state->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, value); +	} + +	if (g_sci->getGameId() == GID_GK1) { +		if (value == kMessageTypeSubtitles) { +			_state->variables[VAR_GLOBAL][kGlobalVarGK1NarratorMode] = NULL_REG; +		} else if (value == kMessageTypeSpeech) { +			_state->variables[VAR_GLOBAL][kGlobalVarGK1NarratorMode] = TRUE_REG; +		} +	} +} + +#ifdef ENABLE_SCI32 +void GuestAdditions::syncMessageTypeFromScummVMUsingShiversStrategy() const { +	if (ConfMan.getBool("subtitles")) { +		_state->variables[VAR_GLOBAL][kGlobalVarShiversFlags] |= 256; +	} else { +		_state->variables[VAR_GLOBAL][kGlobalVarShiversFlags] &= ~256; +	} +} + +void GuestAdditions::syncMessageTypeFromScummVMUsingLSL6HiresStrategy() const { +	// LSL6hires synchronisation happens in send_selector, except when +	// restoring a game, where it happens here +	if (_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags].isNull()) { +		return; +	} + +	reg_t params[] = { make_reg(0, kLSL6HiresSubtitleFlag) }; +	Selector selector; +	reg_t restore; + +	if (ConfMan.getBool("subtitles")) { +		restore = TRUE_REG; +		selector = SELECTOR(clear); +	} else { +		restore = NULL_REG; +		selector = SELECTOR(set); +	} + +	// Attempting to show or hide the ScrollWindow used for subtitles +	// directly (by invoking `show` or `hide`) causes the game to crash with +	// an error about passing an invalid ScrollWindow ID. Fortunately, the +	// game scripts store a flag that restores the window when a game is +	// restored +	_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresRestoreTextWindow] = restore; +	invokeSelector(_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags], selector, 1, params); +} +#endif + +void GuestAdditions::syncMessageTypeToScummVM(const int index, const reg_t value) { +	switch (_features->getMessageTypeSyncStrategy()) { +	case kMessageTypeSyncStrategyDefault: +		syncMessageTypeToScummVMUsingDefaultStrategy(index, value); +		break; + +#ifdef ENABLE_SCI32 +	case kMessageTypeSyncStrategyShivers: +		syncMessageTypeToScummVMUsingShiversStrategy(index, value); +		break; + +	case kMessageTypeSyncStrategyLSL6Hires: +		// LSL6hires synchronisation happens via send_selector +#endif +	case kMessageTypeSyncStrategyNone: +		break; +	} +} + +void GuestAdditions::syncMessageTypeToScummVMUsingDefaultStrategy(const int index, const reg_t value) { +	if (index == kGlobalVarMessageType) { +		// ScummVM audio options haven't been applied yet. Use this set call +		// as a trigger to apply defaults from ScummVM, ignoring the default +		// value that was just received from the game scripts +		if (!_messageTypeSynced || _state->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) { +			_messageTypeSynced = true; +			syncAudioOptionsFromScummVM(); +			return; +		} + +		ConfMan.setBool("subtitles", value.toSint16() & kMessageTypeSubtitles); +		ConfMan.setBool("speech_mute", !(value.toSint16() & kMessageTypeSpeech)); +	} +} + +#ifdef ENABLE_SCI32 +void GuestAdditions::syncMessageTypeToScummVMUsingShiversStrategy(const int index, const reg_t value) { +	if (index == kGlobalVarShiversFlags) { +		// ScummVM audio options haven't been applied yet, so apply them +		// and ignore the default value that was just received from the +		// game scripts +		if (!_messageTypeSynced || _state->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) { +			_messageTypeSynced = true; +			syncAudioOptionsFromScummVM(); +			return; +		} + +		ConfMan.setBool("subtitles", value.toUint16() & 256); +	} +} + +void GuestAdditions::syncMessageTypeToScummVMUsingLSL6HiresStrategy(const reg_t sendObj, Selector &selector, reg_t *argp) { +	if (_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresGameFlags] == sendObj && +		(selector == SELECTOR(clear) || selector == SELECTOR(set))) { + +		if (argp[1].toUint16() == kLSL6HiresSubtitleFlag) { +			if (_messageTypeSynced) { +				ConfMan.setBool("subtitles", selector == SELECTOR(clear)); +			} else if (ConfMan.getBool("subtitles")) { +				selector = SELECTOR(clear); +				argp[-1].setOffset(selector); +				_messageTypeSynced = true; +			} else { +				selector = SELECTOR(set); +				argp[-1].setOffset(selector); +				_messageTypeSynced = true; +			} +		} +	} +} +#endif + +#pragma mark - +#pragma mark Master volume sync + +void GuestAdditions::syncMasterVolumeFromScummVM() const { +	const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume; + +	// When the volume changes from the ScummVM launcher, ScummVM automatically +	// adjusts the software mixer in Engine::syncSoundSettings, but MIDI may not +	// run through the ScummVM mixer so its master volume must be adjusted +	// explicitly +	if (g_sci->_soundCmd) { +		g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : musicVolume); +	} + +#ifdef ENABLE_SCI32 +	const int16 sfxVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume; + +	// Volume was changed from ScummVM during the game, so resync the +	// in-game UI +	syncInGameUI(musicVolume, sfxVolume); +#endif +} + +void GuestAdditions::syncMasterVolumeToScummVM(const int16 masterVolume) const { +	const int scummVMVolume = masterVolume * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX; +	ConfMan.setInt("music_volume", scummVMVolume); + +	if (Common::checkGameGUIOption(GUIO_LINKMUSICTOSFX, ConfMan.get("guioptions"))) { +		ConfMan.setInt("sfx_volume", scummVMVolume); +		if (Common::checkGameGUIOption(GUIO_LINKSPEECHTOSFX, ConfMan.get("guioptions"))) { +			ConfMan.setInt("speech_volume", scummVMVolume); +		} +	} + +	// In SCI32, digital audio volume is controlled separately by +	// kDoAudioVolume +	// TODO: In SCI16, the volume slider only changed the music volume. +	// Is this non-standard behavior better, or just wrong? +	if (getSciVersion() < SCI_VERSION_2) { +		ConfMan.setInt("sfx_volume", scummVMVolume); +		ConfMan.setInt("speech_volume", scummVMVolume); +	} +	g_engine->syncSoundSettings(); +} + +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Globals volume sync + +void GuestAdditions::syncAudioVolumeGlobalsFromScummVM() const { +	// On muting: Setting the music volume to zero when mute is enabled is done +	// only for the games that use MIDI for music playback, since MIDI playback +	// does not always run through the ScummVM mixer. Games that use digital +	// audio for music do not need any extra code since that always runs +	// straight through the audio mixer, which gets muted directly +	switch (g_sci->getGameId()) { +	case GID_GK1: { +		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume; +		const int16 dacVolume = (ConfMan.getInt("sfx_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume; +		syncGK1VolumeFromScummVM(musicVolume, dacVolume); +		syncGK1UI(); +		break; +	} + +	case GID_GK2: { +		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume; +		syncGK2VolumeFromScummVM(musicVolume); +		syncGK2UI(); +		break; +	} + +	case GID_LSL6HIRES: { +		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * kLSL6HiresUIVolumeMax / Audio::Mixer::kMaxMixerVolume; +		syncLSL6HiresVolumeFromScummVM(musicVolume); +		syncLSL6HiresUI(musicVolume); +		break; +	} + +	case GID_PHANTASMAGORIA: { +		reg_t &musicGlobal = _state->variables[VAR_GLOBAL][kGlobalVarPhant1MusicVolume]; +		reg_t &dacGlobal   = _state->variables[VAR_GLOBAL][kGlobalVarPhant1DACVolume]; + +		const int16 oldMusicVolume = musicGlobal.toSint16(); +		const int16 oldDacVolume   = dacGlobal.toSint16(); + +		const int16 musicVolume = (ConfMan.getInt("music_volume") + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume; +		const int16 dacVolume   = (ConfMan.getInt("sfx_volume") + 1)   * Audio32::kMaxVolume / Audio::Mixer::kMaxMixerVolume; + +		g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : musicVolume); + +		// Phant1 has a fragile volume UI. Global volumes need to be set during +		// UI updates to move the volume bars to the correct position +		syncPhant1UI(oldMusicVolume, musicVolume, musicGlobal, oldDacVolume, dacVolume, dacGlobal); +		break; +	} + +	case GID_TORIN: { +		const int16 musicVolume  = (ConfMan.getInt("music_volume") + 1)  * 100 / Audio::Mixer::kMaxMixerVolume; +		const int16 sfxVolume    = (ConfMan.getInt("sfx_volume") + 1)    * 100 / Audio::Mixer::kMaxMixerVolume; +		const int16 speechVolume = (ConfMan.getInt("speech_volume") + 1) * 100 / Audio::Mixer::kMaxMixerVolume; +		syncTorinVolumeFromScummVM(musicVolume, sfxVolume, speechVolume); +		syncTorinUI(musicVolume, sfxVolume, speechVolume); +		break; +	} + +	default: +		error("Trying to sync audio volume globals in a game with no implementation"); +	} +} + +void GuestAdditions::syncGK1StartupVolumeFromScummVM(const int index, const reg_t value) const { +	if (index == kGlobalVarGK1Music1 || index == kGlobalVarGK1Music2 || +		index == kGlobalVarGK1DAC1 || index == kGlobalVarGK1DAC2 || +		index == kGlobalVarGK1DAC3) { + +		int16 volume; +		Selector selector; + +		switch (readSelectorValue(_segMan, value, SELECTOR(type))) { +		case kSoundsMusicType: { +			volume = (ConfMan.getInt("music_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume; +			selector = SELECTOR(musicVolume); +			break; +		} + +		case kSoundsSoundType: { +			volume = (ConfMan.getInt("sound_volume") + 1) * MUSIC_VOLUME_MAX / Audio::Mixer::kMaxMixerVolume; +			selector = SELECTOR(soundVolume); +			break; +		} + +		default: +			error("Unknown sound type"); +		} + +		writeSelectorValue(_segMan, value, selector, volume); +		writeSelectorValue(_segMan, value, selector, volume); +	} +} + +void GuestAdditions::syncGK1VolumeFromScummVM(const int16 musicVolume, const int16 dacVolume) const { +	const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds]; +	if (!soundsId.isNull()) { +		List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements))); +		reg_t soundId = sounds->first; +		while (!soundId.isNull()) { +			Node *sound = _segMan->lookupNode(soundId); +			const int16 type = readSelectorValue(_segMan, sound->value, SELECTOR(type)); +			int16 volume; + +			if (type == kSoundsMusicType) { +				volume = ConfMan.getBool("mute") ? 0 : musicVolume; +				writeSelectorValue(_segMan, sound->value, SELECTOR(musicVolume), musicVolume); +			} else if (type == kSoundsSoundType) { +				volume = dacVolume; +				writeSelectorValue(_segMan, sound->value, SELECTOR(soundVolume), dacVolume); +			} else { +				error("Unknown sound type %d", type); +			} + +			// `setVolume` will set the `vol` property on the sound object; +			// if it did not do this, an invocation of the `setVol` selector +			// would need to be here (though doing so would result in +			// recursion, so don't) +			g_sci->_soundCmd->setVolume(sound->value, volume); +			soundId = sound->succ; +		} +	} +} + +void GuestAdditions::syncGK2VolumeFromScummVM(const int16 musicVolume) const { +	_state->variables[VAR_GLOBAL][kGlobalVarGK2MusicVolume] = make_reg(0, musicVolume); + +	// Calling `setVol` on all sounds is necessary to propagate the volume +	// change to existing sounds, and matches how game scripts propagate +	// volume changes when the in-game music slider is moved +	const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds]; +	if (!soundsId.isNull()) { +		List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements))); +		reg_t soundId = sounds->first; +		while (!soundId.isNull()) { +			Node *sound = _segMan->lookupNode(soundId); +			reg_t params[] = { make_reg(0, musicVolume) }; +			invokeSelector(sound->value, SELECTOR(setVol), 1, params); +			soundId = sound->succ; +		} +	} +} + +void GuestAdditions::syncLSL6HiresVolumeFromScummVM(const int16 musicVolume) const { +	_state->variables[VAR_GLOBAL][kGlobalVarLSL6HiresMusicVolume] = make_reg(0, musicVolume); +	g_sci->_soundCmd->setMasterVolume(ConfMan.getBool("mute") ? 0 : (musicVolume * MUSIC_MASTERVOLUME_MAX / kLSL6HiresUIVolumeMax)); +} + +void GuestAdditions::syncTorinVolumeFromScummVM(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const { +	_state->variables[VAR_GLOBAL][kGlobalVarTorinMusicVolume]  = make_reg(0, musicVolume); +	_state->variables[VAR_GLOBAL][kGlobalVarTorinSFXVolume]    = make_reg(0, sfxVolume); +	_state->variables[VAR_GLOBAL][kGlobalVarTorinSpeechVolume] = make_reg(0, speechVolume); + +	// Calling `reSyncVol` on all sounds is necessary to propagate the +	// volume change to existing sounds, and matches how game scripts +	// propagate volume changes when the in-game volume sliders are moved +	const reg_t soundsId = _state->variables[VAR_GLOBAL][kGlobalVarSounds]; +	if (!soundsId.isNull()) { +		const Selector selector = SELECTOR(reSyncVol); +		List *sounds = _segMan->lookupList(readSelector(_segMan, soundsId, SELECTOR(elements))); +		reg_t soundId = sounds->first; +		while (!soundId.isNull()) { +			Node *sound = _segMan->lookupNode(soundId); +			const reg_t &soundObj = sound->value; + +			if (_segMan->isHeapObject(soundObj) && lookupSelector(_segMan, soundObj, selector, nullptr, nullptr) != kSelectorNone) { +				invokeSelector(sound->value, SELECTOR(reSyncVol)); +			} +			soundId = sound->succ; +		} +	} +} + +void GuestAdditions::syncAudioVolumeGlobalsToScummVM(const int index, const reg_t value) const { +	switch (g_sci->getGameId()) { +	case GID_GK2: +		if (index == kGlobalVarGK2MusicVolume) { +			const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume; +			ConfMan.setInt("music_volume", musicVolume); +		} +		break; + +	case GID_LSL6HIRES: +		if (index == kGlobalVarLSL6HiresMusicVolume) { +			const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / kLSL6HiresUIVolumeMax; +			ConfMan.setInt("music_volume", musicVolume); +		} +		break; + +	case GID_PHANTASMAGORIA: +		if (index == kGlobalVarPhant1MusicVolume) { +			const int16 musicVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX; +			ConfMan.setInt("music_volume", musicVolume); +		} else if (index == kGlobalVarPhant1DACVolume) { +			const int16 dacVolume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / Audio32::kMaxVolume; +			ConfMan.setInt("sfx_volume", dacVolume); +			ConfMan.setInt("speech_volume", dacVolume); +		} +		break; + +	case GID_TORIN: +		if (index == kGlobalVarTorinMusicVolume || +			index == kGlobalVarTorinSFXVolume || +			index == kGlobalVarTorinSpeechVolume) { + +			const int16 volume = value.toSint16() * Audio::Mixer::kMaxMixerVolume / 100; + +			switch (index) { +			case kGlobalVarTorinMusicVolume: +				ConfMan.setInt("music_volume", volume); +				break; +			case kGlobalVarTorinSFXVolume: +				ConfMan.setInt("sfx_volume", volume); +				break; +			case kGlobalVarTorinSpeechVolume: +				ConfMan.setInt("speech_volume", volume); +				break; +			} +		} +		break; + +	default: +		break; +	} +} + +void GuestAdditions::syncGK1AudioVolumeToScummVM(const reg_t soundObj, int16 volume) const { +	const Common::String objName = _segMan->getObjectName(soundObj); +	volume = volume * Audio::Mixer::kMaxMixerVolume / MUSIC_VOLUME_MAX; + +	// Using highest-numbered sound objects to sync only after all slots +	// have been set by the volume slider +	if (objName == "gkMusic2") { +		ConfMan.setInt("music_volume", volume); +		g_engine->syncSoundSettings(); +	} else if (objName == "gkSound3") { +		ConfMan.setInt("sfx_volume", volume); +		ConfMan.setInt("speech_volume", volume); +		g_engine->syncSoundSettings(); +	} +} + +#pragma mark - +#pragma mark Audio UI sync + +void GuestAdditions::syncInGameUI(const int16 musicVolume, const int16 sfxVolume) const { +	if (_state->abortScriptProcessing != kAbortNone) { +		// Attempting to update a UI that is in the process of being destroyed +		// will result in a crash +		return; +	} + +	switch (g_sci->getGameId()) { +	case GID_PQ4: +		syncPQ4UI(musicVolume); +		break; + +	case GID_PQSWAT: +		syncPQSWATUI(); +		break; + +	case GID_QFG4: +		syncQFG4UI(musicVolume); +		break; + +	case GID_SHIVERS: +		syncShivers1UI(sfxVolume); +		break; + +	case GID_SQ6: +		syncSQ6UI(); +		break; + +	default: +		break; +	} +} + +void GuestAdditions::syncGK1UI() const { +	const reg_t bars[] = { _segMan->findObjectByName("musicBar"), +						   _segMan->findObjectByName("soundBar") }; + +	for (int i = 0; i < ARRAYSIZE(bars); ++i) { +		const reg_t barId = bars[i]; +		if (!barId.isNull()) { +			// Resetting the position to 0 causes the bar to refresh its +			// position when it next draws +			writeSelectorValue(_segMan, barId, SELECTOR(position), 0); + +			// The `signal` property indicates bar visibility (for some +			// reason, the normal `-info-` flag is not used) +			if (readSelectorValue(_segMan, barId, SELECTOR(signal)) & 0x20) { +				// `show` pulls a new value from the underlying sound object +				// and refreshes the bar rendering +				invokeSelector(barId, SELECTOR(show)); +			} +		} +	} +} + +void GuestAdditions::syncGK2UI() const { +	const reg_t sliderId = _segMan->findObjectByName("soundSlider"); +	if (!sliderId.isNull() && _segMan->getObject(sliderId)->isInserted()) { +		const reg_t oldAcc = _state->r_acc; +		invokeSelector(sliderId, SELECTOR(initialOff)); +		writeSelector(_segMan, sliderId, SELECTOR(x), _state->r_acc); +		_state->r_acc = oldAcc; +	} +} + +void GuestAdditions::syncLSL6HiresUI(const int16 musicVolume) const { +	const reg_t musicDialId = _segMan->findObjectByName("volumeDial"); +	if (!musicDialId.isNull()) { +		writeSelectorValue(_segMan, musicDialId, SELECTOR(curPos), musicVolume); +		writeSelectorValue(_segMan, musicDialId, SELECTOR(cel), musicVolume); +		reg_t params[] = { make_reg(0, musicVolume) }; +		invokeSelector(musicDialId, SELECTOR(update), 1, params); +		if (_segMan->getObject(musicDialId)->isInserted()) { +			g_sci->_gfxFrameout->kernelUpdateScreenItem(musicDialId); +		} +	} +} + +void GuestAdditions::syncPhant1UI(const int16 oldMusicVolume, const int16 musicVolume, reg_t &musicGlobal, const int16 oldDacVolume, const int16 dacVolume, reg_t &dacGlobal) const { +	const reg_t buttonId = _segMan->findObjectByName("dacVolUp"); +	if (buttonId.isNull() || !_segMan->getObject(buttonId)->isInserted()) { +		// No inserted dacVolUp button means the control panel with the +		// volume controls is not visible and we can just update the values +		// and leave +		musicGlobal.setOffset(musicVolume); +		dacGlobal.setOffset(dacVolume); +		return; +	} + +	reg_t thermo = _segMan->findObjectByName("midiVolThermo"); +	if (!thermo.isNull()) { +		int count = ABS(musicVolume - oldMusicVolume); +		const int stepSize = (musicVolume > oldMusicVolume ? 1 : -1); +		while (count--) { +			musicGlobal.incOffset(stepSize); +			invokeSelector(thermo, SELECTOR(doit)); +		} +	} + +	thermo = _segMan->findObjectByName("dacVolThermo"); +	if (!thermo.isNull()) { +		int count = ABS(dacVolume - oldDacVolume) / 8; +		const int stepSize = (dacVolume > oldDacVolume ? 8 : -8); +		while (count--) { +			dacGlobal.incOffset(stepSize); +			invokeSelector(thermo, SELECTOR(doit)); +		} +	} +} + +void GuestAdditions::syncPQ4UI(const int16 musicVolume) const { +	const SegmentId segment = _segMan->getScriptSegment(9, SCRIPT_GET_DONT_LOAD); +	if (segment != 0 && _segMan->getScript(segment)->getLocalsCount() > 2) { +		const reg_t barId = _segMan->getScript(segment)->getLocalsBegin()[2]; +		if (!barId.isNull()) { +			reg_t params[] = { make_reg(0, musicVolume) }; +			invokeSelector(barId, SELECTOR(setSize), 1, params); +		} +	} +} + +void GuestAdditions::syncPQSWATUI() const { +	const reg_t barId = _segMan->findObjectByName("volumeLed"); +	if (!barId.isNull() && _segMan->getObject(barId)->isInserted()) { +		invokeSelector(barId, SELECTOR(displayValue)); +	} +} + +void GuestAdditions::syncQFG4UI(const int16 musicVolume) const { +	const reg_t sliderId = _segMan->findObjectByName("volumeSlider"); +	if (!sliderId.isNull()) { +		const int16 yPosition = 84 - musicVolume * 34 / 10; +		writeSelectorValue(_segMan, sliderId, SELECTOR(y), yPosition); + +		// There does not seem to be any good way to learn whether the +		// volume slider is visible (and thus eligible for +		// kUpdateScreenItem) +		const reg_t planeId = readSelector(_segMan, sliderId, SELECTOR(plane)); +		if (g_sci->_gfxFrameout->getPlanes().findByObject(planeId) != nullptr) { +			g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId); +		} +	} +} + +void GuestAdditions::syncShivers1UI(const int16 dacVolume) const { +	const reg_t sliderId = _segMan->findObjectByName("spVolume"); +	if (!sliderId.isNull()) { +		const int16 xPosition = dacVolume * 78 / Audio32::kMaxVolume + 32; +		writeSelectorValue(_segMan, sliderId, SELECTOR(x), xPosition); +		if (_segMan->getObject(sliderId)->isInserted()) { +			g_sci->_gfxFrameout->kernelUpdateScreenItem(sliderId); +		} +	} +} + +void GuestAdditions::syncSQ6UI() const { +	const reg_t bars[] = { _segMan->findObjectByName("musicBar"), +						   _segMan->findObjectByName("soundBar") }; +	for (int i = 0; i < ARRAYSIZE(bars); ++i) { +		const reg_t barId = bars[i]; +		if (!barId.isNull()) { +			invokeSelector(barId, SELECTOR(show)); +		} +	} +} + +void GuestAdditions::syncTorinUI(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const { +	const reg_t sliders[] = { _segMan->findObjectByName("oMusicScroll"), +							  _segMan->findObjectByName("oSFXScroll"), +							  _segMan->findObjectByName("oAudioScroll") }; +	const int16 values[] = { musicVolume, sfxVolume, speechVolume }; +	for (int i = 0; i < ARRAYSIZE(sliders); ++i) { +		const reg_t sliderId = sliders[i]; +		if (!sliderId.isNull()) { +			reg_t params[] = { make_reg(0, values[i]) }; +			invokeSelector(sliderId, SELECTOR(setPos), 1, params); +		} +	} +} + +#pragma mark - +#pragma mark Talk speed sync + +void GuestAdditions::syncTextSpeedFromScummVM() const { +	const int16 textSpeed = 8 - (ConfMan.getInt("talkspeed") + 1) * 8 / 255; + +	_state->variables[VAR_GLOBAL][kGlobalVarTextSpeed] = make_reg(0, textSpeed); + +	if (g_sci->getGameId() == GID_GK1) { +		const reg_t textBarId = _segMan->findObjectByName("textBar"); +		if (!textBarId.isNull()) { +			// Resetting the bar position to 0 causes the game to retrieve the +			// new text speed value and re-render +			writeSelectorValue(_segMan, textBarId, SELECTOR(position), 0); +		} +	} +} + +void GuestAdditions::syncTextSpeedToScummVM(const int index, const reg_t value) const { +	if (index == kGlobalVarTextSpeed) { +		ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8); +	} +} + +#endif + +} // End of namespace Sci diff --git a/engines/sci/engine/guest_additions.h b/engines/sci/engine/guest_additions.h new file mode 100644 index 0000000000..a6875d43ad --- /dev/null +++ b/engines/sci/engine/guest_additions.h @@ -0,0 +1,245 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_ENGINE_GUEST_ADDITIONS_H +#define SCI_ENGINE_GUEST_ADDITIONS_H + +#include "sci/engine/vm_types.h" + +namespace Sci { + +struct EngineState; +class GameFeatures; +class SegManager; + +/** + * The GuestAdditions class hooks into the SCI virtual machine to provide + * enhanced interactions between the ScummVM GUI and the game engine. Currently, + * this enhanced functionality encompasses synchronisation of audio volumes and + * other audio-related settings. + * + * Some parts of the audio sync are applied as script patches using the normal + * ScriptPatcher mechanism. These patches are designed to prevent the game from + * resetting to a default volume when starting up or loading a save. + */ +class GuestAdditions { +public: +	GuestAdditions(EngineState *state, GameFeatures *features); + +	/** +	 * Synchronises audio volume settings from ScummVM to the game. Called +	 * whenever the ScummVM launcher is dismissed. +	 */ +	void syncSoundSettings() const; + +	/** +	 * Synchronises all audio settings from ScummVM to the game. Called when the +	 * game is first started, and when save games are loaded. +	 */ +	void syncAudioOptionsFromScummVM() const; + +	/** +	 * Clears audio settings synchronisation state. +	 */ +	void reset(); + +private: +	EngineState *_state; +	GameFeatures *_features; +	SegManager *_segMan; + +	/** +	 * Convenience function for invoking selectors that reduces boilerplate code +	 * required by Sci::invokeSelector. +	 */ +	void invokeSelector(const reg_t objId, const Selector selector, const int argc = 0, const StackPtr argv = nullptr) const; + +	/** +	 * Determines whether the current stack contains calls from audio controls +	 * that indicate a user-initiated change of audio settings. +	 */ +	bool shouldSyncAudio() const; + +#pragma mark - +#pragma mark Hooks + +public: +	/** +	 * Guest additions hook for SciEngine::runGame. +	 */ +	void sciEngineRunGameHook(); + +	/** +	 * Guest additions hook for write_var. +	 */ +	void writeVarHook(const int type, const int index, const reg_t value); + +	/** +	 * Guest additions hook for kDoSoundMasterVolume. +	 * +	 * @returns true if the default action should be prevented +	 */ +	bool kDoSoundMasterVolumeHook(const int volume) const; + +#ifdef ENABLE_SCI32 +	/** +	 * Guest additions hook for send_selector. +	 */ +	void sendSelectorHook(const reg_t sendObj, Selector &selector, reg_t *argp); + +	/** +	 * Guest additions hook for Audio32::setVolume. +	 * +	 * @returns true if the default action should be prevented +	 */ +	bool audio32SetVolumeHook(const int16 channelIndex, const int16 volume) const; + +	/** +	 * Guest additions hook for kDoSoundSetVolume. +	 */ +	void kDoSoundSetVolumeHook(const reg_t soundObj, const int16 volume) const; +#endif + +#pragma mark - +#pragma mark Message type sync + +private: +	/** +	 * true if the message type (text/speech/text+speech) has been synchronised +	 * from ScummVM to the game. +	 */ +	bool _messageTypeSynced; + +	/** +	 * Synchronises the message type (speech/text/speech+text) from a ScummVM to +	 * a game. +	 */ +	void syncMessageTypeFromScummVM() const; + +	void syncMessageTypeFromScummVMUsingDefaultStrategy() const; +#ifdef ENABLE_SCI32 +	void syncMessageTypeFromScummVMUsingShiversStrategy() const; +	void syncMessageTypeFromScummVMUsingLSL6HiresStrategy() const; +#endif + +	/** +	 * Synchronises the message type (speech/text/speech+text) from a game to +	 * ScummVM. +	 */ +	void syncMessageTypeToScummVM(const int index, const reg_t value); + +	void syncMessageTypeToScummVMUsingDefaultStrategy(const int index, const reg_t value); +#ifdef ENABLE_SCI32 +	void syncMessageTypeToScummVMUsingShiversStrategy(const int index, const reg_t value); +	void syncMessageTypeToScummVMUsingLSL6HiresStrategy(const reg_t sendObj, Selector &selector, reg_t *argp); +#endif + +#pragma mark - +#pragma mark Master volume sync + +private: +	/** +	 * Synchronises audio volume settings from ScummVM to the game, for games +	 * that do not store volume themselves and just call to the kernel. +	 */ +	void syncMasterVolumeFromScummVM() const; + +	/** +	 * Synchronises audio volume settings from the game to ScummVM, for games +	 * that do not store volume themselves and just call to the kernel. +	 */ +	void syncMasterVolumeToScummVM(const int16 masterVolume) const; + +#ifdef ENABLE_SCI32 +#pragma mark - +#pragma mark Globals volume sync + +private: +	/** +	 * Synchronises audio volume settings from ScummVM to the game, for games +	 * that store volumes in globals. +	 */ +	void syncAudioVolumeGlobalsFromScummVM() const; + +	/** +	 * Synchronises audio volume settings from ScummVM to GK1 at game startup +	 * time. +	 */ +	void syncGK1StartupVolumeFromScummVM(const int index, const reg_t value) const; + +	void syncGK1VolumeFromScummVM(const int16 musicVolume, const int16 dacVolume) const; +	void syncGK2VolumeFromScummVM(const int16 musicVolume) const; +	void syncLSL6HiresVolumeFromScummVM(const int16 musicVolume) const; +	void syncTorinVolumeFromScummVM(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const; + +	/** +	 * Synchronises audio volume settings from a game to ScummVM, for games +	 * that store volumes in globals. +	 */ +	void syncAudioVolumeGlobalsToScummVM(const int index, const reg_t value) const; + +	/** +	 * Synchronises audio volume settings from GK1 to ScummVM. +	 */ +	void syncGK1AudioVolumeToScummVM(const reg_t soundObj, const int16 volume) const; + +#pragma mark - +#pragma mark Audio UI sync + +private: +	/** +	 * Synchronises the in-game control panel UI in response to a change of +	 * volume from the ScummVM GUI. The values of the volume parameters passed +	 * to this function are game-specific. +	 */ +	void syncInGameUI(const int16 musicVolume, const int16 sfxVolume) const; + +	void syncGK1UI() const; +	void syncGK2UI() const; +	void syncLSL6HiresUI(const int16 musicVolume) const; +	void syncPhant1UI(const int16 oldMusicVolume, const int16 musicVolume, reg_t &musicGlobal, const int16 oldDacVolume, const int16 dacVolume, reg_t &dacGlobal) const; +	void syncPQ4UI(const int16 musicVolume) const; +	void syncPQSWATUI() const; +	void syncQFG4UI(const int16 musicVolume) const; +	void syncShivers1UI(const int16 dacVolume) const; +	void syncSQ6UI() const; +	void syncTorinUI(const int16 musicVolume, const int16 sfxVolume, const int16 speechVolume) const; + +#pragma mark - +#pragma mark Talk speed sync + +private: +	/** +	 * Synchronises text speed settings from ScummVM to a game. +	 */ +	void syncTextSpeedFromScummVM() const; + +	/** +	 * Synchronises text speed settings from a game to ScummVM. +	 */ +	void syncTextSpeedToScummVM(const int index, const reg_t value) const; +#endif +}; + +} // End of namespace Sci + +#endif // SCI_ENGINE_GUEST_ADDITIONS_H diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index 4a5e4f3df6..e7cad21d67 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -46,7 +46,7 @@ reg_t kDoSound(EngineState *s, int argc, reg_t *argv) {  	error("not supposed to call this");  } -#define CREATE_DOSOUND_FORWARD(_name_) reg_t k##_name_(EngineState *s, int argc, reg_t *argv) { return g_sci->_soundCmd->k##_name_(argc, argv, s->r_acc); } +#define CREATE_DOSOUND_FORWARD(_name_) reg_t k##_name_(EngineState *s, int argc, reg_t *argv) { return g_sci->_soundCmd->k##_name_(s, argc, argv); }  CREATE_DOSOUND_FORWARD(DoSoundInit)  CREATE_DOSOUND_FORWARD(DoSoundPlay) @@ -77,21 +77,21 @@ reg_t kDoSoundPhantasmagoriaMac(EngineState *s, int argc, reg_t *argv) {  	switch (argv[0].toUint16()) {  	case 0: -		return g_sci->_soundCmd->kDoSoundMasterVolume(argc - 1, argv + 1, s->r_acc); +		return g_sci->_soundCmd->kDoSoundMasterVolume(s, argc - 1, argv + 1);  	case 2: -		return g_sci->_soundCmd->kDoSoundInit(argc - 1, argv + 1, s->r_acc); +		return g_sci->_soundCmd->kDoSoundInit(s, argc - 1, argv + 1);  	case 3: -		return g_sci->_soundCmd->kDoSoundDispose(argc - 1, argv + 1, s->r_acc); +		return g_sci->_soundCmd->kDoSoundDispose(s, argc - 1, argv + 1);  	case 4: -		return g_sci->_soundCmd->kDoSoundPlay(argc - 1, argv + 1, s->r_acc); +		return g_sci->_soundCmd->kDoSoundPlay(s, argc - 1, argv + 1);  	case 5: -		return g_sci->_soundCmd->kDoSoundStop(argc - 1, argv + 1, s->r_acc); +		return g_sci->_soundCmd->kDoSoundStop(s, argc - 1, argv + 1);  	case 8: -		return g_sci->_soundCmd->kDoSoundSetVolume(argc - 1, argv + 1, s->r_acc); +		return g_sci->_soundCmd->kDoSoundSetVolume(s, argc - 1, argv + 1);  	case 9: -		return g_sci->_soundCmd->kDoSoundSetLoop(argc - 1, argv + 1, s->r_acc); +		return g_sci->_soundCmd->kDoSoundSetLoop(s, argc - 1, argv + 1);  	case 10: -		return g_sci->_soundCmd->kDoSoundUpdateCues(argc - 1, argv + 1, s->r_acc); +		return g_sci->_soundCmd->kDoSoundUpdateCues(s, argc - 1, argv + 1);  	}  	error("Unknown kDoSound Phantasmagoria Mac subop %d", argv[0].toUint16()); diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h index 61f942c04a..dca7e97e81 100644 --- a/engines/sci/engine/object.h +++ b/engines/sci/engine/object.h @@ -179,6 +179,10 @@ public:  			_infoSelectorSci3 &= ~flag;  		}  	} + +	bool isInserted() const { +		return getInfoSelector().toUint16() & kInfoFlagViewInserted; +	}  #endif  	reg_t getNameSelector() const { diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index f5ec2d300c..97e53a0742 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -1079,12 +1079,29 @@ static const uint16 gk2InvScrollPatch[] = {  	PATCH_END  }; +// The init code that runs when GK2 starts up unconditionally resets the +// music volume to 63, but the game should always use the volume stored in +// ScummVM. +// Applies to at least: English 1.00 CD +static const uint16 gk2VolumeResetSignature[] = { +	SIG_MAGICDWORD, +	0x35, 0x3f, // ldi $3f +	0xa1, 0x4c, // sag $4c (music volume) +	SIG_END +}; + +static const uint16 gk2VolumeResetPatch[] = { +	0x33, 0x02,  // jmp 2 [past volume changes] +	PATCH_END +}; +  //          script, description,                                              signature                         patch  static const SciScriptPatcherEntry gk2Signatures[] = { +	{  true,     0, "disable volume reset on startup",                     1, gk2VolumeResetSignature,          gk2VolumeResetPatch }, +	{  true,    23, "inventory starts scroll down in the wrong direction", 1, gk2InvScrollSignature,            gk2InvScrollPatch },  	{  true, 64990, "increase number of save games",                       1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },  	{  true, 64990, "increase number of save games",                       1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },  	{  true, 64990, "disable change directory button",                     1, sci2ChangeDirSignature,           sci2ChangeDirPatch }, -	{  true,    23, "inventory starts scroll down in the wrong direction", 1, gk2InvScrollSignature,            gk2InvScrollPatch },  	SCI_SIGNATUREENTRY_TERMINATOR  }; @@ -2295,8 +2312,30 @@ static const uint16 larry6HiresPatchSetScale[] = {  	PATCH_END  }; +// The init code that runs when LSL6hires starts up unconditionally resets the +// master music volume to 12 (and the volume dial to 11), but the game should +// always use the volume stored in ScummVM. +// Applies to at least: English CD +static const uint16 larry6HiresSignatureVolumeReset[] = { +	SIG_MAGICDWORD, +	0x38, SIG_UINT16(0x221), // pushi $221 (masterVolume) +	0x78,                    // push1 +	0x39, 0x0c,              // push $0c +	0x81, 0x01,              // lag $01 +	0x4a, SIG_UINT16(0x06),  // send $6 +	0x35, 0x0b,              // ldi $0b +	0xa1, 0xc2,              // sag $c2 +	SIG_END +}; + +static const uint16 larry6HiresPatchVolumeReset[] = { +	0x32, PATCH_UINT16(12),  // jmp 12 [past volume changes] +	PATCH_END +}; +  //          script, description,                                      signature                         patch  static const SciScriptPatcherEntry larry6HiresSignatures[] = { +	{  true,    71, "disable volume reset on startup",             1, larry6HiresSignatureVolumeReset,  larry6HiresPatchVolumeReset },  	{  true,   270, "fix incorrect setScale call",                 1, larry6HiresSignatureSetScale,     larry6HiresPatchSetScale },  	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },  	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 }, @@ -3151,9 +3190,42 @@ static const SciScriptPatcherEntry mothergooseHiresSignatures[] = {  #pragma mark -  #pragma mark Phantasmagoria +// Phantasmagoria persists audio volumes in the save games, but ScummVM manages +// game volumes through the launcher, so stop the game from overwriting the +// ScummVM volumes with volumes from save games. +// Applies to at least: English CD +static const uint16 phant1SignatureSavedVolume[] = { +	0x7a,                         // push2 +	0x39, 0x08,                   // pushi 8 +	0x38, SIG_UINT16(0x20b),      // push $20b (readWord) +	0x76,                         // push0 +	0x72, SIG_UINT16(0x13c),      // lofsa $13c (PREF.DAT) +	0x4a, SIG_UINT16(0x04),       // send 4 +	SIG_MAGICDWORD, +	0xa1, 0xbc,                   // sag $bc +	0x36,                         // push +	0x43, 0x76, SIG_UINT16(0x04), // callk DoAudio[76], 4 +	0x7a,                         // push2 +	0x76,                         // push0 +	0x38, SIG_UINT16(0x20b),      // push $20b (readWord) +	0x76,                         // push0 +	0x72, SIG_UINT16(0x13c),      // lofsa $13c (PREF.DAT) +	0x4a, SIG_UINT16(0x04),       // send 4 +	0xa1, 0xbb,                   // sag $bb +	0x36,                         // push +	0x43, 0x75, SIG_UINT16(0x04), // callk DoSound[75], 4 +	SIG_END +}; + +static const uint16 phant1PatchSavedVolume[] = { +	0x32, PATCH_UINT16(36),         // jmp [to prefFile::close] +	PATCH_END +}; +  //          script, description,                                      signature                        patch  static const SciScriptPatcherEntry phantasmagoriaSignatures[] = {  	{  true,   901, "invalid array construction",                  1, sci21IntArraySignature,          sci21IntArrayPatch }, +	{  true,  1111, "ignore audio settings from save game",        1, phant1SignatureSavedVolume,      phant1PatchSavedVolume },  	SCI_SIGNATUREENTRY_TERMINATOR  }; @@ -3312,6 +3384,50 @@ static const SciScriptPatcherEntry pq4Signatures[] = {  	SCI_SIGNATUREENTRY_TERMINATOR  }; +#pragma mark - +#pragma mark Police Quest: SWAT + +// The init code that runs when PQ:SWAT starts up unconditionally resets the +// master sound volume to 127, but the game should always use the volume stored +// in ScummVM. +// Applies to at least: English CD +static const uint16 pqSwatSignatureVolumeReset1[] = { +	SIG_MAGICDWORD, +	0x38, SIG_UINT16(0x21a), // pushi $21a (masterVolume) +	0x78,                    // push1 +	0x39, 0x7f,              // push $7f +	0x54, SIG_UINT16(0x06),  // self 6 +	SIG_END +}; + +static const uint16 pqSwatPatchVolumeReset1[] = { +	0x32, PATCH_UINT16(6), // jmp 6 [past volume reset] +	PATCH_END +}; + +// pqInitCode::doit +static const uint16 pqSwatSignatureVolumeReset2[] = { +	SIG_MAGICDWORD, +	0x38, SIG_UINT16(0x21a), // pushi $21a (masterVolume) +	0x78,                    // push1 +	0x39, 0x0f,              // pushi $f +	0x81, 0x01,              // lag 1 +	0x4a, SIG_UINT16(0x06),  // send 6 +	SIG_END +}; + +static const uint16 pqSwatPatchVolumeReset2[] = { +	0x32, PATCH_UINT16(8), // jmp 8 [past volume reset] +	PATCH_END +}; + +//          script, description,                                      signature                         patch +static const SciScriptPatcherEntry pqSwatSignatures[] = { +	{  true,     0, "disable volume reset on startup",             1, pqSwatSignatureVolumeReset1,       pqSwatPatchVolumeReset1 }, +	{  true,     1, "disable volume reset on startup",             1, pqSwatSignatureVolumeReset2,       pqSwatPatchVolumeReset2 }, +	SCI_SIGNATUREENTRY_TERMINATOR +}; +  #endif  // =========================================================================== @@ -4284,8 +4400,41 @@ static const SciScriptPatcherEntry qfg3Signatures[] = {  #pragma mark -  #pragma mark Quest for Glory 4 +// The init code that runs when QFG4 starts up unconditionally resets the +// master music volume to 15, but the game should always use the volume stored +// in ScummVM. +// Applies to at least: English floppy +static const uint16 qfg4SignatureVolumeReset[] = { +	SIG_MAGICDWORD, +	0x38, SIG_UINT16(0x215), // pushi $215 (masterVolume) +	0x78,                    // push1 +	0x39, 0x0f,              // pushi $f +	0x81, 0x01,              // lag 1 (Glory object) +	0x4a, SIG_UINT16(0x06),  // send 6 +	SIG_END +}; + +// Same as above, but with a different masterVolume selector. +// Applies to at least: English CD +static const uint16 qfg4CDSignatureVolumeReset[] = { +	SIG_MAGICDWORD, +	0x38, SIG_UINT16(0x217), // pushi $217 (masterVolume) +	0x78,                    // push1 +	0x39, 0x0f,              // pushi $f +	0x81, 0x01,              // lag 1 (Glory object) +	0x4a, SIG_UINT16(0x06),  // send 6 +	SIG_END +}; + +static const uint16 qfg4PatchVolumeReset[] = { +	0x32, PATCH_UINT16(8),  // jmp 8 [past volume changes] +	PATCH_END +}; +  //          script, description,                                      signature                         patch  static const SciScriptPatcherEntry qfg4Signatures[] = { +	{  true,     1, "disable volume reset on startup (floppy)",    1, qfg4SignatureVolumeReset,         qfg4PatchVolumeReset }, +	{  true,     1, "disable volume reset on startup (CD)",        1, qfg4CDSignatureVolumeReset,       qfg4PatchVolumeReset },  	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },  	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },  	{  true, 64990, "disable change directory button",             1, sci2ChangeDirSignature,           sci2ChangeDirPatch }, @@ -4948,8 +5097,57 @@ static const SciScriptPatcherEntry sq6Signatures[] = {  #pragma mark -  #pragma mark Torins Passage +// The init code that runs when Torin starts up unconditionally resets the +// master music volume to defaults, but the game should always use the volume +// stored in ScummVM. +// Applies to at least: English CD +static const uint16 torinVolumeResetSignature1[] = { +	SIG_MAGICDWORD, +	0x35, 0x28, // ldi $28 +	0xa1, 0xe3, // sag $e3 (music volume) +	0x35, 0x3c, // ldi $3c +	0xa1, 0xe4, // sag $e4 (sfx volume) +	0x35, 0x64, // ldi $64 +	0xa1, 0xe5, // sag $e5 (speech volume) +	SIG_END +}; + +static const uint16 torinVolumeResetPatch1[] = { +	0x33, 0x0a, // jmp [past volume resets] +	PATCH_END +}; + +// The init code that runs when Torin starts up unconditionally resets the +// master music volume to values stored in torin.prf, but the game should always +// use the volume stored in ScummVM. +// Applies to at least: English CD +static const uint16 torinVolumeResetSignature2[] = { +	SIG_MAGICDWORD, +	0x38, SIG_UINT16(0x20b), // pushi $020b +	0x76,                    // push0 +	SIG_ADDTOOFFSET(6),      // advance file stream +	0xa1, 0xe3,              // sag $e3 (music volume) +	SIG_ADDTOOFFSET(10),     // advance file stream +	0xa1, 0xe4,              // sag $e4 (sfx volume) +	SIG_ADDTOOFFSET(10),     // advance file stream +	0xa1, 0xe5,              // sag $e5 (speech volume) +	SIG_END +}; + +static const uint16 torinVolumeResetPatch2[] = { +	PATCH_ADDTOOFFSET(10), // advance file stream +	0x18, 0x18,            // waste bytes +	PATCH_ADDTOOFFSET(10), // advance file stream +	0x18, 0x18,            // waste bytes +	PATCH_ADDTOOFFSET(10), // advance file stream +	0x18, 0x18,            // waste bytes +	PATCH_END +}; +  //          script, description,                                      signature                         patch  static const SciScriptPatcherEntry torinSignatures[] = { +	{  true, 64000, "disable volume reset on startup 1/2",         1, torinVolumeResetSignature1,        torinVolumeResetPatch1 }, +	{  true, 64000, "disable volume reset on startup 2/2",         1, torinVolumeResetSignature2,        torinVolumeResetPatch2 },  	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature1,           sci2NumSavesPatch1 },  	{  true, 64990, "increase number of save games",               1, sci2NumSavesSignature2,           sci2NumSavesPatch2 },  	SCI_SIGNATUREENTRY_TERMINATOR @@ -5469,6 +5667,9 @@ void ScriptPatcher::processScript(uint16 scriptNr, SciSpan<byte> scriptData) {  	case GID_PQ4:  		signatureTable = pq4Signatures;  		break; +	case GID_PQSWAT: +		signatureTable = pqSwatSignatures; +		break;  #endif  	case GID_QFG1:  		signatureTable = qfg1egaSignatures; diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index 2bc4051a79..8a1a9e76d4 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -202,6 +202,20 @@ void Kernel::mapSelectors() {  	FIND_SELECTOR(magnifier);  	FIND_SELECTOR(frameOut);  	FIND_SELECTOR(casts); +	FIND_SELECTOR(setVol); +	FIND_SELECTOR(reSyncVol); +	FIND_SELECTOR(set); +	FIND_SELECTOR(clear); +	FIND_SELECTOR(curPos); +	FIND_SELECTOR(update); +	FIND_SELECTOR(show); +	FIND_SELECTOR(position); +	FIND_SELECTOR(musicVolume); +	FIND_SELECTOR(soundVolume); +	FIND_SELECTOR(initialOff); +	FIND_SELECTOR(setPos); +	FIND_SELECTOR(setSize); +	FIND_SELECTOR(displayValue);  #endif  } diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index 8d1edeb489..d8c0bf711b 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -158,6 +158,20 @@ struct SelectorCache {  	Selector magnifier;  	Selector frameOut;  	Selector casts; // needed for sync'ing screen items/planes with scripts, when our save/restore code is patched in (see GfxFrameout::syncWithScripts) +	Selector setVol; // for GK2 volume sync on restore +	Selector reSyncVol; // for Torin volume sync on restore +	Selector set; // for LSL6hires subtitle sync +	Selector clear; // for LSL6hires subtitle sync +	Selector curPos; // for LSL6hires volume sync +	Selector update; // for LSL6hires volume sync +	Selector show; // for GK1 volume sync +	Selector position; // for GK1 volume sync +	Selector musicVolume; // for GK1 volume sync +	Selector soundVolume; // for GK1 volume sync +	Selector initialOff; // for GK2 volume sync +	Selector setPos; // for Torin volume sync +	Selector setSize; // for PQ4 volume sync +	Selector displayValue; // for PQ:SWAT volume sync  #endif  }; diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index 76df322b30..6e2229727f 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -19,14 +19,12 @@   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.   *   */ -  #include "common/system.h"  #include "sci/sci.h"	// for INCLUDE_OLDGFX  #include "sci/debug.h"	// for g_debug_sleeptime_factor -#include "sci/event.h" -  #include "sci/engine/file.h" +#include "sci/engine/guest_additions.h"  #include "sci/engine/kernel.h"  #include "sci/engine/state.h"  #include "sci/engine/selector.h" @@ -84,6 +82,8 @@ void EngineState::reset(bool isRestoring) {  		_memorySegmentSize = 0;  		_fileHandles.resize(5);  		abortScriptProcessing = kAbortNone; +	} else { +		g_sci->_guestAdditions->reset();  	}  	// reset delayed restore game functionality @@ -120,7 +120,6 @@ void EngineState::reset(bool isRestoring) {  	scriptGCInterval = GC_INTERVAL;  	_videoState.reset(); -	_syncedAudioOptions = false;  }  void EngineState::speedThrottler(uint32 neededSleep) { diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index 5297a176d3..dd6b728716 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -214,7 +214,6 @@ public:  	// TODO: Excise video code from the state manager  	VideoState _videoState; -	bool _syncedAudioOptions;  	/**  	 * Resets the engine state. diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 3535b278c0..7e514a2dc6 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -27,7 +27,13 @@  #include "sci/sci.h"  #include "sci/console.h"  #include "sci/resource.h" +#ifdef ENABLE_SCI32 +#include "audio/mixer.h" +#include "sci/sound/audio32.h" +#include "sci/sound/music.h" +#endif  #include "sci/engine/features.h" +#include "sci/engine/guest_additions.h"  #include "sci/engine/state.h"  #include "sci/engine/kernel.h"  #include "sci/engine/object.h" @@ -199,36 +205,7 @@ static void write_var(EngineState *s, int type, int index, reg_t value) {  		s->variables[type][index] = value; -#ifdef ENABLE_SCI32 -		if (type == VAR_GLOBAL && getSciVersion() >= SCI_VERSION_2 && g_sci->getEngineState()->_syncedAudioOptions) { - -			switch (g_sci->getGameId()) { -			case GID_LSL6HIRES: -				if (index == kGlobalVarLSL6HiresTextSpeed) { -					ConfMan.setInt("talkspeed", (14 - value.toSint16()) * 255 / 13); -				} -				break; -			default: -				if (index == kGlobalVarTextSpeed) { -					ConfMan.setInt("talkspeed", (8 - value.toSint16()) * 255 / 8); -				} -			} -		} -#endif - -		if (type == VAR_GLOBAL && index == kGlobalVarMessageType) { -			// The game is trying to change its speech/subtitle settings -			if (!g_sci->getEngineState()->_syncedAudioOptions || s->variables[VAR_GLOBAL][kGlobalVarQuit] == TRUE_REG) { -				// ScummVM audio options haven't been applied yet, so apply them. -				// We also force the ScummVM audio options when loading a game from -				// the launcher. -				g_sci->syncIngameAudioOptions(); -				g_sci->getEngineState()->_syncedAudioOptions = true; -			} else { -				// Update ScummVM's audio options -				g_sci->updateScummVMAudioOptions(); -			} -		} +		g_sci->_guestAdditions->writeVarHook(type, index, value);  	}  } @@ -311,6 +288,10 @@ ExecStack *send_selector(EngineState *s, reg_t send_obj, reg_t work_obj, StackPt  		if (argc > 0x800)	// More arguments than the stack could possibly accomodate for  			error("send_selector(): More than 0x800 arguments to function call"); +#ifdef ENABLE_SCI32 +		g_sci->_guestAdditions->sendSelectorHook(send_obj, selector, argp); +#endif +  		SelectorType selectorType = lookupSelector(s->_segMan, send_obj, selector, &varp, &funcp);  		if (selectorType == kSelectorNone)  			error("Send to invalid selector 0x%x of object at %04x:%04x", 0xffff & selector, PRINT_REG(send_obj)); diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index 0444cf5ffc..1ac6fed0b6 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -142,16 +142,32 @@ enum GlobalVar {  	kGlobalVarCurrentRoom    = 2,  	kGlobalVarSpeed          = 3,  // SCI16  	kGlobalVarQuit           = 4, +	kGlobalVarSounds         = 8,  	kGlobalVarPlanes         = 10, // SCI32  	kGlobalVarCurrentRoomNo  = 11,  	kGlobalVarPreviousRoomNo = 12,  	kGlobalVarNewRoomNo      = 13,  	kGlobalVarScore          = 15, +	kGlobalVarGK2MusicVolume = 76, // 0 to 127  	kGlobalVarFastCast       = 84, // SCI16  	kGlobalVarMessageType    = 90,  	kGlobalVarTextSpeed      = 94, // SCI32; 0 is fastest, 8 is slowest -	kGlobalVarLSL6HiresTextSpeed = 167, // 1 is fastest, 14 is slowest -	kGlobalVarShivers1Score  = 349 +	kGlobalVarGK1Music1            = 102, // 0 to 127 +	kGlobalVarGK1Music2            = 103, // 0 to 127 +	kGlobalVarLSL6HiresGameFlags   = 137, +	kGlobalVarGK1NarratorMode      = 166, // 0 for text, 1 for speech +	kGlobalVarPhant1MusicVolume    = 187, // 0 to 15 +	kGlobalVarPhant1DACVolume      = 188, // 0 to 127 +	kGlobalVarLSL6HiresMusicVolume = 194, // 0 to 13 +	kGlobalVarGK1DAC1              = 207, // 0 to 127 +	kGlobalVarGK1DAC2              = 208, // 0 to 127 +	kGlobalVarLSL6HiresRestoreTextWindow = 210, +	kGlobalVarGK1DAC3              = 211, // 0 to 127 +	kGlobalVarShiversFlags         = 211, +	kGlobalVarTorinMusicVolume     = 227, // 0 to 100 +	kGlobalVarTorinSFXVolume       = 228, // 0 to 100 +	kGlobalVarTorinSpeechVolume    = 229, // 0 to 100 +	kGlobalVarShivers1Score        = 349  };  /** Number of kernel calls in between gcs; should be < 50000 */ diff --git a/engines/sci/module.mk b/engines/sci/module.mk index eb2c6a148b..7decff6e7a 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -12,6 +12,7 @@ MODULE_OBJS := \  	engine/features.o \  	engine/file.o \  	engine/gc.o \ +	engine/guest_additions.o \  	engine/kernel.o \  	engine/kevent.o \  	engine/kfile.o \ diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 34930a648e..c8e500b700 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -33,6 +33,7 @@  #include "sci/event.h"  #include "sci/engine/features.h" +#include "sci/engine/guest_additions.h"  #include "sci/engine/message.h"  #include "sci/engine/object.h"  #include "sci/engine/state.h" @@ -94,6 +95,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam  	_video32 = nullptr;  	_gfxCursor32 = nullptr;  #endif +	_guestAdditions = nullptr;  	_features = 0;  	_resMan = 0;  	_gamestate = 0; @@ -202,6 +204,7 @@ SciEngine::~SciEngine() {  	delete _kernel;  	delete _vocabulary;  	delete _console; +	delete _guestAdditions;  	delete _features;  	delete _gfxMacIconBar; @@ -280,6 +283,7 @@ Common::Error SciEngine::run() {  		_vocabulary = new Vocabulary(_resMan, false);  	_gamestate = new EngineState(segMan); +	_guestAdditions = new GuestAdditions(_gamestate, _features);  	_eventMan = new EventManager(_resMan->detectFontExtended());  #ifdef ENABLE_SCI32  	if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { @@ -331,7 +335,7 @@ Common::Error SciEngine::run() {  	_soundCmd = new SoundCommandParser(_resMan, segMan, _kernel, _audio, _features->detectDoSoundType());  	syncSoundSettings(); -	syncIngameAudioOptions(); +	_guestAdditions->syncAudioOptionsFromScummVM();  	// Patch in our save/restore code, so that dialogs are replaced  	patchGameSaveRestore(); @@ -834,14 +838,14 @@ void SciEngine::runGame() {  	if (DebugMan.isDebugChannelEnabled(kDebugLevelOnStartup))  		_console->attach(); -	_gamestate->_syncedAudioOptions = false; +	_guestAdditions->reset();  	do {  		_gamestate->_executionStackPosChanged = false;  		run_vm(_gamestate);  		exitGame(); -		_gamestate->_syncedAudioOptions = true; +		_guestAdditions->sciEngineRunGameHook();  		if (_gamestate->abortScriptProcessing == kAbortRestartGame) {  			_gamestate->_segMan->resetSegMan(); @@ -854,7 +858,7 @@ void SciEngine::runGame() {  			if (_gfxMenu)  				_gfxMenu->reset();  			_gamestate->abortScriptProcessing = kAbortNone; -			_gamestate->_syncedAudioOptions = false; +			_guestAdditions->reset();  		} else if (_gamestate->abortScriptProcessing == kAbortLoadGame) {  			_gamestate->abortScriptProcessing = kAbortNone;  			_gamestate->_executionStack.clear(); @@ -865,8 +869,7 @@ void SciEngine::runGame() {  			_gamestate->abortScriptProcessing = kAbortNone;  			syncSoundSettings(); -			syncIngameAudioOptions(); -			// Games do not set their audio settings when loading +			_guestAdditions->syncAudioOptionsFromScummVM();  		} else {  			break;	// exit loop  		} @@ -1055,150 +1058,7 @@ void SciEngine::pauseEngineIntern(bool pause) {  void SciEngine::syncSoundSettings() {  	Engine::syncSoundSettings(); - -	bool mute = false; -	if (ConfMan.hasKey("mute")) -		mute = ConfMan.getBool("mute"); - -	int soundVolumeMusic = (mute ? 0 : ConfMan.getInt("music_volume")); - -	if (_gamestate && _soundCmd) { -		int vol =  (soundVolumeMusic + 1) * MUSIC_MASTERVOLUME_MAX / Audio::Mixer::kMaxMixerVolume; -		_soundCmd->setMasterVolume(vol); -	} -} - -void SciEngine::syncIngameAudioOptions() { -	bool useGlobal90 = false; - -	// Sync the in-game speech/subtitles settings for SCI1.1 CD games -	if (isCD()) { -		switch (getSciVersion()) { -		case SCI_VERSION_1_1: -			// All SCI1.1 CD games use global 90 -			useGlobal90 = true; -			break; -#ifdef ENABLE_SCI32 -		case SCI_VERSION_2: -		case SCI_VERSION_2_1_EARLY: -		case SCI_VERSION_2_1_MIDDLE: -		case SCI_VERSION_2_1_LATE: -			// Only use global 90 for some specific games, not all SCI32 games used this method -			switch (_gameId) { -			case GID_KQ7: // SCI2.1 -			case GID_GK1: // SCI2 -			case GID_GK2: // SCI2.1 -			case GID_SQ6: // SCI2.1 -			case GID_TORIN: // SCI2.1 -			case GID_QFG4: // SCI2.1 -			case GID_PQ4:	// SCI2 -			case GID_PHANTASMAGORIA:	// SCI2.1 -			case GID_MOTHERGOOSEHIRES:	// SCI2.1 -				useGlobal90 = true; -				break; -			case GID_LSL6: // SCI2.1 -				// TODO: Uses gameFlags array -				break; -			// Shivers does not use global 90 -			// Police Quest: SWAT does not use global 90 -			// -			// TODO: Unknown at the moment: -			// LSL7, Lighthouse, RAMA, Phantasmagoria 2 -			default: -				return; -			} -			break; -#endif // ENABLE_SCI32 -		default: -			return; -		} - -		bool subtitlesOn = ConfMan.getBool("subtitles"); -		bool speechOn = !ConfMan.getBool("speech_mute"); - -#ifdef ENABLE_SCI32 -		if (getSciVersion() >= SCI_VERSION_2) { -			GlobalVar index; -			uint16 textSpeed; - -			switch (g_sci->getGameId()) { -			case GID_LSL6HIRES: -				index = kGlobalVarLSL6HiresTextSpeed; -				textSpeed = 14 - ConfMan.getInt("talkspeed") * 14 / 255 + 1; -				break; -			default: -				index = kGlobalVarTextSpeed; -				textSpeed = 8 - ConfMan.getInt("talkspeed") * 8 / 255; -			} - -			_gamestate->variables[VAR_GLOBAL][index] = make_reg(0, textSpeed); -		} -#endif - -		if (useGlobal90) { -			if (subtitlesOn && !speechOn) { -				_gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 1);	// subtitles -			} else if (!subtitlesOn && speechOn) { -				_gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2);	// speech -			} else if (subtitlesOn && speechOn) { -				// Is it a game that supports simultaneous speech and subtitles? -				switch (_gameId) { -				case GID_SQ4: -				case GID_FREDDYPHARKAS: -				case GID_ECOQUEST: -				case GID_LSL6: -				case GID_LAURABOW2: -				case GID_KQ6: -#ifdef ENABLE_SCI32 -				// Unsure about Gabriel Knight 2 -				case GID_KQ7: // SCI2.1 -				case GID_GK1: // SCI2 -				case GID_SQ6: // SCI2.1, SQ6 seems to always use subtitles anyway -				case GID_TORIN: // SCI2.1 -				case GID_QFG4: // SCI2.1 -				case GID_PQ4:	// SCI2 -				// Phantasmagoria does not support simultaneous speech + subtitles -				// Mixed Up Mother Goose Deluxe does not support simultaneous speech + subtitles -#endif // ENABLE_SCI32 -					_gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 3);	// speech + subtitles -					break; -				default: -					// Game does not support speech and subtitles, set it to speech -					_gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType] = make_reg(0, 2);	// speech -				} -			} -		} -	} -} - -void SciEngine::updateScummVMAudioOptions() { -	// Update ScummVM's speech/subtitles settings for SCI1.1 CD games, -	// depending on the in-game settings -	if ((isCD() && getSciVersion() == SCI_VERSION_1_1) || -		getSciVersion() >= SCI_VERSION_2) { - -		uint16 ingameSetting = _gamestate->variables[VAR_GLOBAL][kGlobalVarMessageType].getOffset(); - -		switch (ingameSetting) { -		case 1: -			// subtitles -			ConfMan.setBool("subtitles", true); -			ConfMan.setBool("speech_mute", true); -			break; -		case 2: -			// speech -			ConfMan.setBool("subtitles", false); -			ConfMan.setBool("speech_mute", false); -			break; -		case 3: -			// speech + subtitles -			ConfMan.setBool("subtitles", true); -			ConfMan.setBool("speech_mute", false); -			break; -		default: -			break; -		} -	} +	_guestAdditions->syncSoundSettings();  }  void SciEngine::loadMacExecutable() { diff --git a/engines/sci/sci.h b/engines/sci/sci.h index f5ccc19d7c..744a1b4aed 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -62,6 +62,7 @@ class Vocabulary;  class ResourceManager;  class Kernel;  class GameFeatures; +class GuestAdditions;  class Console;  class AudioPlayer;  class SoundCommandParser; @@ -263,28 +264,6 @@ public:  	uint32 getTickCount();  	void setTickCount(const uint32 ticks); -	/** -	 * Syncs the audio options of the ScummVM launcher (speech, subtitles or -	 * both) with the in-game audio options of certain CD game versions. For -	 * some games, this allows simultaneous playing of speech and subtitles, -	 * even if the original games didn't support this feature. -	 * -	 * SCI1.1 games which support simultaneous speech and subtitles: -	 * - EcoQuest 1 CD -	 * - Leisure Suit Larry 6 CD -	 * SCI1.1 games which don't support simultaneous speech and subtitles, -	 * and we add this functionality in ScummVM: -	 * - Space Quest 4 CD -	 * - Freddy Pharkas CD -	 * - Laura Bow 2 CD -	 * SCI1.1 games which don't support simultaneous speech and subtitles, -	 * and we haven't added any extra functionality in ScummVM because extra -	 * script patches are needed: -	 * - King's Quest 6 CD -	 */ -	void syncIngameAudioOptions(); -	void updateScummVMAudioOptions(); -  	const SciGameId &getGameId() const { return _gameId; }  	const char *getGameIdStr() const;  	int getResourceVersion() const; @@ -398,6 +377,7 @@ public:  	Sync *_sync;  	SoundCommandParser *_soundCmd;  	GameFeatures *_features; +	GuestAdditions *_guestAdditions;  	opcode_format (*_opcode_formats)[4]; diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp index 69a83ebc8e..e9e90e41a7 100644 --- a/engines/sci/sound/audio32.cpp +++ b/engines/sci/sound/audio32.cpp @@ -36,6 +36,8 @@  #include "common/types.h"           // for Flag::NO  #include "engine.h"                 // for Engine, g_engine  #include "sci/engine/features.h"    // for GameFeatures +#include "sci/engine/guest_additions.h" // for GuestAdditions +#include "sci/engine/state.h"       // for EngineState  #include "sci/engine/vm_types.h"    // for reg_t, make_reg, NULL_REG  #include "sci/resource.h"           // for ResourceId, ResourceType::kResour...  #include "sci/sci.h"                // for SciEngine, g_sci, getSciVersion @@ -118,6 +120,9 @@ Audio32::Audio32(ResourceManager *resMan) :  	}  	_useModifiedAttenuation = g_sci->_features->usesModifiedAudioAttenuation(); +	// The mixer stream type is given as `kSFXSoundType` so that audio from +	// Audio32 will be mixed at the same standard volume as the video players +	// (which must use `kSFXSoundType` as well).  	_mixer->playStream(Audio::Mixer::kSFXSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);  } @@ -980,7 +985,7 @@ reg_t Audio32::kernelPlay(const bool autoPlay, const int argc, const reg_t *cons  int16 Audio32::getVolume(const int16 channelIndex) const {  	if (channelIndex < 0 || channelIndex >= _numActiveChannels) { -		return _mixer->getChannelVolume(_handle) * kMaxVolume / Audio::Mixer::kMaxChannelVolume; +		return (_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) + 1) * kMaxVolume / Audio::Mixer::kMaxMixerVolume;  	}  	Common::StackLock lock(_mutex); @@ -990,10 +995,9 @@ int16 Audio32::getVolume(const int16 channelIndex) const {  void Audio32::setVolume(const int16 channelIndex, int16 volume) {  	volume = MIN<int16>(kMaxVolume, volume);  	if (channelIndex == kAllChannels) { -		ConfMan.setInt("sfx_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); -		ConfMan.setInt("speech_volume", volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); -		_mixer->setChannelVolume(_handle, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); -		g_engine->syncSoundSettings(); +		if (!g_sci->_guestAdditions->audio32SetVolumeHook(channelIndex, volume)) { +			setMasterVolume(volume); +		}  	} else if (channelIndex != kNoExistingChannel) {  		Common::StackLock lock(_mutex);  		getChannel(channelIndex).volume = volume; diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h index 9130cbe687..8a1d8cfb43 100644 --- a/engines/sci/sound/audio32.h +++ b/engines/sci/sound/audio32.h @@ -504,6 +504,13 @@ public:  	}  	/** +	 * Sets the master volume for digital audio playback. +	 */ +	void setMasterVolume(const int16 volume) { +		_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume * Audio::Mixer::kMaxChannelVolume / kMaxVolume); +	} + +	/**  	 * Initiate an immediate fade of the given channel.  	 */  	bool fadeChannel(const int16 channelIndex, const int16 targetVolume, const int16 speed, const int16 steps, const bool stopAfterFade); diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp index b9a764c93a..e0cbb97d19 100644 --- a/engines/sci/sound/soundcmd.cpp +++ b/engines/sci/sound/soundcmd.cpp @@ -29,6 +29,7 @@  #include "sci/sound/soundcmd.h"  #include "sci/engine/features.h" +#include "sci/engine/guest_additions.h"  #include "sci/engine/kernel.h"  #include "sci/engine/object.h"  #include "sci/engine/selector.h" @@ -57,10 +58,10 @@ SoundCommandParser::~SoundCommandParser() {  	delete _music;  } -reg_t SoundCommandParser::kDoSoundInit(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundInit(EngineState *s, int argc, reg_t *argv) {  	debugC(kDebugLevelSound, "kDoSound(init): %04x:%04x", PRINT_REG(argv[0]));  	processInitSound(argv[0]); -	return acc; +	return s->r_acc;  }  int SoundCommandParser::getSoundResourceId(reg_t obj) { @@ -153,13 +154,13 @@ void SoundCommandParser::processInitSound(reg_t obj) {  	}  } -reg_t SoundCommandParser::kDoSoundPlay(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundPlay(EngineState *s, int argc, reg_t *argv) {  	debugC(kDebugLevelSound, "kDoSound(play): %04x:%04x", PRINT_REG(argv[0]));  	bool playBed = false;  	if (argc >= 2 && !argv[1].isNull())  		playBed = true;  	processPlaySound(argv[0], playBed); -	return acc; +	return s->r_acc;  }  void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) { @@ -225,10 +226,10 @@ void SoundCommandParser::processPlaySound(reg_t obj, bool playBed) {  	musicSlot->fadeStep = 0;  } -reg_t SoundCommandParser::kDoSoundDispose(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundDispose(EngineState *s, int argc, reg_t *argv) {  	debugC(kDebugLevelSound, "kDoSound(dispose): %04x:%04x", PRINT_REG(argv[0]));  	processDisposeSound(argv[0]); -	return acc; +	return s->r_acc;  }  void SoundCommandParser::processDisposeSound(reg_t obj) { @@ -248,10 +249,10 @@ void SoundCommandParser::processDisposeSound(reg_t obj) {  		writeSelectorValue(_segMan, obj, SELECTOR(state), kSoundStopped);  } -reg_t SoundCommandParser::kDoSoundStop(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundStop(EngineState *s, int argc, reg_t *argv) {  	debugC(kDebugLevelSound, "kDoSound(stop): %04x:%04x", PRINT_REG(argv[0]));  	processStopSound(argv[0], false); -	return acc; +	return s->r_acc;  }  void SoundCommandParser::processStopSound(reg_t obj, bool sampleFinishedPlaying) { @@ -282,7 +283,7 @@ void SoundCommandParser::processStopSound(reg_t obj, bool sampleFinishedPlaying)  	_music->soundStop(musicSlot);  } -reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundPause(EngineState *s, int argc, reg_t *argv) {  	if (argc == 1)  		debugC(kDebugLevelSound, "kDoSound(pause): %04x:%04x", PRINT_REG(argv[0]));  	else @@ -333,7 +334,7 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {  		if (!musicSlot) {  			// This happens quite frequently  			debugC(kDebugLevelSound, "kDoSound(pause): Slot not found (%04x:%04x)", PRINT_REG(obj)); -			return acc; +			return s->r_acc;  		}  #ifdef ENABLE_SCI32 @@ -354,17 +355,17 @@ reg_t SoundCommandParser::kDoSoundPause(int argc, reg_t *argv, reg_t acc) {  #endif  			_music->soundToggle(musicSlot, shouldPause);  	} -	return acc; +	return s->r_acc;  }  // SCI0 only command  //  It's called right after restoring a game - it's responsible to kick off playing music again  //  we don't need this at all, so we don't do anything here -reg_t SoundCommandParser::kDoSoundResumeAfterRestore(int argc, reg_t *argv, reg_t acc) { -	return acc; +reg_t SoundCommandParser::kDoSoundResumeAfterRestore(EngineState *s, int argc, reg_t *argv) { +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundMute(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundMute(EngineState *s, int argc, reg_t *argv) {  	uint16 previousState = _music->soundGetSoundOn();  	if (argc > 0) {  		debugC(kDebugLevelSound, "kDoSound(mute): %d", argv[0].toUint16()); @@ -374,37 +375,33 @@ reg_t SoundCommandParser::kDoSoundMute(int argc, reg_t *argv, reg_t acc) {  	return make_reg(0, previousState);  } -reg_t SoundCommandParser::kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc) { -	acc = make_reg(0, _music->soundGetMasterVolume()); +reg_t SoundCommandParser::kDoSoundMasterVolume(EngineState *s, int argc, reg_t *argv) { +	s->r_acc = make_reg(0, _music->soundGetMasterVolume());  	if (argc > 0) {  		debugC(kDebugLevelSound, "kDoSound(masterVolume): %d", argv[0].toSint16());  		int vol = CLIP<int16>(argv[0].toSint16(), 0, MUSIC_MASTERVOLUME_MAX); -		vol = vol * Audio::Mixer::kMaxMixerVolume / MUSIC_MASTERVOLUME_MAX; -		ConfMan.setInt("music_volume", vol); -		// In SCI32, digital audio volume is controlled separately by -		// kDoAudioVolume -		if (_soundVersion < SCI_VERSION_2_1_EARLY) { -			ConfMan.setInt("sfx_volume", vol); + +		if (!g_sci->_guestAdditions->kDoSoundMasterVolumeHook(vol)) { +			setMasterVolume(vol);  		} -		g_engine->syncSoundSettings();  	} -	return acc; +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundFade(EngineState *s, int argc, reg_t *argv) {  	reg_t obj = argv[0];  	// The object can be null in several SCI0 games (e.g. Camelot, KQ1, KQ4, MUMG).  	// Check bugs #3035149, #3036942 and #3578335.  	// In this case, we just ignore the call.  	if (obj.isNull() && argc == 1) -		return acc; +		return s->r_acc;  	MusicEntry *musicSlot = _music->getSlot(obj);  	if (!musicSlot) {  		debugC(kDebugLevelSound, "kDoSound(fade): Slot not found (%04x:%04x)", PRINT_REG(obj)); -		return acc; +		return s->r_acc;  	}  	int volume = musicSlot->volume; @@ -412,7 +409,7 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {  #ifdef ENABLE_SCI32  	if (_soundVersion >= SCI_VERSION_2_1_EARLY && musicSlot->isSample) {  		g_sci->_audio32->fadeChannel(ResourceId(kResourceTypeAudio, musicSlot->resourceId), musicSlot->soundObj, argv[1].toSint16(), argv[2].toSint16(), argv[3].toSint16(), argc > 4 ? (bool)argv[4].toSint16() : false); -		return acc; +		return s->r_acc;  	}  #endif @@ -420,7 +417,7 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {  	if (musicSlot->status != kSoundPlaying) {  		debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x fading requested, but sound is currently not playing", PRINT_REG(obj));  		writeSelectorValue(_segMan, obj, SELECTOR(signal), SIGNAL_OFFSET); -		return acc; +		return s->r_acc;  	}  	switch (argc) { @@ -439,7 +436,7 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {  		// Check if the song is already at the requested volume. If it is, don't  		// perform any fading. Happens for example during the intro of Longbow.  		if (musicSlot->fadeTo == musicSlot->volume) -			return acc; +			return s->r_acc;  		// Sometimes we get objects in that position, so fix the value (refer to workarounds.cpp)  		if (!argv[1].getSegment()) @@ -464,14 +461,14 @@ reg_t SoundCommandParser::kDoSoundFade(int argc, reg_t *argv, reg_t acc) {  	}  	debugC(kDebugLevelSound, "kDoSound(fade): %04x:%04x to %d, step %d, ticker %d", PRINT_REG(obj), musicSlot->fadeTo, musicSlot->fadeStep, musicSlot->fadeTickerStep); -	return acc; +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundGetPolyphony(EngineState *s, int argc, reg_t *argv) {  	return make_reg(0, _music->soundGetVoices());	// Get the number of voices  } -reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundUpdate(EngineState *s, int argc, reg_t *argv) {  	reg_t obj = argv[0];  	debugC(kDebugLevelSound, "kDoSound(update): %04x:%04x", PRINT_REG(argv[0])); @@ -479,7 +476,7 @@ reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) {  	MusicEntry *musicSlot = _music->getSlot(obj);  	if (!musicSlot) {  		warning("kDoSound(update): Slot not found (%04x:%04x)", PRINT_REG(obj)); -		return acc; +		return s->r_acc;  	}  	musicSlot->loop = readSelectorValue(_segMan, obj, SELECTOR(loop)); @@ -489,12 +486,12 @@ reg_t SoundCommandParser::kDoSoundUpdate(int argc, reg_t *argv, reg_t acc) {  	int16 objPrio = readSelectorValue(_segMan, obj, SELECTOR(priority));  	if (objPrio != musicSlot->priority)  		_music->soundSetPriority(musicSlot, objPrio); -	return acc; +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundUpdateCues(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv) {  	processUpdateCues(argv[0]); -	return acc; +	return s->r_acc;  }  void SoundCommandParser::processUpdateCues(reg_t obj) { @@ -608,7 +605,7 @@ void SoundCommandParser::processUpdateCues(reg_t obj) {  	}  } -reg_t SoundCommandParser::kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv) {  	// The 4 parameter variant of this call is used in at least LSL1VGA, room  	// 110 (Lefty's bar), to distort the music when Larry is drunk and stands  	// up - bug #3614447. @@ -637,13 +634,13 @@ reg_t SoundCommandParser::kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc) {  		// if so, allow it  		//_music->sendMidiCommand(_midiCommand);  		warning("kDoSound(sendMidi): Slot not found (%04x:%04x)", PRINT_REG(obj)); -		return acc; +		return s->r_acc;  	}  	_music->sendMidiCommand(musicSlot, midiCommand); -	return acc; +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundGlobalReverb(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv) {  	byte prevReverb = _music->getCurrentReverb();  	byte reverb = argv[0].toUint16() & 0xF; @@ -656,7 +653,7 @@ reg_t SoundCommandParser::kDoSoundGlobalReverb(int argc, reg_t *argv, reg_t acc)  	return make_reg(0, prevReverb);  } -reg_t SoundCommandParser::kDoSoundSetHold(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSetHold(EngineState *s, int argc, reg_t *argv) {  	reg_t obj = argv[0];  	debugC(kDebugLevelSound, "doSoundSetHold: %04x:%04x, %d", PRINT_REG(argv[0]), argv[1].toUint16()); @@ -664,24 +661,24 @@ reg_t SoundCommandParser::kDoSoundSetHold(int argc, reg_t *argv, reg_t acc) {  	MusicEntry *musicSlot = _music->getSlot(obj);  	if (!musicSlot) {  		warning("kDoSound(setHold): Slot not found (%04x:%04x)", PRINT_REG(obj)); -		return acc; +		return s->r_acc;  	}  	// Set the special hold marker ID where the song should be looped at.  	musicSlot->hold = argv[1].toSint16(); -	return acc; +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundGetAudioCapability(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv) {  	// Tests for digital audio support  	return make_reg(0, 1);  } -reg_t SoundCommandParser::kDoSoundStopAll(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundStopAll(EngineState *s, int argc, reg_t *argv) {  	// TODO: this can't be right, this gets called in kq1 - e.g. being in witch house, getting the note  	//  now the point jingle plays and after a messagebox they call this - and would stop the background effects with it  	//  this doesn't make sense, so i disable it for now -	return acc; +	return s->r_acc;  	Common::StackLock(_music->_mutex); @@ -697,10 +694,10 @@ reg_t SoundCommandParser::kDoSoundStopAll(int argc, reg_t *argv, reg_t acc) {  		(*i)->dataInc = 0;  		_music->soundStop(*i);  	} -	return acc; +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv) {  	reg_t obj = argv[0];  	int16 value = argv[1].toSint16(); @@ -711,7 +708,7 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {  		// the drum sounds of the energizer bunny at the beginning), so this is  		// normal behavior.  		//warning("cmdSetSoundVolume: Slot not found (%04x:%04x)", PRINT_REG(obj)); -		return acc; +		return s->r_acc;  	}  	debugC(kDebugLevelSound, "kDoSound(setVolume): %d", value); @@ -724,15 +721,20 @@ reg_t SoundCommandParser::kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc) {  		_music->soundSetVolume(musicSlot, value);  	}  #endif +  	if (musicSlot->volume != value) {  		musicSlot->volume = value;  		_music->soundSetVolume(musicSlot, value);  		writeSelectorValue(_segMan, obj, SELECTOR(vol), value); +#ifdef ENABLE_SCI32 +		g_sci->_guestAdditions->kDoSoundSetVolumeHook(obj, value); +#endif  	} -	return acc; + +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSetPriority(EngineState *s, int argc, reg_t *argv) {  	reg_t obj = argv[0];  	int16 value = argv[1].toSint16(); @@ -741,7 +743,7 @@ reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc)  	MusicEntry *musicSlot = _music->getSlot(obj);  	if (!musicSlot) {  		debugC(kDebugLevelSound, "kDoSound(setPriority): Slot not found (%04x:%04x)", PRINT_REG(obj)); -		return acc; +		return s->r_acc;  	}  	if (value == -1) { @@ -759,10 +761,10 @@ reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc)  		_music->soundSetPriority(musicSlot, value);  	} -	return acc; +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSetLoop(EngineState *s, int argc, reg_t *argv) {  	reg_t obj = argv[0];  	int16 value = argv[1].toSint16(); @@ -780,7 +782,7 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {  		} else {  			// Doesn't really matter  		} -		return acc; +		return s->r_acc;  	}  #ifdef ENABLE_SCI32 @@ -805,13 +807,13 @@ reg_t SoundCommandParser::kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc) {  	}  #endif -	return acc; +	return s->r_acc;  } -reg_t SoundCommandParser::kDoSoundSuspend(int argc, reg_t *argv, reg_t acc) { +reg_t SoundCommandParser::kDoSoundSuspend(EngineState *s, int argc, reg_t *argv) {  	// TODO  	warning("kDoSound(suspend): STUB"); -	return acc; +	return s->r_acc;  }  void SoundCommandParser::updateSci0Cues() { @@ -879,6 +881,17 @@ void SoundCommandParser::setMasterVolume(int vol) {  	_music->soundSetMasterVolume(vol);  } +#ifdef ENABLE_SCI32 +void SoundCommandParser::setVolume(const reg_t obj, const int volume) { +	MusicEntry *slot = _music->getSlot(obj); +	if (slot != nullptr) { +		slot->volume = volume; +		writeSelectorValue(_segMan, obj, SELECTOR(vol), volume); +		_music->soundSetVolume(slot, volume); +	} +} +#endif +  void SoundCommandParser::pauseAll(bool pause) {  	_music->pauseAll(pause);  } diff --git a/engines/sci/sound/soundcmd.h b/engines/sci/sound/soundcmd.h index 5bb7cf2cb1..928c9b1acb 100644 --- a/engines/sci/sound/soundcmd.h +++ b/engines/sci/sound/soundcmd.h @@ -46,7 +46,7 @@ public:  	SoundCommandParser(ResourceManager *resMan, SegManager *segMan, Kernel *kernel, AudioPlayer *audio, SciVersion soundVersion);  	~SoundCommandParser(); -	//reg_t parseCommand(int argc, reg_t *argv, reg_t acc); +	//reg_t parseCommand(EngineState *s, int argc, reg_t *argv);  	// Functions used for game state loading  	void clearPlayList(); @@ -56,6 +56,9 @@ public:  	// Functions used for the ScummVM menus  	void setMasterVolume(int vol);  	void pauseAll(bool pause); +#ifdef ENABLE_SCI32 +	void setVolume(const reg_t obj, const int vol); +#endif  	// Debug console functions  	void startNewSound(int number); @@ -78,29 +81,29 @@ public:  	 */  	void updateSci0Cues(); -	reg_t kDoSoundInit(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundPlay(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundRestore(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundMute(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundPause(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundResumeAfterRestore(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundStop(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundStopAll(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundDispose(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundMasterVolume(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundFade(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundGetPolyphony(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundUpdate(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundUpdateCues(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundSendMidi(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundGlobalReverb(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundSetHold(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundDummy(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundGetAudioCapability(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundSetVolume(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundSetLoop(int argc, reg_t *argv, reg_t acc); -	reg_t kDoSoundSuspend(int argc, reg_t *argv, reg_t acc); +	reg_t kDoSoundInit(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundPlay(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundRestore(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundMute(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundPause(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundResumeAfterRestore(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundStop(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundStopAll(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundDispose(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundMasterVolume(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundFade(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundGetPolyphony(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundUpdate(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundUpdateCues(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundSendMidi(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundGlobalReverb(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundSetHold(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundDummy(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundGetAudioCapability(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundSetVolume(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundSetPriority(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundSetLoop(EngineState *s, int argc, reg_t *argv); +	reg_t kDoSoundSuspend(EngineState *s, int argc, reg_t *argv);  private:  	//typedef Common::Array<MusicEntryCommand *> SoundCommandContainer; | 
