[Scummvm-git-logs] scummvm master -> 1224510cd1277b39552190e4f28c22bc1f649caa
sev-
noreply at scummvm.org
Thu Jul 10 12:09:32 UTC 2025
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
1224510cd1 ADL: Add text-to-speech (TTS)
Commit: 1224510cd1277b39552190e4f28c22bc1f649caa
https://github.com/scummvm/scummvm/commit/1224510cd1277b39552190e4f28c22bc1f649caa
Author: ellm135 (ellm13531 at gmail.com)
Date: 2025-07-10T14:09:29+02:00
Commit Message:
ADL: Add text-to-speech (TTS)
Changed paths:
engines/adl/adl.cpp
engines/adl/adl.h
engines/adl/detection.cpp
engines/adl/detection.h
engines/adl/display.cpp
engines/adl/display.h
engines/adl/hires1.cpp
engines/adl/metaengine.cpp
diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp
index 7ecf997e8cd..53ca0776517 100644
--- a/engines/adl/adl.cpp
+++ b/engines/adl/adl.cpp
@@ -237,7 +237,8 @@ Common::String AdlEngine::inputString(byte prompt) const {
if (b == ('\r' | 0x80)) {
s += b;
- _display->printString(Common::String(b));
+ _display->printString(Common::String(b), false);
+ _display->sayText(s, Common::TextToSpeechManager::INTERRUPT);
return s;
}
@@ -256,7 +257,7 @@ Common::String AdlEngine::inputString(byte prompt) const {
} else {
if (s.size() < 255) {
s += b;
- _display->printString(Common::String(b));
+ _display->printString(Common::String(b), false);
}
}
}
@@ -279,8 +280,10 @@ byte AdlEngine::inputKey(bool showCursor) const {
continue;
switch (event.kbd.keycode) {
- case Common::KEYCODE_BACKSPACE:
case Common::KEYCODE_RETURN:
+ stopTextToSpeech();
+ // fall through
+ case Common::KEYCODE_BACKSPACE:
key = convertKey(event.kbd.keycode);
break;
default:
@@ -544,6 +547,13 @@ void AdlEngine::loadDroppedItemOffsets(Common::ReadStream &stream, byte count) {
}
}
+void AdlEngine::stopTextToSpeech() const {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr && ConfMan.getBool("tts_enabled") && ttsMan->isSpeaking()) {
+ ttsMan->stop();
+ }
+}
+
void AdlEngine::drawPic(byte pic, Common::Point pos) const {
if (_roomData.pictures.contains(pic)) {
Common::StreamPtr stream(_roomData.pictures[pic]->createReadStream());
@@ -776,6 +786,12 @@ Common::Error AdlEngine::run() {
init();
g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr) {
+ ttsMan->enable(ConfMan.getBool("tts_enabled"));
+ ttsMan->setLanguage(ConfMan.get("language"));
+ }
+
int saveSlot = ConfMan.getInt("save_slot");
if (saveSlot >= 0) {
if (loadGameState(saveSlot).getCode() != Common::kNoError)
diff --git a/engines/adl/adl.h b/engines/adl/adl.h
index 3c151fdeec1..334a8e99009 100644
--- a/engines/adl/adl.h
+++ b/engines/adl/adl.h
@@ -311,6 +311,8 @@ protected:
virtual void advanceClock() { }
void loadDroppedItemOffsets(Common::ReadStream &stream, byte count);
+ void stopTextToSpeech() const;
+
// Opcodes
typedef Common::SharedPtr<Common::Functor1<ScriptEnv &, int> > Opcode;
diff --git a/engines/adl/detection.cpp b/engines/adl/detection.cpp
index 29b49edc8f8..76bf6159153 100644
--- a/engines/adl/detection.cpp
+++ b/engines/adl/detection.cpp
@@ -37,8 +37,8 @@ static const DebugChannelDef debugFlagList[] = {
DEBUG_CHANNEL_END
};
-#define DEFAULT_OPTIONS GUIO6(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES, GAMEOPTION_APPLE2E_CURSOR, GUIO_NOMIDI)
-#define MH_OPTIONS GUIO6(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES, GAMEOPTION_APPLE2E_CURSOR, GUIO_NOMIDI)
+#define DEFAULT_OPTIONS GUIO7(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES, GAMEOPTION_APPLE2E_CURSOR, GUIO_NOMIDI, GAMEOPTION_TTS)
+#define MH_OPTIONS GUIO7(GAMEOPTION_NTSC, GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_MONO_TEXT, GAMEOPTION_SCANLINES, GAMEOPTION_APPLE2E_CURSOR, GUIO_NOMIDI, GAMEOPTION_TTS)
static const PlainGameDescriptor adlGames[] = {
{ "hires0", "Hi-Res Adventure #0: Mission Asteroid" },
diff --git a/engines/adl/detection.h b/engines/adl/detection.h
index 0026d3b23a1..faa1a698428 100644
--- a/engines/adl/detection.h
+++ b/engines/adl/detection.h
@@ -84,6 +84,7 @@ struct AdlGameDescription {
#define GAMEOPTION_NTSC GUIO_GAMEOPTIONS4
#define GAMEOPTION_MONO_TEXT GUIO_GAMEOPTIONS5
#define GAMEOPTION_APPLE2E_CURSOR GUIO_GAMEOPTIONS6
+#define GAMEOPTION_TTS GUIO_GAMEOPTIONS7
} // End of namespace Adl
diff --git a/engines/adl/display.cpp b/engines/adl/display.cpp
index ef59f4a4086..4f554981db8 100644
--- a/engines/adl/display.cpp
+++ b/engines/adl/display.cpp
@@ -19,6 +19,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/debug.h"
#include "common/rect.h"
#include "common/str.h"
@@ -73,20 +74,55 @@ void Display::moveCursorTo(const Common::Point &pos) {
error("Cursor position (%i, %i) out of bounds", pos.x, pos.y);
}
-void Display::printString(const Common::String &str) {
+void Display::printString(const Common::String &str, bool voiceString) {
for (const auto &c : str)
printChar(c);
+ if (voiceString) {
+ sayText(str);
+ }
+
renderText();
}
-void Display::printAsciiString(const Common::String &str) {
+void Display::printAsciiString(const Common::String &str, bool voiceString) {
for (const auto &c : str)
printChar(asciiToNative(c));
+ if (voiceString) {
+ sayText(str);
+ }
+
renderText();
}
+void Display::sayText(const Common::String &text, Common::TextToSpeechManager::Action action) const {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan != nullptr && ConfMan.getBool("tts_enabled")) {
+ ttsMan->say(convertText(text), action);
+ }
+}
+
+Common::U32String Display::convertText(const Common::String &text) const {
+ Common::String result(text);
+
+ bool startsWithDash = result[0] == '\xad';
+
+ for (uint i = 0; i < result.size(); i++) {
+ // Convert carriage returns and dashes to spaces
+ // Only convert dashes if the line starts with a dash (which is usually the case with the "enter command"
+ // prompt), to avoid converting mid-line dashes to spaces and causing odd voicing
+ // (i.e. if the dash in "Hi-Res Adventure" is replaced, English TTS pronounces it as "Hi Residential Adventure")
+ if (result[i] == '\x8d' || (startsWithDash && result[i] == '\xad')) {
+ result[i] = '\xa0';
+ }
+
+ result[i] &= 0x7f;
+ }
+
+ return Common::U32String(result, Common::CodePage::kASCII);
+}
+
void Display::setCharAtCursor(byte c) {
_textBuf[_cursorPos] = c;
}
diff --git a/engines/adl/display.h b/engines/adl/display.h
index 9ba6680e297..ba5c68edc36 100644
--- a/engines/adl/display.h
+++ b/engines/adl/display.h
@@ -22,6 +22,7 @@
#ifndef ADL_DISPLAY_H
#define ADL_DISPLAY_H
+#include "common/text-to-speech.h"
#include "common/types.h"
namespace Common {
@@ -53,8 +54,10 @@ public:
void moveCursorTo(const Common::Point &pos);
void moveCursorForward();
void moveCursorBackward();
- void printString(const Common::String &str);
- void printAsciiString(const Common::String &str);
+ void printString(const Common::String &str, bool voiceString = true);
+ void printAsciiString(const Common::String &str, bool voiceString = true);
+ void sayText(const Common::String &text, Common::TextToSpeechManager::Action action = Common::TextToSpeechManager::QUEUE) const;
+ Common::U32String convertText(const Common::String &text) const;
void setCharAtCursor(byte c);
uint getTextWidth() const { return _textWidth; }
uint getTextHeight() const { return _textHeight; }
diff --git a/engines/adl/hires1.cpp b/engines/adl/hires1.cpp
index e1f40768185..b1109d5d6db 100644
--- a/engines/adl/hires1.cpp
+++ b/engines/adl/hires1.cpp
@@ -109,6 +109,8 @@ protected:
void HiRes1Engine::showInstructions(Common::SeekableReadStream &stream) {
_display->setMode(Display::kModeText);
+ Common::String ttsMessage;
+
for (;;) {
byte opc = stream.readByte();
@@ -121,6 +123,9 @@ void HiRes1Engine::showInstructions(Common::SeekableReadStream &stream) {
// HOME
_display->home();
} else if (addr == 0x6ffd) {
+ _display->sayText(ttsMessage);
+ ttsMessage.clear();
+
// GETLN1
inputString();
@@ -129,6 +134,13 @@ void HiRes1Engine::showInstructions(Common::SeekableReadStream &stream) {
} else {
// We assume print string call (addr varies per game)
Common::String str = readString(stream);
+ ttsMessage += str;
+
+ // If the string ends in two carriage returns, add a newline to the end of the TTS message
+ // (since carriage returns will be replaced with spaces)
+ if (str.size() > 1 && str[str.size() - 1] == '\x8d' && str[str.size() - 2] == '\x8d') {
+ ttsMessage += '\n';
+ }
if (stream.err() || stream.eos())
error("Error reading instructions");
@@ -137,11 +149,11 @@ void HiRes1Engine::showInstructions(Common::SeekableReadStream &stream) {
size_t posChr4 = str.findFirstOf(_display->asciiToNative(4));
if (posChr4 != str.npos) {
- _display->printString(str.substr(0, posChr4));
+ _display->printString(str.substr(0, posChr4), false);
return;
}
- _display->printString(str);
+ _display->printString(str, false);
}
}
}
@@ -186,18 +198,23 @@ void HiRes1Engine::runIntro() {
_display->home();
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_0, '"');
- _display->printAsciiString(str + '\r');
+ _display->printAsciiString(str + '\r', false);
+ Common::String ttsMessage = str + ' ';
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_1, '"');
- _display->printAsciiString(str + "\r\r");
+ _display->printAsciiString(str + "\r\r", false);
+ ttsMessage += str + '\n';
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_2, '"');
- _display->printAsciiString(str + "\r\r");
+ _display->printAsciiString(str + "\r\r", false);
+ ttsMessage += str + '\n';
str = readStringAt(*basic, IDI_HR1_OFS_PD_TEXT_3, '"');
- _display->printAsciiString(str + '\r');
+ _display->printAsciiString(str + '\r', false);
+ _display->sayText(ttsMessage + ' ' + str);
inputKey();
+ stopTextToSpeech();
if (shouldQuit())
return;
}
@@ -636,9 +653,11 @@ void HiRes1Engine_VF::runIntro() {
if (key == _display->asciiToNative('M')) {
stream->seek(0x75);
+ _display->sayText("M", Common::TextToSpeechManager::INTERRUPT);
showInstructions(*stream);
return;
} else if (key == _display->asciiToNative('J')) {
+ _display->sayText("J", Common::TextToSpeechManager::INTERRUPT);
return;
}
}
diff --git a/engines/adl/metaengine.cpp b/engines/adl/metaengine.cpp
index d8723278412..0019fb68ced 100644
--- a/engines/adl/metaengine.cpp
+++ b/engines/adl/metaengine.cpp
@@ -110,6 +110,20 @@ static const ADExtraGuiOptionsMap optionsList[] = {
}
},
+#ifdef USE_TTS
+ {
+ GAMEOPTION_TTS,
+ {
+ _s("Enable Text to Speech"),
+ _s("Use TTS to read text in the game (if TTS is available)"),
+ "tts_enabled",
+ false,
+ 0,
+ 0
+ }
+ },
+#endif
+
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
More information about the Scummvm-git-logs
mailing list