diff options
31 files changed, 400 insertions, 301 deletions
diff --git a/common/memstream.h b/common/memstream.h index 25fdde91c7..0338d35378 100644 --- a/common/memstream.h +++ b/common/memstream.h @@ -212,13 +212,14 @@ public: /** * MemoryStream based on RingBuffer. Grows if has insufficient buffer size. */ -class MemoryReadWriteStream : public WriteStream { +class MemoryReadWriteStream : public SeekableReadStream, public WriteStream { private: uint32 _capacity; uint32 _size; byte *_data; uint32 _writePos, _readPos, _pos, _length; DisposeAfterUse::Flag _disposeMemory; + bool _eos; void ensureCapacity(uint32 new_len) { if (new_len <= _capacity) @@ -246,7 +247,7 @@ private: } } public: - MemoryReadWriteStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : _capacity(0), _size(0), _data(0), _writePos(0), _readPos(0), _pos(0), _length(0), _disposeMemory(disposeMemory) {} + MemoryReadWriteStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : _capacity(0), _size(0), _data(0), _writePos(0), _readPos(0), _pos(0), _length(0), _disposeMemory(disposeMemory), _eos(false) {} ~MemoryReadWriteStream() { if (_disposeMemory) @@ -271,8 +272,10 @@ public: } virtual uint32 read(void *dataPtr, uint32 dataSize) { - uint32 length = _length; - if (length < dataSize) dataSize = length; + if (_length < dataSize) { + dataSize = _length; + _eos = true; + } if (dataSize == 0 || _capacity == 0) return 0; if (_readPos + dataSize < _capacity) { memcpy(dataPtr, _data + _readPos, dataSize); @@ -287,7 +290,10 @@ public: } int32 pos() const { return _pos - _length; } //'read' position in the stream - uint32 size() const { return _size; } //that's also 'write' position in the stream, as it's append-only + int32 size() const { return _size; } //that's also 'write' position in the stream, as it's append-only + bool seek(int32, int) { return false; } + bool eos() const { return _eos; } + void clearErr() { _eos = false; } byte *getData() { return _data; } }; @@ -164,6 +164,7 @@ _build_scalers=yes _build_hq_scalers=yes _enable_prof=no _global_constructors=no +_no_undefined_var_template=no _bink=yes _cloud=auto # Default vkeybd/keymapper/eventrec options @@ -2023,13 +2024,30 @@ echocheck "whether -Wglobal-constructors work" cat > $TMPC << EOF int main() { return 0; } EOF -cc_check -Wglobal-constructors && _global_constructors=yes +cc_check -Wglobal-constructors -Werror && _global_constructors=yes if test "$_global_constructors" = yes; then append_var CXXFLAGS "-Wglobal-constructors" fi echo $_global_constructors +# If the compiler supports the -Wundefined-var-template flag, silence that warning. +# We get this warning a lot with regard to the Singleton class as we explicitly +# instantiate each specialisation. An alternate way to deal with it would be to +# change the way we instantiate the singleton classes as done in PR #967. +# Note: we check the -Wundefined-var-template as gcc does not error out on unknown +# -Wno-xxx flags. +echocheck "whether -Wno-undefined-var-template work" +cat > $TMPC << EOF +int main() { return 0; } +EOF +cc_check -Wundefined-var-template -Werror && _no_undefined_var_template=yes + +if test "$_no_undefined_var_template" = yes; then + append_var CXXFLAGS "-Wno-undefined-var-template" +fi +echo $_no_undefined_var_template + echo_n "Checking for $_host_alias-strings... " >> "$TMPLOG" if `which $_host_alias-strings >/dev/null 2>&1`; then _strings=$_host_alias-strings diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp index c7f8d1c3d9..ea3e2ce7ec 100644 --- a/engines/mohawk/riven.cpp +++ b/engines/mohawk/riven.cpp @@ -510,7 +510,10 @@ void MohawkEngine_Riven::delay(uint32 ms) { void MohawkEngine_Riven::runLoadDialog() { GUI::SaveLoadChooser slc(_("Load game:"), _("Load"), false); + pauseEngine(true); int slot = slc.runModalWithCurrentTarget(); + pauseEngine(false); + if (slot >= 0) { loadGameStateAndDisplayError(slot); } diff --git a/engines/saga/actor.cpp b/engines/saga/actor.cpp index d8b115f9bd..8c45a2890e 100644 --- a/engines/saga/actor.cpp +++ b/engines/saga/actor.cpp @@ -1174,21 +1174,6 @@ void Actor::actorSpeech(uint16 actorId, const char **strings, int stringsCount, _activeSpeech.speechBox.right = _vm->getDisplayInfo().width - 10; } - // HACK for the compact disk in Ellen's chapter - // Once Ellen starts saying that "Something is different", bring the compact disk in the - // scene. After speaking with AM, the compact disk is visible. She always says this line - // when entering room 59, after speaking with AM, if the compact disk is not picked up yet - // Check Script::sfDropObject for the other part of this hack - if (_vm->getGameId() == GID_IHNM && _vm->_scene->currentChapterNumber() == 3 && - _vm->_scene->currentSceneNumber() == 59 && _activeSpeech.sampleResourceId == 286) { - for (ObjectDataArray::iterator obj = _objs.begin(); obj != _objs.end(); ++obj) { - if (obj->_id == 16385) { // the compact disk - obj->_sceneNumber = 59; - break; - } - } - } - } void Actor::nonActorSpeech(const Common::Rect &box, const char **strings, int stringsCount, int sampleResourceId, int speechFlags) { diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp index 9dcc8d9137..e6b196c4cd 100644 --- a/engines/saga/interface.cpp +++ b/engines/saga/interface.cpp @@ -2533,10 +2533,12 @@ void Interface::converseDisplayTextLines() { char bullet[2] = { (char)0xb7, 0 }; - Rect rect(8, _vm->getDisplayInfo().converseTextLines * _vm->getDisplayInfo().converseTextHeight); - Point textPoint; assert(_conversePanel.buttonsCount >= 6); + Rect rect(8, _vm->getDisplayInfo().converseTextLines * _vm->getDisplayInfo().converseTextHeight); + rect.moveTo(_conversePanel.x + _conversePanel.buttons[0].xOffset, _conversePanel.y + _conversePanel.buttons[0].yOffset); + + Point textPoint; if (_vm->getGameId() == GID_ITE) { bulletForegnd = kITEColorGreen; @@ -2547,13 +2549,11 @@ void Interface::converseDisplayTextLines() { bullet[0] = '>'; // different bullet in IHNM } - rect.moveTo(_conversePanel.x + _conversePanel.buttons[0].xOffset, - _conversePanel.y + _conversePanel.buttons[0].yOffset); - if (_vm->getGameId() == GID_ITE) - _vm->_gfx->drawRect(rect, kITEColorDarkGrey); //fill bullet place - else - _vm->_gfx->drawRect(rect, _vm->KnownColor2ColorId(kKnownColorBlack)); //fill bullet place + _vm->_gfx->drawRect(rect, kITEColorDarkGrey); // fill bullet place + else if (_vm->getGameId() == GID_IHNM) + // TODO: Add these to IHNM_DisplayInfo? + _vm->_gfx->drawRect(Common::Rect(118, 345, 603, 463), _vm->KnownColor2ColorId(kKnownColorBlack)); // fill converse rect for (int i = 0; i < _vm->getDisplayInfo().converseTextLines; i++) { relPos = _converseStartPos + i; diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp index 3cc6586432..7a84944b17 100644 --- a/engines/saga/script.cpp +++ b/engines/saga/script.cpp @@ -1709,15 +1709,16 @@ void Script::whichObject(const Point& mousePoint) { if (_vm->getGameId() == GID_IHNM && objectId == 8199) newRightButtonVerb = getVerbType(kVerbLookAt); - if ((_currentVerb == getVerbType(kVerbPickUp)) || - (_currentVerb == getVerbType(kVerbOpen)) || - (_currentVerb == getVerbType(kVerbClose)) || - ((_currentVerb == getVerbType(kVerbGive)) && !_firstObjectSet) || - ((_currentVerb == getVerbType(kVerbUse)) && !(actor->_flags & kFollower))) { - if (_vm->getGameId() == GID_ITE) { - objectId = ID_NOTHING; - newObjectId = ID_NOTHING; - } + bool actorIsFollower = (actor->_flags & kFollower); + bool actorCanBeUsed = (actor->_flags & kUsable); + + if ( _currentVerb == getVerbType(kVerbPickUp) || + _currentVerb == getVerbType(kVerbOpen) || + _currentVerb == getVerbType(kVerbClose) || + (_currentVerb == getVerbType(kVerbGive) && !_firstObjectSet) || + (_currentVerb == getVerbType(kVerbUse) && !_firstObjectSet && !(actorIsFollower || actorCanBeUsed))) { + objectId = ID_NOTHING; + newObjectId = ID_NOTHING; } } } diff --git a/engines/saga/sfuncs.cpp b/engines/saga/sfuncs.cpp index 2175d8f40a..6456daeb02 100644 --- a/engines/saga/sfuncs.cpp +++ b/engines/saga/sfuncs.cpp @@ -704,14 +704,6 @@ void Script::sfDropObject(SCRIPTFUNC_PARAMS) { obj->_sceneNumber = _vm->_scene->currentSceneNumber(); - // HACK for the compact disk in Ellen's chapter - // Change the scene number of the compact disk so that it's not shown. It will be shown - // once Ellen says that there's something different (i.e. after speaking with AM) - // See Actor::actorSpeech for the other part of this hack - if (_vm->getGameId() == GID_IHNM && _vm->_scene->currentChapterNumber() == 3 && - _vm->_scene->currentSceneNumber() == 59 && obj->_id == 16385) - obj->_sceneNumber = -1; - if (_vm->getGameId() == GID_IHNM) { // Don't update _spriteListResourceId if spriteId is 0 and the object is not the // psychic profile. If spriteId == 0, the object's sprite is incorrectly reset. diff --git a/engines/sludge/sound.cpp b/engines/sludge/sound.cpp index 79b9ab79d8..6820ae14be 100644 --- a/engines/sludge/sound.cpp +++ b/engines/sludge/sound.cpp @@ -52,10 +52,9 @@ SoundManager::SoundManager() { _soundCache = nullptr; _soundCache = new SoundThing[MAX_SAMPLES]; - #if 0 + _modCache = nullptr; _modCache = new SoundThing[MAX_MODS]; - #endif _defVol = 128; _defSoundVol = 255; @@ -70,10 +69,8 @@ SoundManager::~SoundManager() { delete []_soundCache; _soundCache = nullptr; - #if 0 delete []_modCache; _modCache = nullptr; - #endif } bool SoundManager::initSoundStuff() { @@ -82,12 +79,13 @@ bool SoundManager::initSoundStuff() { _soundCache[a].looping = false; _soundCache[a].inSoundList = false; } -#if 0 + for (int a = 0; a < MAX_MODS; a ++) { - _modCache[a].stream = NULL; - _modCache[a].playing = false; + _soundCache[a].fileLoaded = -1; + _soundCache[a].looping = false; + _soundCache[a].inSoundList = false; } -#endif + return _soundOK = true; } @@ -95,48 +93,24 @@ void SoundManager::killSoundStuff() { if (!_soundOK) return; - _silenceIKillYou = true; - for (int i = 0; i < MAX_SAMPLES; i ++) { - if (g_sludge->_mixer->isSoundHandleActive(_soundCache[i].handle)) { - g_sludge->_mixer->stopHandle(_soundCache[i].handle); - } - } -#if 0 - for (int i = 0; i < MAX_MODS; i ++) { - if (_modCache[i].playing) { - - if (! alureStopSource(modCache[i].playingOnSource, AL_TRUE)) { - debugOut("Failed to stop source: %s\n", - alureGetErrorString()); - } - - } - - if (_modCache[i].stream != NULL) { - - if (! alureDestroyStream(modCache[i].stream, 0, NULL)) { - debugOut("Failed to destroy stream: %s\n", - alureGetErrorString()); - } + for (int i = 0; i < MAX_SAMPLES; ++i) + freeSound(i); - } - } -#endif - _silenceIKillYou = false; + for (int i = 0; i < MAX_MODS; ++i) + stopMOD(i); } /* * Some setters: */ - void SoundManager::setMusicVolume(int a, int v) { if (!_soundOK) return; -#if 0 - if (_modCache[a].playing) { - alSourcef(modCache[a].playingOnSource, AL_GAIN, (float) _modLoudness * v / 256); + + if (g_sludge->_mixer->isSoundHandleActive(_modCache[a].handle)) { + _modCache[a].vol = v; + g_sludge->_mixer->setChannelVolume(_modCache[a].handle, _modLoudness * v / 256); } -#endif } void SoundManager::setDefaultMusicVolume(int v) { @@ -176,14 +150,11 @@ int SoundManager::findInSoundCache(int a) { void SoundManager::stopMOD(int i) { if (!_soundOK) return; -#if 0 - alGetError(); - if (modCache[i].playing) { - if (! alureStopSource(modCache[i].playingOnSource, AL_TRUE)) { - debugOut("Failed to stop source: %s\n", alureGetErrorString()); - } + + if (g_sludge->_mixer->isSoundHandleActive(_modCache[i].handle)) { + g_sludge->_mixer->stopHandle(_modCache[i].handle); } -#endif + _modCache[i].fileLoaded = -1; } void SoundManager::huntKillSound(int filenum) { @@ -191,15 +162,10 @@ void SoundManager::huntKillSound(int filenum) { return; int gotSlot = findInSoundCache(filenum); - if (gotSlot == -1) return; - - _silenceIKillYou = true; - - if (g_sludge->_mixer->isSoundHandleActive(_soundCache[gotSlot].handle)) { - g_sludge->_mixer->stopHandle(_soundCache[gotSlot].handle); - } + if (gotSlot == -1) + return; - _silenceIKillYou = false; + freeSound(gotSlot); } void SoundManager::freeSound(int a) { @@ -214,6 +180,8 @@ void SoundManager::freeSound(int a) { handleSoundLists(); } + _soundCache[a].inSoundList = false; + _soundCache[a].looping = false; _soundCache[a].fileLoaded = -1; _silenceIKillYou = false; @@ -233,69 +201,33 @@ void SoundManager::huntKillFreeSound(int filenum) { */ bool SoundManager::playMOD(int f, int a, int fromTrack) { #if 0 + if (!_soundOK) + return true; + stopMOD(a); + // load sound setResourceForFatal(f); - uint32 length = openFileFromNum(f); + uint length = g_sludge->_resMan->openFileFromNum(f); if (length == 0) { - finishAccess(); + g_sludge->_resMan->finishAccess(); setResourceForFatal(-1); return false; } - Common::SeekableReadStream *memImage = bigDataFile->readStream(length); - if (memImage->size() != length || bigDataFile->err()) + // make audio stream + Common::SeekableReadStream *readStream = g_sludge->_resMan->getData(); + Common::SeekableReadStream *memImage = readStream->readStream(length); + if (memImage->size() != (int)length || readStream->err()) debug("Sound reading failed"); Audio::AudioStream *stream = Audio::makeProtrackerStream(memImage); - //TODO: replace by xm file decoders + if (!stream) return false; // play sound - Audio::SoundHandle soundHandle; - g_sludge->_mixer->playStream(Audio::Mixer::kSFXSoundType, &soundHandle, + g_sludge->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_modCache[a].handle, stream, -1, Audio::Mixer::kMaxChannelVolume); - if (!_soundOK) - return true; - stopMOD(a); - - setResourceForFatal(f); - uint32 length = openFileFromNum(f); - if (length == 0) { - finishAccess(); - setResourceForFatal(-1); - return false; - } - - byte *memImage; - memImage = (byte *) loadEntireFileToMemory(bigDataFile, length); - if (! memImage) return fatal(ERROR_MUSIC_MEMORY_LOW); - - _modCache[a].stream = alureCreateStreamFromMemory(memImage, length, 19200, 0, NULL); - - delete memImage; - - if (_modCache[a].stream != NULL) { - setMusicVolume(a, defVol); - - if (! alureSetStreamOrder(modCache[a].stream, fromTrack)) { - debugOut("Failed to set stream order: %s\n", - alureGetErrorString()); - } - - playStream(a, true, true); - - } else { - - debugOut("Failed to create stream from MOD: %s\n", - alureGetErrorString()); - - warning(ERROR_MUSIC_ODDNESS); - _soundCache[a].stream = NULL; - _soundCache[a].playing = false; - _soundCache[a].playingOnSource = 0; - } - setResourceForFatal(-1); #endif return true; } @@ -363,20 +295,12 @@ int SoundManager::makeSoundAudioStream(int f, Audio::AudioStream *&audiostream, return -1; int a = findInSoundCache(f); - if (a != -1) { // if this sound has been loaded before - // still playing - if (g_sludge->_mixer->isSoundHandleActive(_soundCache[a].handle)) { - g_sludge->_mixer->stopHandle(_soundCache[a].handle); // stop it - if (_soundCache[a].inSoundList) { - handleSoundLists(); - } - } - } else { + if (a == -1) { if (f == -2) return -1; a = findEmptySoundSlot(); - freeSound(a); } + freeSound(a); setResourceForFatal(f); uint32 length = g_sludge->_resMan->openFileFromNum(f); @@ -398,6 +322,7 @@ int SoundManager::makeSoundAudioStream(int f, Audio::AudioStream *&audiostream, if (stream) { audiostream = Audio::makeLoopingAudioStream(stream, loopy ? 0 : 1); _soundCache[a].fileLoaded = f; + _soundCache[a].looping = loopy; setResourceForFatal(-1); } else { audiostream = nullptr; diff --git a/engines/sludge/sound.h b/engines/sludge/sound.h index cdd76b33cc..1e1a2a47e4 100644 --- a/engines/sludge/sound.h +++ b/engines/sludge/sound.h @@ -83,9 +83,9 @@ private: struct SoundThing { Audio::SoundHandle handle; - int fileLoaded, vol; //Used for sounds only. (sound saving/loading) - bool looping; //Used for sounds only. (sound saving/loading) - bool inSoundList; + int fileLoaded, vol; //Used for wav/ogg sounds only. (sound saving/loading) + bool looping; //Used for wav/ogg sounds only. (sound saving/loading) + bool inSoundList; //Used for wav/ogg sounds only }; typedef Common::List<SoundList *> SoundListHandles; @@ -97,9 +97,7 @@ private: bool _isHandlingSoundList; SoundThing *_soundCache; - #if 0 SoundThing *_modCache; - #endif int _defVol; int _defSoundVol; diff --git a/engines/sludge/sprites.cpp b/engines/sludge/sprites.cpp index 98d88a8337..c37c4a1905 100644 --- a/engines/sludge/sprites.cpp +++ b/engines/sludge/sprites.cpp @@ -271,6 +271,13 @@ bool GraphicsManager::loadSpriteBank(int fileNum, SpriteBank &loadhere, bool isF // pasteSpriteToBackDrop uses the colour specified by the setPasteColour (or setPasteColor) void GraphicsManager::pasteSpriteToBackDrop(int x1, int y1, Sprite &single, const SpritePalette &fontPal) { + // kill zBuffer + if (_zBuffer->originalNum >= 0 && _zBuffer->sprites) { + int num = _zBuffer->originalNum; + killZBuffer(); + _zBuffer->originalNum = num; + } + //TODO: shader: useLightTexture x1 -= single.xhot; y1 -= single.yhot; @@ -282,6 +289,13 @@ void GraphicsManager::pasteSpriteToBackDrop(int x1, int y1, Sprite &single, cons // burnSpriteToBackDrop adds text in the colour specified by setBurnColour // using the differing brightness levels of the font to achieve an anti-aliasing effect. void GraphicsManager::burnSpriteToBackDrop(int x1, int y1, Sprite &single, const SpritePalette &fontPal) { + // kill zBuffer + if (_zBuffer->originalNum >= 0 && _zBuffer->sprites) { + int num = _zBuffer->originalNum; + killZBuffer(); + _zBuffer->originalNum = num; + } + //TODO: shader: useLightTexture x1 -= single.xhot; y1 -= single.yhot - 1; diff --git a/engines/titanic/carry/mouth.cpp b/engines/titanic/carry/mouth.cpp index d750fc969e..1b2830b99e 100644 --- a/engines/titanic/carry/mouth.cpp +++ b/engines/titanic/carry/mouth.cpp @@ -74,6 +74,11 @@ bool CMouth::PETGainedObjectMsg(CPETGainedObjectMsg *msg) { _field13C = true; } + // WORKAROUND: If Mouth is removed from Titania after inserting, + // message the Titania control so it can be flagged as removed + CTakeHeadPieceMsg headpieceMsg(getName()); + headpieceMsg.execute("TitaniaControl"); + return true; } diff --git a/engines/titanic/game/bridge_view.cpp b/engines/titanic/game/bridge_view.cpp index e8d70c8c43..14361b4e8c 100644 --- a/engines/titanic/game/bridge_view.cpp +++ b/engines/titanic/game/bridge_view.cpp @@ -55,6 +55,7 @@ bool CBridgeView::ActMsg(CActMsg *msg) { } else if (msg->_action == "Go") { _action = BA_GO; setVisible(true); + hideMouse(); volumeMsg._volume = 100; volumeMsg.execute("EngineSounds"); onMsg.execute("EngineSounds"); @@ -67,10 +68,13 @@ bool CBridgeView::ActMsg(CActMsg *msg) { if (msg->_action == "Cruise") { _action = BA_CRUISE; setVisible(true); + hideMouse(); playMovie(MOVIE_NOTIFY_OBJECT); } else if (msg->_action == "GoEnd") { _action = BA_ENDING1; setVisible(true); + hideMouse(); + CChangeMusicMsg musicMsg; musicMsg._flags = 1; musicMsg.execute("BridgeAutoMusicPlayer"); @@ -90,6 +94,7 @@ bool CBridgeView::MovieEndMsg(CMovieEndMsg *msg) { case BA_GO: case BA_CRUISE: setVisible(false); + showMouse(); decTransitions(); break; diff --git a/engines/titanic/game/chicken_dispensor.cpp b/engines/titanic/game/chicken_dispensor.cpp index 89873dcc4d..d44bc7157b 100644 --- a/engines/titanic/game/chicken_dispensor.cpp +++ b/engines/titanic/game/chicken_dispensor.cpp @@ -104,7 +104,7 @@ bool CChickenDispensor::StatusChangeMsg(CStatusChangeMsg *msg) { } bool CChickenDispensor::MovieEndMsg(CMovieEndMsg *msg) { - int movieFrame = getMovieFrame(); + int movieFrame = msg->_endFrame; if (movieFrame == 16) { // Dispensed a chicken @@ -113,12 +113,7 @@ bool CChickenDispensor::MovieEndMsg(CMovieEndMsg *msg) { CActMsg actMsg("Dispense Chicken"); actMsg.execute("Chicken"); - if (_dispenseMode == DISPENSE_HOT) { - // A properly hot chicken is dispensed, no further ones will be - // until the current one is used up, and the fuse in Titania's - // fusebox is removed and replaced - _dispenseMode = DISPENSE_NONE; - } else { + if (_dispenseMode != DISPENSE_HOT) { // WORKAROUND: If the fuse for the dispensor is removed in Titania's fusebox, // make the dispensed chicken already cold CChicken::_temperature = 0; diff --git a/engines/titanic/game/fan_control.cpp b/engines/titanic/game/fan_control.cpp index 7ed22fd560..fc99bd8b36 100644 --- a/engines/titanic/game/fan_control.cpp +++ b/engines/titanic/game/fan_control.cpp @@ -107,7 +107,7 @@ bool CFanControl::StatusChangeMsg(CStatusChangeMsg *msg) { case 2: // Fan Speed button if (_fanOn) { - _state = (_state + 1) % 4; + _state = (_state + 1) % 3; switch (_state) { case 0: playMovie(18, 24, 0); diff --git a/engines/titanic/game_manager.cpp b/engines/titanic/game_manager.cpp index 13fe1d2872..65a2a278c8 100644 --- a/engines/titanic/game_manager.cpp +++ b/engines/titanic/game_manager.cpp @@ -81,8 +81,8 @@ void CGameManager::preLoad() { _timers.destroyContents(); _soundMaker = nullptr; - _trueTalkManager.preLoad(); _sound.preLoad(); + _trueTalkManager.preLoad(); } void CGameManager::postLoad(CProjectItem *project) { diff --git a/engines/titanic/main_game_window.cpp b/engines/titanic/main_game_window.cpp index 053712d412..cfea98cdf1 100644 --- a/engines/titanic/main_game_window.cpp +++ b/engines/titanic/main_game_window.cpp @@ -341,6 +341,12 @@ void CMainGameWindow::keyDown(Common::KeyState keyState) { _gameManager->_gameState.changeView(newView, nullptr); } + } else if (keyState.keycode == Common::KEYCODE_F5) { + // Show the GMM save dialog + g_vm->showScummVMSaveDialog(); + } else if (keyState.keycode == Common::KEYCODE_F7) { + // Show the GMM load dialog + g_vm->showScummVMRestoreDialog(); } else if (_inputAllowed) { _gameManager->_inputTranslator.keyDown(keyState); } diff --git a/engines/titanic/npcs/titania.cpp b/engines/titanic/npcs/titania.cpp index 70ca4bace6..d3e3395fc8 100644 --- a/engines/titanic/npcs/titania.cpp +++ b/engines/titanic/npcs/titania.cpp @@ -149,9 +149,10 @@ bool CTitania::ActMsg(CActMsg *msg) { playSound("z#47.wav", 100); changeView("Titania.Node 7.S", ""); + // Re-enable control, and reset bomb's volume back to normal 60% petShow(); enableMouse(); - CSetFrameMsg frameMsg; + CSetFrameMsg frameMsg(60); frameMsg.execute("Bomb"); } else if (msg->_action == "CheckHead") { @@ -173,8 +174,9 @@ bool CTitania::ActMsg(CActMsg *msg) { workingMsg3._value = _speechCentre ? "Working" : "Random"; } - if (_centralCore && _eye1 && _eye2 && _ear1 && _ear2 && _nose && _mouth - && _speechCentre && _olfactoryCentre && _auditoryCentre) { + if (_centralCore && _eye1 && _eye2 && _ear1 && _ear2 && _nose + && _mouth && _visionCentre && _speechCentre + && _olfactoryCentre && _auditoryCentre) { CProximity prox(Audio::Mixer::kSpeechSoundType); playSound("z#47.wav", prox); @@ -203,11 +205,14 @@ bool CTitania::EnterViewMsg(CEnterViewMsg *msg) { disableMouse(); petHide(); + // The Bomb uses the CSetFrameMsg as a hack for setting the volume. + // In case it's currently active, set it to a quieter 25% so that + // it won't obscure Titania's speech. CSetFrameMsg frameMsg; frameMsg._frameNumber = 25; frameMsg.execute("Bomb"); - playCutscene(0, 52); + playCutscene(0, 52); setVisible(false); CActMsg actMsg("TitaniaSpeech"); actMsg.execute("TitaniaSpeech"); diff --git a/engines/titanic/sound/qmixer.cpp b/engines/titanic/sound/qmixer.cpp index 5c511c3cae..beb1502ab4 100644 --- a/engines/titanic/sound/qmixer.cpp +++ b/engines/titanic/sound/qmixer.cpp @@ -208,18 +208,13 @@ void QMixer::qsWaveMixPump() { if (!channel._sounds.empty()) { SoundEntry &sound = channel._sounds.front(); if (sound._started && !_mixer->isSoundHandleActive(sound._soundHandle)) { - if (sound._loops == -1 || sound._loops-- > 0) { - // Need to loop (replay) the sound again - sound._soundHandle = sound._waveFile->play(channel.getRawVolume()); - } else { - // Sound is finished - if (sound._callback) - // Call the callback to signal end - sound._callback(iChannel, sound._waveFile, sound._userData); - - // Remove sound record from channel - channel._sounds.erase(channel._sounds.begin()); - } + // Sound is finished + if (sound._callback) + // Call the callback to signal end + sound._callback(iChannel, sound._waveFile, sound._userData); + + // Remove sound record from channel + channel._sounds.erase(channel._sounds.begin()); } } @@ -232,7 +227,8 @@ void QMixer::qsWaveMixPump() { channel._distance = 0.0; // Play the wave - sound._soundHandle = sound._waveFile->play(channel.getRawVolume()); + sound._soundHandle = sound._waveFile->play( + sound._loops, channel.getRawVolume()); sound._started = true; } } diff --git a/engines/titanic/sound/sound_manager.cpp b/engines/titanic/sound/sound_manager.cpp index 514618783b..4b5887b1e7 100644 --- a/engines/titanic/sound/sound_manager.cpp +++ b/engines/titanic/sound/sound_manager.cpp @@ -288,7 +288,7 @@ void QSoundManager::setVolume(int handle, uint volume, uint seconds) { _channelsVolume[slot._channel] = volume; updateVolume(slot._channel, seconds * 1000); - if (volume) { + if (!volume) { uint ticks = g_vm->_events->getTicksCount() + seconds * 1000; if (!slot._ticks || ticks >= slot._ticks) slot._ticks = ticks; diff --git a/engines/titanic/sound/wave_file.cpp b/engines/titanic/sound/wave_file.cpp index 8a4755ac97..c1aab42a7f 100644 --- a/engines/titanic/sound/wave_file.cpp +++ b/engines/titanic/sound/wave_file.cpp @@ -204,10 +204,15 @@ void CWaveFile::unlock(const int16 *ptr) { // No implementation needed in ScummVM } -Audio::SoundHandle CWaveFile::play(byte volume) { - Audio::SeekableAudioStream *stream = createAudioStream(); +Audio::SoundHandle CWaveFile::play(int numLoops, byte volume) { + Audio::SeekableAudioStream *audioStream = createAudioStream(); Audio::SoundHandle handle; + Audio::AudioStream *stream = audioStream; + if (numLoops != 0) + stream = new Audio::LoopingAudioStream(audioStream, + (numLoops == -1) ? 0 : numLoops); + _mixer->playStream(_soundType, &handle, stream, -1, volume, 0, DisposeAfterUse::NO); return handle; diff --git a/engines/titanic/sound/wave_file.h b/engines/titanic/sound/wave_file.h index 17c7b62f4b..c14891e2e4 100644 --- a/engines/titanic/sound/wave_file.h +++ b/engines/titanic/sound/wave_file.h @@ -126,8 +126,12 @@ public: /** * Plays the wave file + * @param numLoops Number of times to loop. 0 for none, + * -1 for infinite, and >0 for specified number of times + * @param volume Volume to play at + * @returns Audio handle for started sound */ - Audio::SoundHandle play(byte volume); + Audio::SoundHandle play(int numLoops, byte volume); }; } // End of namespace Titanic diff --git a/engines/titanic/support/credit_text.cpp b/engines/titanic/support/credit_text.cpp index 98c3d9bf1d..6ee17a2b95 100644 --- a/engines/titanic/support/credit_text.cpp +++ b/engines/titanic/support/credit_text.cpp @@ -96,7 +96,7 @@ void CCreditText::setup() { _screenManagerP->setFontNumber(oldFontNumber); _groupIt = _groups.begin(); _lineIt = (*_groupIt)->_lines.begin(); - _yOffset = _objectP->getBounds().height() + _fontHeight * 2; + _yOffset = _objectP->_bounds.height() + _fontHeight * 2; } CString CCreditText::readLine(Common::SeekableReadStream *stream) { diff --git a/engines/titanic/titanic.cpp b/engines/titanic/titanic.cpp index 8a1b00c0fc..b6ee868877 100644 --- a/engines/titanic/titanic.cpp +++ b/engines/titanic/titanic.cpp @@ -25,9 +25,11 @@ #include "common/config-manager.h" #include "common/debug-channels.h" #include "common/events.h" +#include "common/translation.h" #include "engines/util.h" #include "graphics/scaler.h" #include "graphics/thumbnail.h" +#include "gui/saveload.h" #include "titanic/titanic.h" #include "titanic/debugger.h" #include "titanic/carry/hose.h" @@ -179,23 +181,30 @@ bool TitanicEngine::canLoadGameStateCurrently() { CGameManager *gameManager = _window->_gameManager; CScreenManager *screenMan = CScreenManager::_screenManagerPtr; - if (!_window->_inputAllowed || !gameManager->_gameState._petActive) + if (!_window->_inputAllowed) return false; if (screenMan && screenMan->_inputHandler->isLocked()) return false; CProjectItem *project = gameManager->_project; if (project) { - CPetControl *pet = project->getPetControl(); - if (pet && !pet->isAreaUnlocked()) - return false; + if (gameManager->_gameState._petActive) { + CPetControl *pet = project->getPetControl(); + if (pet && !pet->isAreaUnlocked()) + return false; + } + } else { + return false; } return true; } bool TitanicEngine::canSaveGameStateCurrently() { - return canLoadGameStateCurrently(); + CGameManager *gameManager = _window->_gameManager; + + return gameManager->_gameState._petActive && + canLoadGameStateCurrently(); } Common::Error TitanicEngine::loadGameState(int slot) { @@ -251,4 +260,47 @@ void TitanicEngine::GUIError(const char *msg, ...) { GUIErrorMessage(buffer); } + +void TitanicEngine::showScummVMSaveDialog() { + if (!canSaveGameStateCurrently()) + return; + + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); + + pauseEngine(true); + int slot = dialog->runModalWithCurrentTarget(); + pauseEngine(false); + + if (slot >= 0) { + Common::String desc = dialog->getResultString(); + + if (desc.empty()) { + // create our own description for the saved game, the user didn't enter it + desc = dialog->createDefaultSaveDescription(slot); + } + + // Save the game + saveGameState(slot, desc); + } + + delete dialog; +} + +void TitanicEngine::showScummVMRestoreDialog() { + if (!canLoadGameStateCurrently()) + return; + + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); + + pauseEngine(true); + int slot = dialog->runModalWithCurrentTarget(); + pauseEngine(false); + + if (slot >= 0) { + loadGameState(slot); + } + + delete dialog; +} + } // End of namespace Titanic diff --git a/engines/titanic/titanic.h b/engines/titanic/titanic.h index dc7fd156b8..0c15cf3e72 100644 --- a/engines/titanic/titanic.h +++ b/engines/titanic/titanic.h @@ -191,6 +191,16 @@ public: * Displays an error message in a GUI dialog */ void GUIError(const char *msg, ...) GCC_PRINTF(2, 3); + + /** + * Shows the ScummVM GMM save dialog + */ + void showScummVMSaveDialog(); + + /** + * Shows the ScummVM GMM load dialog + */ + void showScummVMRestoreDialog(); }; extern TitanicEngine *g_vm; diff --git a/engines/wage/detection_tables.h b/engines/wage/detection_tables.h index b207946882..bde10785df 100644 --- a/engines/wage/detection_tables.h +++ b/engines/wage/detection_tables.h @@ -158,7 +158,6 @@ static const ADGameDescription gameDescriptions[] = { FANGAME("Star Trek", "3067332e6f0bb0314579f9bf102e1b56", 53320), FANGAME("Strange Disappearance", "9d6e41b61c0fc90400e5da2fcb653a4a", 772282), FANGAME("The Sultan's Palace", "fde31cbcc77b66969b4cfcd43075341e", 456855), - // Code 0x03 in text FANGAME("Swamp Witch", "bd8c8394be31f7845d55785b7ccfbbde", 739781), // Original file name "Swamp Witch†" FANGAME("Swamp Witch", "07463c8b3b908b0c493a41b949ac1ff5", 740131), // alt version, normal file name FANGAME("Sweetspace Now!", "574dc7dd25543f7a516d6524f0c5ab33", 123813), // Comes with Jumble diff --git a/engines/wage/script.cpp b/engines/wage/script.cpp index c4cf23fcb1..3405c8bf47 100644 --- a/engines/wage/script.cpp +++ b/engines/wage/script.cpp @@ -1161,12 +1161,19 @@ void Script::convertToText() { break; if (c < 0x80) { - if (c < 0x20) - error("convertToText: Unknown code 0x%02x at %d", c, _data->pos()); + if (c < 0x20) { + warning("convertToText: Unknown code 0x%02x at %d", c, _data->pos()); + c = ' '; + } do { scr->line += c; c = _data->readByte(); + + if (c < 0x20) { + warning("convertToText: Unknown code 0x%02x at %d", c, _data->pos()); + c = ' '; + } } while (c < 0x80); _data->seek(-1, SEEK_CUR); diff --git a/graphics/macgui/mactextwindow.cpp b/graphics/macgui/mactextwindow.cpp index 9bbf2c8d10..85a07b975e 100644 --- a/graphics/macgui/mactextwindow.cpp +++ b/graphics/macgui/mactextwindow.cpp @@ -306,6 +306,9 @@ bool MacTextWindow::processEvent(Common::Event &event) { } } + if (hasAllFocus()) + return MacWindow::processEvent(event); // Pass it to upstream + if (click == kBorderScrollUp || click == kBorderScrollDown) { if (event.type == Common::EVENT_LBUTTONDOWN) { int consoleHeight = getInnerDimensions().height(); diff --git a/graphics/macgui/macwindow.cpp b/graphics/macgui/macwindow.cpp index 227f789d30..7b9afa4157 100644 --- a/graphics/macgui/macwindow.cpp +++ b/graphics/macgui/macwindow.cpp @@ -459,7 +459,9 @@ bool MacWindow::processEvent(Common::Event &event) { _draggedY = event.mouse.y; _wm->setFullRefresh(true); - (*_callback)(click, event, _dataPtr); + + if (_callback) + (*_callback)(click, event, _dataPtr); } break; case Common::EVENT_LBUTTONDOWN: diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp index 9a3e711c19..8eaf8e9f5b 100644 --- a/graphics/macgui/macwindowmanager.cpp +++ b/graphics/macgui/macwindowmanager.cpp @@ -289,8 +289,10 @@ void MacWindowManager::draw() { Common::Rect clip(w->getDimensions().left - 2, w->getDimensions().top - 2, w->getDimensions().right - 2, w->getDimensions().bottom - 2); clip.clip(_screen->getBounds()); + clip.clip(Common::Rect(0, 0, g_system->getWidth() - 1, g_system->getHeight() - 1)); - g_system->copyRectToScreen(_screen->getBasePtr(clip.left, clip.top), _screen->pitch, clip.left, clip.top, clip.width(), clip.height()); + if (!clip.isEmpty()) + g_system->copyRectToScreen(_screen->getBasePtr(clip.left, clip.top), _screen->pitch, clip.left, clip.top, clip.width(), clip.height()); } } diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp index 245319a931..981dd82e8b 100644 --- a/video/coktel_decoder.cpp +++ b/video/coktel_decoder.cpp @@ -1543,6 +1543,93 @@ Graphics::PixelFormat IMDDecoder::getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); } +class DPCMStream : public Audio::AudioStream { +public: + DPCMStream(Common::SeekableReadStream *stream, int rate, int channels, bool oldStereo) { + _stream = stream; + _rate = rate; + _channels = channels; + _oldStereo = oldStereo; + if (oldStereo) { + _buffer[0] = _buffer[1] = 0; + } + } + + ~DPCMStream() { + delete _stream; + } + + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return _channels == 2; } + int getRate() const { return _rate; } + bool endOfData() const { return _stream->pos() >= _stream->size() || _stream->eos() || _stream->err(); } + +private: + Common::SeekableReadStream *_stream; + int _channels; + int _rate; + int _buffer[2]; + bool _oldStereo; +}; + +int DPCMStream::readBuffer(int16 *buffer, const int numSamples) { + static const uint16 tableDPCM[128] = { + 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080, + 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120, + 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0, + 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230, + 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280, + 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0, + 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320, + 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370, + 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0, + 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480, + 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700, + 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00, + 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000 + }; + + assert((numSamples % _channels) == 0); + + int samples = 0; + + // Our starting position + if (!_oldStereo && _stream->pos() == 0) { + for (int i = 0; i < _channels; i++) + *buffer++ = _buffer[i] = _stream->readSint16LE(); + + samples += _channels; + } + + while (!endOfData() && samples < numSamples) { + if (_channels == 2 && _stream->size() == 1) { + warning("Buffer underrun in DPCMStream"); + break; + } + + for (int i = 0; i < _channels; i++) { + byte data = _stream->readByte(); + + if (data & 0x80) + _buffer[i] -= tableDPCM[data & 0x7f]; + else + _buffer[i] += tableDPCM[data]; + + // Emulating x86 16-bit signed register overflow + if (_buffer[i] > 32767) { + _buffer[i] -= 65536; + } else if (_buffer[i] < -32768) { + _buffer[i] += 65536; + } + + *buffer++ = _buffer[i]; + } + + samples += _channels; + } + + return samples; +} VMDDecoder::File::File() { offset = 0; @@ -1581,7 +1668,7 @@ VMDDecoder::VMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : _soundLastFilledFrame(0), _audioFormat(kAudioFormat8bitRaw), _hasVideo(false), _videoCodec(0), _blitMode(0), _bytesPerPixel(0), _firstFramePos(0), _videoBufferSize(0), _externalCodec(false), _codec(0), - _subtitle(-1), _isPaletted(true), _autoStartSound(true) { + _subtitle(-1), _isPaletted(true), _autoStartSound(true), _oldStereoBuffer(nullptr) { _videoBuffer [0] = 0; _videoBuffer [1] = 0; @@ -1625,7 +1712,7 @@ bool VMDDecoder::seek(int32 frame, int whence, bool restart) { delete _audioStream; _soundStage = kSoundLoaded; - _audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0); + createAudioStream(); } _subtitle = -1; @@ -1915,13 +2002,19 @@ bool VMDDecoder::assessAudioProperties() { } } else { + if (_soundStereo == 2) { + supportedFormat = false; + } + _soundBytesPerSample = 1; - _audioFormat = kAudioFormat8bitRaw; _soundHeaderSize = 0; _soundDataSize = _soundSliceSize; - if (_soundStereo > 0) - supportedFormat = false; + if (_soundStereo == 1) { + _audioFormat = kAudioFormat16bitDPCM; + } else { + _audioFormat = kAudioFormat8bitRaw; + } } if (!supportedFormat) { @@ -1930,14 +2023,12 @@ bool VMDDecoder::assessAudioProperties() { return false; } - _frameRate = Common::Rational(_soundFreq, _soundSliceSize); + _frameRate = Common::Rational(_soundFreq, _soundSliceSize / (_soundStereo == 1 ? 2 : 1)); _hasSound = true; _soundEnabled = true; _soundStage = kSoundLoaded; - - _audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0); - + createAudioStream(); return true; } @@ -2074,6 +2165,7 @@ void VMDDecoder::close() { _soundDataSize = 0; _soundLastFilledFrame = 0; _audioFormat = kAudioFormat8bitRaw; + _oldStereoBuffer = nullptr; _hasVideo = false; _videoCodec = 0; @@ -2123,9 +2215,9 @@ void VMDDecoder::processFrame() { bool startSound = false; - for (uint16 i = 0; i < _partsPerFrame; i++) { - uint32 pos = _stream->pos(); + _stream->seek(_frames[_curFrame].offset, SEEK_SET); + for (uint16 i = 0; i < _partsPerFrame; i++) { Part &part = _frames[_curFrame].parts[i]; if (part.type == kPartTypeAudio) { @@ -2147,7 +2239,7 @@ void VMDDecoder::processFrame() { if (_soundEnabled) { uint32 mask = _stream->readUint32LE(); - filledSoundSlices(part.size - 4, mask); + filledSoundSlices(part.size - /* mask size */ 4, mask); if (_soundStage == kSoundLoaded) startSound = true; @@ -2176,8 +2268,6 @@ void VMDDecoder::processFrame() { _stream->skip(part.size); } - _stream->seek(pos + part.size); - } else if ((part.type == kPartTypeVideo) && !_hasVideo) { warning("VMDDecoder::processFrame(): Header claims there's no video, but video found (%d)", part.size); @@ -2470,6 +2560,14 @@ void VMDDecoder::blit24(const Graphics::Surface &srcSurf, Common::Rect &rect) { } void VMDDecoder::emptySoundSlice(uint32 size) { + if (_soundStereo == 1) { + // Technically an empty slice could be used at the very beginning of the + // stream, but anywhere else it would need to dynamically calculate the + // delta between the current sample and zero sample level and the steps + // to get a zero level + error("Old-style stereo cannot be filled with an empty slice"); + } + byte *soundBuf = (byte *)malloc(size); if (soundBuf) { @@ -2488,6 +2586,17 @@ void VMDDecoder::filledSoundSlice(uint32 size) { return; } + if (_soundStereo == 1) { + void *buf = malloc(size); + assert(buf); + const uint32 numBytesRead = _stream->read(buf, size); + assert(numBytesRead == size); + const uint32 numBytesWritten = _oldStereoBuffer->write(buf, size); + assert(numBytesWritten == size); + free(buf); + return; + } + Common::SeekableReadStream *data = _stream->readStream(size); Audio::AudioStream *sliceStream = 0; @@ -2508,9 +2617,9 @@ void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) { uint8 max; uint8 n = evaluateMask(mask, fillInfo, max); - int32 extraSize; - - extraSize = size - n * _soundDataSize; + // extraSize is needed by videos in some games (GK2) or audio data will be + // incomplete + int32 extraSize = size - n * _soundDataSize; if (_soundSlicesCount > 32) extraSize -= (_soundSlicesCount - 32) * _soundDataSize; @@ -2518,6 +2627,11 @@ void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) { if (n > 0) extraSize /= n; + // extraSize cannot be negative or audio data will be incomplete in some + // games (old-style stereo videos in Lighthouse) + if (extraSize < 0) + extraSize = 0; + for (uint8 i = 0; i < max; i++) if (fillInfo[i]) filledSoundSlice(_soundDataSize + extraSize); @@ -2528,6 +2642,14 @@ void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) { filledSoundSlice((_soundSlicesCount - 32) * _soundDataSize + _soundHeaderSize); } +void VMDDecoder::createAudioStream() { + _audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0); + if (_soundStereo == 1) { + _oldStereoBuffer = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + _audioStream->queueAudioStream(new DPCMStream(_oldStereoBuffer, _soundFreq, 2, true)); + } +} + uint8 VMDDecoder::evaluateMask(uint32 mask, bool *fillInfo, uint8 &max) { max = MIN<int>(_soundSlicesCount - 1, 31); @@ -2555,86 +2677,12 @@ Audio::AudioStream *VMDDecoder::create8bitRaw(Common::SeekableReadStream *stream return Audio::makeRawStream(stream, _soundFreq, flags, DisposeAfterUse::YES); } -class DPCMStream : public Audio::AudioStream { -public: - DPCMStream(Common::SeekableReadStream *stream, int rate, int channels) { - _stream = stream; - _rate = rate; - _channels = channels; - } - - ~DPCMStream() { - delete _stream; - } - - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const { return _channels == 2; } - int getRate() const { return _rate; } - bool endOfData() const { return _stream->pos() >= _stream->size() || _stream->eos() || _stream->err(); } - -private: - Common::SeekableReadStream *_stream; - int _channels; - int _rate; - int _buffer[2]; -}; - -int DPCMStream::readBuffer(int16 *buffer, const int numSamples) { - static const uint16 tableDPCM[128] = { - 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080, - 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120, - 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0, - 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230, - 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280, - 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0, - 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320, - 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370, - 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0, - 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480, - 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700, - 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00, - 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000 - }; - - assert((numSamples % _channels) == 0); - - int samples = 0; - - // Our starting position - if (_stream->pos() == 0) { - for (int i = 0; i < _channels; i++) - *buffer++ = _buffer[i] = _stream->readSint16LE(); - - samples += _channels; - } - - while (!endOfData() && samples < numSamples) { - for (int i = 0; i < _channels; i++) { - byte data = _stream->readByte(); - - if (data & 0x80) - _buffer[i] -= tableDPCM[data & 0x7f]; - else - _buffer[i] += tableDPCM[data]; - - // Emulating x86 16-bit signed register overflow - if (_buffer[i] > 32767) { - _buffer[i] -= 65536; - } else if (_buffer[i] < -32768) { - _buffer[i] += 65536; - } - - *buffer++ = _buffer[i]; - } - - samples += _channels; - } - - return samples; -} - Audio::AudioStream *VMDDecoder::create16bitDPCM(Common::SeekableReadStream *stream) { - return new DPCMStream(stream, _soundFreq, (_soundStereo == 0) ? 1 : 2); + // Old-style stereo audio blocks are not self-contained so cannot be played + // using this mechanism + assert(_soundStereo != 1); + + return new DPCMStream(stream, _soundFreq, (_soundStereo == 0) ? 1 : 2, false); } class VMD_ADPCMStream : public Audio::DVI_ADPCMStream { diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h index b8faa99712..d1189c9394 100644 --- a/video/coktel_decoder.h +++ b/video/coktel_decoder.h @@ -43,6 +43,7 @@ namespace Common { struct Rect; +class MemoryReadWriteStream; class SeekableReadStream; } namespace Audio { @@ -503,6 +504,17 @@ private: AudioFormat _audioFormat; bool _autoStartSound; + /** + * Old stereo format packs a DPCM stream into audio packets without ensuring + * that each packet contains an even amount of samples. In order for the + * stream to play back correctly, all audio data needs to be pushed into a + * single data buffer and read from there. + * + * This buffer is owned by _audioStream and will be disposed when + * _audioStream is disposed. + */ + Common::MemoryReadWriteStream *_oldStereoBuffer; + // Video properties bool _hasVideo; uint32 _videoCodec; @@ -545,6 +557,7 @@ private: void emptySoundSlice (uint32 size); void filledSoundSlice (uint32 size); void filledSoundSlices(uint32 size, uint32 mask); + void createAudioStream(); uint8 evaluateMask(uint32 mask, bool *fillInfo, uint8 &max); |