SCI: Improve audio volume & settings sync code

This patch includes enhancements to the ScummVM integration with
SCI engine, with particular focus on SCI32 support.

1. Fixes audio volumes syncing erroneously to ScummVM in games
   that modify the audio volume without user action (e.g. SCI1.1
   talkies that reduce music volume during speech playback). Now,
   volumes will only be synchronised when the user interacts with
   the game's audio settings. This mechanism works by looking for
   a known volume control object in the stack, and only syncing
   when the control object is present. (Ports and planes were
   researched and found unreliable.)

2. Fixes audio syncing in SCI32 games that do not set game
   volumes through kDoSoundMasterVolume/kDoAudioVolume, like GK1,
   GK2, Phant1, and Torin.

3. Fixes speech/subtitles syncing in SCI32 games that do not use
   global 90, like LSL6hires.

4. Fixes in-game volume controls in SCI32 games reflecting
   outdated audio volumes when a change is made during the game
   from the ScummVM launcher.

5. Fixes SCI32 games that would restore volumes from save games
   or reset volumes on startup, which caused game volumes to be
   out-of-sync with ScummVM when started.

6. ScummVM integration code for audio sync has been abstracted
   into a new GuestAdditions class. This keeps the ScummVM-
   specific code all in one place, with only small hooks into the
   engine code. ScummVM integrated save/load code should probably
   also go here in the future.

Fixes Trac#9700.
This commit is contained in:
Colin Snover 2017-01-26 13:18:41 -06:00
parent c2e9fee932
commit 3303a88139
21 changed files with 1677 additions and 327 deletions

View file

@ -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)

View file

@ -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));

View file

@ -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);
@ -147,6 +157,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,

View file

@ -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

View file

@ -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

View file

@ -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());

View file

@ -179,6 +179,10 @@ public:
_infoSelectorSci3 &= ~flag;
}
}
bool isInserted() const {
return getInfoSelector().toUint16() & kInfoFlagViewInserted;
}
#endif
reg_t getNameSelector() const {

View file

@ -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;

View file

@ -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
}

View file

@ -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
};

View file

@ -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) {

View file

@ -214,7 +214,6 @@ public:
// TODO: Excise video code from the state manager
VideoState _videoState;
bool _syncedAudioOptions;
/**
* Resets the engine state.

View file

@ -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));

View file

@ -142,15 +142,31 @@ 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
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
};

View file

@ -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 \

View file

@ -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() {

View file

@ -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];

View file

@ -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;

View file

@ -503,6 +503,13 @@ public:
setVolume(findChannelById(resourceId, soundNode), volume);
}
/**
* 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.
*/

View file

@ -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);
}
return acc;
#ifdef ENABLE_SCI32
g_sci->_guestAdditions->kDoSoundSetVolumeHook(obj, value);
#endif
}
reg_t SoundCommandParser::kDoSoundSetPriority(int argc, reg_t *argv, reg_t acc) {
return s->r_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);
}

View file

@ -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;