[Scummvm-git-logs] scummvm master -> d4a98dcadbb9d374966c207be34f7a1926be26fe
bluegr
bluegr at gmail.com
Sun Sep 1 21:48:53 CEST 2019
This automated email contains information about 95 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
f78fc85f3a TTS: Create a TTS skeleton
39e74b027e TTS: Add part of linux TTS
8c44b03231 TTS: Resolve segfault when exiting scummvm
7f895d21df TTS: Add checks for correct TTS engine state.
62a011e28a TTS: Add ttsStatus push and pop functions
fa6faca76a TTS: Add TTS checkbox to Options
b5cebcbeae TTS: Add text to speech to the GUI.
8bd7e39265 TTS: Add voice selection to options
7d72fc0d60 TTS: Restrict TTS on linux to only english
a9eaf46c42 TTS: Add warning if the TTS could not be inited
e801e83f43 TTS: Restart spd connection on speak error.
c899f5fbc3 TTS: Add Accessibility tab to all themes
5ee30a1b73 TTS: Convert strings to UTF-8
5e44796d6b TTS: Add correct language switching
53e0f21adc TTS: Reformat tts volume setting for GUI
6303f522e1 TTS: Refactor tts in configure
8357e8e6bf TTS: Prepare for windows TTS
d2d34a4eca TTS: Start implementing windows TTS
318c6d7ec6 TTS: Finish implementing the Windows TTS manager
33f8aadfeb TTS: Add age to TTSVoice
ce64528129 TTS: Make sure that TTS lang matches transMan lang
dae7d64d38 TTS: Add documentation to common/text-to-speech.h
62e219ece3 TTS: Implement default behavior of TTS setters
8e4a24f55e TTS: Unify setters between linux and win backends.
5d9f03e71d TTS: Add reference counting to TTSVoice
4d95720731 TTS: Fix voice setting on startup
5c1f562452 TTS: Implement conversion to UTF-8 in say on linux
d31ffb676a TTS: Implement conversion to unicode in say on win
7c78912931 TTS: Move popState to the base class
bb3346fba7 TTS: Update the TTS documentation
788b15652d TTS: Change String == "" to String.empty()
b5d5576f90 TTS: Add check to getVoice, fix typo.
3027acc12e TTS: Minor refactorisations
134d955006 TTS: Add iconv implementation of strToUtf8
33549d9c02 TTS: Add getVoiceIndicesByGender to the base class
324a90f4f6 TTS: Implement tts state switching when needed.
e04463b05a MORTEVIELLE: Add text to speech capability
e4363ba229 TTS: Minor Windows TTS refactoring
0631070833 TTS: Fix state switching on windows
d2b9b9ef21 MORTEVIELLE: Map characters to different voices
c9ec089e61 MORTEVIELLE: Remove unneeded TTS code
2ecbf9ac18 MORTEVIELLE: Return old code to waitSpeech
4ec10ffec7 TTS: Remove "static" from initTextToSpeech()
58065ceacd TTS: Refactoring
1795206289 TTS: Add checks to configure.
c7dbf192ef TTS: Remove unneeded code
99550a95b2 MORTEVILLE: Stop speech when pressing F8.
a1f69e6b06 MORTEVIELLE: Refactoring as suggested by Criezy
990ab61793 MORTEVIELLE: Improve voice mapping to characters.
e965df1e88 TTS: Add TTS support when compiling with msvc.
0434419b31 TTS: Implement TextToSpeechManager for macOS
5baa023ed0 TTS: Improve documentation
bac880816b TTS: Stop or pause speaking on word boundaries in macOS implementation
59631627c8 TTS: Fix reading of list widgets
f89ca9ad5c TTS: Add tooltip reading when they appear.
bbbb608c52 TTS: Implement OSD message reading
6703f88f7f TTS: Implement speech queueing on Linux and Win
a81b59a3c4 TTS: Don't read tooltips when TTS is speaking
1694b930e6 TTS: Make one say() method non-virtual
9ca2602e82 TTS: Implement speech queueing on macOS
7ec4f03a08 TTS: Make state switching faster on Linux
a5c9e8c74b TESTBET: Add TTS tests
1234f8e42f TTS: Fix pause() and resume() on linux
feaba6fff6 TTS: Reimplement isSpeaking on Windows.
98cea3e2ce TESTBED: Add state queries to TTS tests.
4bae32ffe7 TTS: Add *_NO_REPEAT actions
fb12e3b36b TESTBED: Rewrite parts of TTS tests.
84df34df10 TESTBED: Add tests for TTS *_NO_REPEAT actions
7613bcaa5f TTS: Use QUEUE_NO_REPEAT action for tooltips
21fb4cef06 TTS: Implement our own queuing for linux
5198459bba TESTBED: Move the TTS tests after the critical components
bd2757138b TESTBED: Make sure to process events while waiting for speech to finish
13a32fc5e9 TTS: Implement *_NO_REPEAT actions and Fix state synchronization issues on macOS
4036b73b6c TTS: Fix INTERRUPT_NO_REPEAT on Windows
bc10117980 TTS: Restart SPD if resume() fails.
38e769430a TTS: Improve documentation
f131cad3e5 TESTBED: Improve some TTS tests
01d843c527 TTS: Update documentation of INTERRUPT_NO_REPEAT
be5c865c50 TTS: Add proper speech queuing, update INT_NO_REP.
1a6ad384cb TESTBED: Increase pause in TTS pause test
b1bffaba86 TTS: Implement our own queuing on windows.
c2c4458253 JANITORIAL: Remove some trailing whitespaces
c861581adc TTS: Fix missing indentation
54c37f6f46 TTS: Document diferences in resume()
07acdb8433 TESTBED: Don't build TTS tests if TTS is disabled
c402666635 TTS: Refactoring
d49331132a TTS: Add summary of changes in sphelper-scummvm.h
b97333d4b7 TTS: Remove USE_PLATFORM_TTS defines
0d332e065e TTS: Rename LinuxTextToSpeechManager to SpeechDispatcherManager
4b5b812712 TTS: Better documentation of TTSVoice.
d399c37e6e WIN32: Use uint instead of unsigned int.
6baa9c8ddb TESTBED: Fix include guard in speech.h
55c399c7c0 TTS: Use Common::Encoding for encoding conversion.
291360a280 COMMON: Add CP850 conversion.
d4a98dcadb COMMON: Add CP437 encoding conversion table.
Commit: f78fc85f3a0c92245802098327425d21670d9479
https://github.com/scummvm/scummvm/commit/f78fc85f3a0c92245802098327425d21670d9479
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Create a TTS skeleton
Changed paths:
A backends/text-to-speech/linux/linux-text-to-speech.cpp
A backends/text-to-speech/linux/linux-text-to-speech.h
A common/text-to-speech.h
backends/module.mk
backends/platform/sdl/posix/posix.cpp
common/system.cpp
common/system.h
configure
diff --git a/backends/module.mk b/backends/module.mk
index ee12343..42c8404 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -344,5 +344,10 @@ MODULE_OBJS += \
saves/recorder/recorder-saves.o
endif
+ifdef USE_LINUX_TTS
+MODULE_OBJS += \
+ text-to-speech/linux/linux-text-to-speech.o
+endif
+
# Include common rules
include $(srcdir)/rules.mk
diff --git a/backends/platform/sdl/posix/posix.cpp b/backends/platform/sdl/posix/posix.cpp
index b204daa..50a1ccd 100644
--- a/backends/platform/sdl/posix/posix.cpp
+++ b/backends/platform/sdl/posix/posix.cpp
@@ -54,6 +54,10 @@
#ifdef HAS_POSIX_SPAWN
#include <spawn.h>
#endif
+
+#ifdef USE_LINUX_TTS
+#include "backends/text-to-speech/linux/linux-text-to-speech.h"
+#endif
extern char **environ;
OSystem_POSIX::OSystem_POSIX(Common::String baseConfigName)
@@ -79,6 +83,11 @@ void OSystem_POSIX::initBackend() {
if (_savefileManager == 0)
_savefileManager = new POSIXSaveFileManager();
+#ifdef USE_LINUX_TTS
+ // Initialize Text to Speech manager
+ _textToSpeechManager = new LinuxTextToSpeechManager();
+#endif
+
// Invoke parent implementation of this method
OSystem_SDL::initBackend();
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
new file mode 100644
index 0000000..7a8e899
--- /dev/null
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -0,0 +1,37 @@
+/* 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.
+ *
+ */
+
+// Disable symbol overrides so that we can use system headers.
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/text-to-speech/linux/linux-text-to-speech.h"
+
+#if defined(USE_LINUX_TTS)
+#include "common/translation.h"
+
+LinuxTextToSpeechManager::LinuxTextToSpeechManager() {
+}
+
+LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
+}
+
+#endif
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
new file mode 100644
index 0000000..ab9b2b6
--- /dev/null
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -0,0 +1,40 @@
+/* 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 BACKENDS_TEXT_TO_SPEECH_LINUX_H
+#define BACKENDS_TEXT_TO_SPEECH_LINUX_H
+
+#include "common/scummsys.h"
+
+#if defined(USE_LINUX_TTS)
+
+#include "common/text-to-speech.h"
+
+class LinuxTextToSpeechManager : public Common::TextToSpeechManager {
+public:
+ LinuxTextToSpeechManager();
+ virtual ~LinuxTextToSpeechManager();
+};
+
+#endif
+
+#endif // BACKENDS_UPDATES_LINUX_H
diff --git a/common/system.cpp b/common/system.cpp
index 9f6dc60..8d1ab84 100644
--- a/common/system.cpp
+++ b/common/system.cpp
@@ -31,6 +31,7 @@
#include "common/updates.h"
#include "common/dialogs.h"
#include "common/textconsole.h"
+#include "common/text-to-speech.h"
#include "backends/audiocd/default/default-audiocd.h"
#include "backends/fs/fs-factory.h"
@@ -49,6 +50,9 @@ OSystem::OSystem() {
#if defined(USE_UPDATES)
_updateManager = nullptr;
#endif
+#if defined(USE_TTS)
+ _textToSpeechManager = nullptr;
+#endif
#if defined(USE_SYSDIALOGS)
_dialogManager = nullptr;
#endif
@@ -76,6 +80,11 @@ OSystem::~OSystem() {
_updateManager = nullptr;
#endif
+#if defined(USE_TTS)
+ delete _textToSpeechManager;
+ _textToSpeechManager = 0;
+#endif
+
#if defined(USE_SYSDIALOGS)
delete _dialogManager;
_dialogManager = nullptr;
diff --git a/common/system.h b/common/system.h
index 77bdcd0..f17625e 100644
--- a/common/system.h
+++ b/common/system.h
@@ -49,6 +49,9 @@ class TaskbarManager;
#if defined(USE_UPDATES)
class UpdateManager;
#endif
+#if defined(USE_TTS)
+class TextToSpeechManager;
+#endif
#if defined(USE_SYSDIALOGS)
class DialogManager;
#endif
@@ -184,6 +187,15 @@ protected:
Common::UpdateManager *_updateManager;
#endif
+#if defined(USE_TTS)
+ /**
+ * No default value is provided for _textToSpeechManager by OSystem.
+ *
+ * @note _textToSpeechManager is deleted by the OSystem destructor.
+ */
+ Common::TextToSpeechManager *_textToSpeechManager;
+#endif
+
#if defined(USE_SYSDIALOGS)
/**
* No default value is provided for _dialogManager by OSystem.
@@ -1325,6 +1337,17 @@ public:
}
#endif
+#if defined(USE_TTS)
+ /**
+ * Returns the TextToSpeechManager, used to handle text to speech features.
+ *
+ * @return the TextToSpeechManager for the current architecture
+ */
+ virtual Common::TextToSpeechManager *getTextToSpeechManager() {
+ return _textToSpeechManager;
+ }
+#endif
+
#if defined(USE_SYSDIALOGS)
/**
* Returns the DialogManager, used to handle system dialogs.
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
new file mode 100644
index 0000000..e95050e
--- /dev/null
+++ b/common/text-to-speech.h
@@ -0,0 +1,45 @@
+/* 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 BACKENDS_TEXT_TO_SPEECH_ABSTRACT_H
+#define BACKENDS_TEXT_TO_SPEECH_ABSTRACT_H
+
+#if defined(USE_TTS)
+
+namespace Common {
+
+/**
+ * The TextToSpeechManager allows speech synthesis.
+ *
+ */
+class TextToSpeechManager {
+public:
+ TextToSpeechManager() {}
+ virtual ~TextToSpeechManager() {}
+
+};
+
+} // End of namespace Common
+
+#endif
+
+#endif // BACKENDS_TEXT_TO_SPEECH_ABSTRACT_H
diff --git a/configure b/configure
index dfbbdb7..8cfe584 100755
--- a/configure
+++ b/configure
@@ -166,6 +166,8 @@ _updates=no
_libunity=auto
_dialogs=auto
_iconv=auto
+_tts=auto
+_linux_tts=no
# Default option behavior yes/no
_debug_build=auto
_release_build=auto
@@ -1250,6 +1252,8 @@ for ac_option in $@; do
--disable-updates) _updates=no ;;
--enable-libunity) _libunity=yes ;;
--disable-libunity) _libunity=no ;;
+ --enable-tts) _tts=yes ;;
+ --disable-tts) _tts=no ;;
--enable-bink) _bink=yes ;;
--disable-bink) _bink=no ;;
--opengl-mode=*)
@@ -5357,6 +5361,26 @@ fi
define_in_config_if_yes $_dialogs 'USE_SYSDIALOGS'
#
+# Check whether to build TTS integration support
+#
+echo_n "Building text-to-speech integration support... "
+if test "$_tts" = "no"; then
+ echo "no"
+else
+ case $_host_os in
+ linux*)
+ echo "linux"
+ _tts=yes
+ _linux_tts=yes
+ ;;
+ *)
+ ;;
+ esac
+fi
+define_in_config_if_yes $_tts 'USE_TTS'
+define_in_config_if_yes $_linux_tts 'USE_LINUX_TTS'
+
+#
# Check whether to build Bink video support
#
echo_n "Building Bink video support... "
Commit: 39e74b027e372dcaed33ffa1b18f0de4abf82eb1
https://github.com/scummvm/scummvm/commit/39e74b027e372dcaed33ffa1b18f0de4abf82eb1
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add part of linux TTS
Changed paths:
A common/text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
common/module.mk
common/text-to-speech.h
configure
gui/widget.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 7a8e899..bbe6705 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -26,12 +26,177 @@
#include "backends/text-to-speech/linux/linux-text-to-speech.h"
#if defined(USE_LINUX_TTS)
+#include <speech-dispatcher/libspeechd.h>
+
#include "common/translation.h"
+#include "common/debug.h"
+#include "common/system.h"
+SPDConnection *_connection;
+
+void speech_begin_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
+ LinuxTextToSpeechManager *manager =
+ static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(LinuxTextToSpeechManager::SPEAKING);
+}
+
+void speech_end_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
+ LinuxTextToSpeechManager *manager =
+ static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(LinuxTextToSpeechManager::READY);
+}
+
+void speech_cancel_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
+ LinuxTextToSpeechManager *manager =
+ static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(LinuxTextToSpeechManager::READY);
+}
+
+void speech_resume_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
+ LinuxTextToSpeechManager *manager =
+ static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(LinuxTextToSpeechManager::SPEAKING);
+}
+
+void speech_pause_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
+ LinuxTextToSpeechManager *manager =
+ static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(LinuxTextToSpeechManager::PAUSED);
+}
+
+LinuxTextToSpeechManager::LinuxTextToSpeechManager()
+ : _speechState(READY) {
+ _connection = spd_open("ScummVM", "main", NULL, SPD_MODE_THREADED);
+ if (_connection == 0) {
+ debug("couldn't open");
+ return;
+ }
-LinuxTextToSpeechManager::LinuxTextToSpeechManager() {
+ _connection->callback_begin = speech_begin_callback;
+ spd_set_notification_on(_connection, SPD_BEGIN);
+ _connection->callback_end = speech_end_callback;
+ spd_set_notification_on(_connection, SPD_END);
+ _connection->callback_cancel = speech_cancel_callback;
+ spd_set_notification_on(_connection, SPD_CANCEL);
+ _connection->callback_resume = speech_resume_callback;
+ spd_set_notification_on(_connection, SPD_RESUME);
+ _connection->callback_pause = speech_pause_callback;
+ spd_set_notification_on(_connection, SPD_PAUSE);
+
+ setLanguage(Common::String("en"));
+ updateVoices();
}
LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
+ spd_close(_connection);
+}
+
+void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechState state) {
+ _speechState = state;
+}
+
+bool LinuxTextToSpeechManager::say(Common::String str) {
+ if (isSpeaking())
+ stop();
+ return spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1;
+
+}
+
+bool LinuxTextToSpeechManager::stop() {
+ if (_speechState == READY)
+ return false;
+ return spd_cancel(_connection) == -1;
+}
+
+bool LinuxTextToSpeechManager::pause() {
+ if (_speechState == READY || _speechState == PAUSED)
+ return false;
+ return spd_pause(_connection) == -1;
+}
+
+bool LinuxTextToSpeechManager::resume() {
+ if (_speechState == READY || _speechState == SPEAKING)
+ return false;
+ return spd_resume(_connection) == -1;
+}
+
+bool LinuxTextToSpeechManager::isSpeaking() {
+ if (_speechState == SPEAKING)
+ return true;
+ return false;
+}
+
+void LinuxTextToSpeechManager::setVoice(Common::TTSVoice *voice) {
+ assert(voice != nullptr && voice->getData() != nullptr);
+ spd_set_voice_type(_connection, *(SPDVoiceType *)(voice->getData()));
+ _ttsState->_activeVoice = voice;
+}
+
+void LinuxTextToSpeechManager::setRate(int rate) {
+ assert(rate >= -100 && rate <= 100);
+ spd_set_voice_rate(_connection, rate);
+ _ttsState->_rate = rate;
+}
+
+void LinuxTextToSpeechManager::setPitch(int pitch) {
+ assert(pitch >= -100 && pitch <= 100);
+ spd_set_voice_pitch(_connection, pitch);
+ _ttsState->_pitch = pitch;
+}
+
+void LinuxTextToSpeechManager::setVolume(int volume) {
+ assert(volume >= -100 && volume <= 100);
+ spd_set_volume(_connection, volume);
+ _ttsState->_volume = volume;
+}
+
+void LinuxTextToSpeechManager::setLanguage(Common::String language) {
+ spd_set_language(_connection, language.c_str());
+ _ttsState->_language = language;
+ if (_ttsState->_activeVoice)
+ setVoice(_ttsState->_activeVoice);
+}
+
+void LinuxTextToSpeechManager::updateVoices() {
+ /* just use these voices:
+ SPD_MALE1, SPD_MALE2, SPD_MALE3,
+ SPD_FEMALE1, SPD_FEMALE2, SPD_FEMALE3,
+ SPD_CHILD_MALE, SPD_CHILD_FEMALE
+
+ it depends on the user to map them to the right voices in speech-dispatcher
+ configuration
+ */
+
+ SPDVoiceType *type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
+ *type = SPD_MALE1;
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::MALE, (void *) type));
+
+ type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
+ *type = SPD_MALE2;
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::MALE, (void *) type));
+
+ type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
+ *type = SPD_MALE3;
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::MALE, (void *) type));
+
+ type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
+ *type = SPD_FEMALE1;
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::FEMALE, (void *) type));
+
+ type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
+ *type = SPD_FEMALE2;
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::FEMALE, (void *) type));
+
+ type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
+ *type = SPD_FEMALE3;
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::FEMALE, (void *) type));
+
+ type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
+ *type = SPD_CHILD_MALE;
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::MALE, (void *) type));
+
+ type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
+ *type = SPD_CHILD_FEMALE;
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::FEMALE, (void *) type));
}
#endif
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index ab9b2b6..b0f71d3 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -28,11 +28,42 @@
#if defined(USE_LINUX_TTS)
#include "common/text-to-speech.h"
+#include "common/str.h"
class LinuxTextToSpeechManager : public Common::TextToSpeechManager {
public:
+ enum SpeechState {
+ READY,
+ PAUSED,
+ SPEAKING
+ };
+
LinuxTextToSpeechManager();
virtual ~LinuxTextToSpeechManager();
+
+ virtual bool say(Common::String str);
+
+ virtual bool stop();
+ virtual bool pause();
+ virtual bool resume();
+
+ virtual bool isSpeaking();
+
+ virtual void setVoice(Common::TTSVoice *voice);
+
+ virtual void setRate(int rate);
+
+ virtual void setPitch(int pitch);
+
+ virtual void setVolume(int volume);
+
+ virtual void setLanguage(Common::String language);
+
+ void updateState(SpeechState state);
+
+private:
+ virtual void updateVoices();
+ SpeechState _speechState;
};
#endif
diff --git a/common/module.mk b/common/module.mk
index 46e32d7..56ed05f 100644
--- a/common/module.mk
+++ b/common/module.mk
@@ -99,5 +99,10 @@ MODULE_OBJS += \
lua/scummvm_file.o
endif
+ifdef USE_TTS
+MODULE_OBJS += \
+ text-to-speech.o
+endif
+
# Include common rules
include $(srcdir)/rules.mk
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
new file mode 100644
index 0000000..d1b9539
--- /dev/null
+++ b/common/text-to-speech.cpp
@@ -0,0 +1,47 @@
+/* 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 "common/text-to-speech.h"
+#if defined(USE_TTS)
+
+namespace Common {
+TextToSpeechManager::TextToSpeechManager() {
+ _ttsState = new TTSState;
+ _ttsState->_pitch = 0;
+ _ttsState->_volume = 0;
+ _ttsState->_rate = 0;
+ _ttsState->_activeVoice = nullptr;
+ _ttsState->_next = nullptr;
+}
+
+TextToSpeechManager::~TextToSpeechManager() {
+ TTSState *tmp = _ttsState;
+ while (tmp != nullptr) {
+ tmp = _ttsState->_next;
+ delete _ttsState;
+ _ttsState = tmp;
+ }
+}
+
+}
+#endif
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index e95050e..62cc5e8 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -23,19 +23,90 @@
#ifndef BACKENDS_TEXT_TO_SPEECH_ABSTRACT_H
#define BACKENDS_TEXT_TO_SPEECH_ABSTRACT_H
+#include "common/scummsys.h"
+
#if defined(USE_TTS)
+#include "common/array.h"
+#include "common/debug.h"
namespace Common {
+class TTSVoice {
+ friend class TextToSpeechManager;
+
+ public:
+ enum Gender {
+ MALE,
+ FEMALE,
+ UNKNOWN
+ };
+
+ public:
+ TTSVoice()
+ : _gender(UNKNOWN)
+ , _data(nullptr) {}
+ TTSVoice(Gender gender, void *data)
+ : _gender(gender)
+ , _data(data) {}
+ ~TTSVoice() { debug("%d", * (int *)_data); if (_data != nullptr) free(_data); }
+ Gender getGender() { return _gender; };
+ void setGender(Gender gender) { _gender = gender; };
+ void setData(void *data) { _data = data; };
+ void *getData() { return _data; };
+
+ protected:
+ Gender _gender;
+ void *_data;
+};
+
+struct TTSState {
+ int _rate;
+ int _pitch;
+ int _volume;
+ String _language;
+ TTSVoice *_activeVoice;
+ Array<TTSVoice> _availaibleVoices;
+ TTSState *_next;
+};
+
/**
* The TextToSpeechManager allows speech synthesis.
*
*/
class TextToSpeechManager {
public:
- TextToSpeechManager() {}
- virtual ~TextToSpeechManager() {}
+ TextToSpeechManager();
+ virtual ~TextToSpeechManager();
+
+ virtual bool say(String str) { return false; }
+
+ virtual bool stop() { return false; }
+ virtual bool pause() { return false; }
+ virtual bool resume() { return false; }
+
+ virtual bool isSpeaking() { return false; }
+
+ virtual void setVoice(TTSVoice *voice) {}
+ TTSVoice getVoice() { return *(_ttsState->_activeVoice); }
+
+ virtual void setRate(int rate) {}
+ int getRate() { return _ttsState->_rate; }
+
+ virtual void setPitch(int pitch) {}
+ int getPitch() { return _ttsState->_pitch; }
+
+ virtual void setVolume(int volume) {}
+ int getVolume() { return _ttsState->_volume; }
+
+ virtual void setLanguage(String language) {}
+ String getLanguage() { return _ttsState->_language; }
+
+ Array<TTSVoice> getVoicesArray() { return _ttsState->_availaibleVoices; }
+
+protected:
+ TTSState *_ttsState;
+ virtual void updateVoices() {};
};
} // End of namespace Common
diff --git a/configure b/configure
index 8cfe584..af65184 100755
--- a/configure
+++ b/configure
@@ -5379,6 +5379,7 @@ else
fi
define_in_config_if_yes $_tts 'USE_TTS'
define_in_config_if_yes $_linux_tts 'USE_LINUX_TTS'
+append_var LIBS '-lspeechd'
#
# Check whether to build Bink video support
diff --git a/gui/widget.h b/gui/widget.h
index f87816b..bcab3b4 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -31,6 +31,8 @@
#include "graphics/surface.h"
#include "gui/object.h"
#include "gui/ThemeEngine.h"
+#include "common/text-to-speech.h"
+#include "common/system.h"
namespace GUI {
@@ -218,7 +220,7 @@ public:
void handleMouseUp(int x, int y, int button, int clickCount);
void handleMouseDown(int x, int y, int button, int clickCount);
- void handleMouseEntered(int button) { if (_duringPress) { setFlags(WIDGET_PRESSED); } else { setFlags(WIDGET_HILITED); } markAsDirty(); }
+ void handleMouseEntered(int button) { g_system->getTextToSpeechManager()->say(_label); if (_duringPress) { setFlags(WIDGET_PRESSED); } else { setFlags(WIDGET_HILITED); } markAsDirty(); }
void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED | WIDGET_PRESSED); markAsDirty(); }
void setHighLighted(bool enable);
Commit: 8c44b032314475376e039853392292c578464091
https://github.com/scummvm/scummvm/commit/8c44b032314475376e039853392292c578464091
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Resolve segfault when exiting scummvm
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
common/text-to-speech.cpp
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index bbe6705..5df8f80 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -87,7 +87,7 @@ LinuxTextToSpeechManager::LinuxTextToSpeechManager()
}
LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
- spd_close(_connection);
+ //spd_close(_connection);
}
void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechState state) {
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
index d1b9539..1e2456d 100644
--- a/common/text-to-speech.cpp
+++ b/common/text-to-speech.cpp
@@ -38,6 +38,9 @@ TextToSpeechManager::~TextToSpeechManager() {
TTSState *tmp = _ttsState;
while (tmp != nullptr) {
tmp = _ttsState->_next;
+ for (TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
+ free(i->_data);
+ }
delete _ttsState;
_ttsState = tmp;
}
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 62cc5e8..8d4b0a0 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -48,7 +48,6 @@ class TTSVoice {
TTSVoice(Gender gender, void *data)
: _gender(gender)
, _data(data) {}
- ~TTSVoice() { debug("%d", * (int *)_data); if (_data != nullptr) free(_data); }
Gender getGender() { return _gender; };
void setGender(Gender gender) { _gender = gender; };
void setData(void *data) { _data = data; };
Commit: 7f895d21df5ddb7c82a2f685d3a9fcc09b3d3a7a
https://github.com/scummvm/scummvm/commit/7f895d21df5ddb7c82a2f685d3a9fcc09b3d3a7a
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add checks for correct TTS engine state.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 5df8f80..2586b16 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -67,7 +67,7 @@ LinuxTextToSpeechManager::LinuxTextToSpeechManager()
: _speechState(READY) {
_connection = spd_open("ScummVM", "main", NULL, SPD_MODE_THREADED);
if (_connection == 0) {
- debug("couldn't open");
+ _speechState = BROKEN;
return;
}
@@ -82,12 +82,13 @@ LinuxTextToSpeechManager::LinuxTextToSpeechManager()
_connection->callback_pause = speech_pause_callback;
spd_set_notification_on(_connection, SPD_PAUSE);
- setLanguage(Common::String("en"));
+ setLanguage(Common::String("cs"));
updateVoices();
}
LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
- //spd_close(_connection);
+ if (_connection != 0)
+ spd_close(_connection);
}
void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechState state) {
@@ -95,6 +96,8 @@ void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechState
}
bool LinuxTextToSpeechManager::say(Common::String str) {
+ if (_speechState == BROKEN)
+ return true;
if (isSpeaking())
stop();
return spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1;
@@ -102,54 +105,70 @@ bool LinuxTextToSpeechManager::say(Common::String str) {
}
bool LinuxTextToSpeechManager::stop() {
- if (_speechState == READY)
- return false;
+ if (_speechState == READY || _speechState == BROKEN)
+ return true;
return spd_cancel(_connection) == -1;
}
bool LinuxTextToSpeechManager::pause() {
- if (_speechState == READY || _speechState == PAUSED)
- return false;
+ if (_speechState == READY || _speechState == PAUSED || _speechState == BROKEN)
+ return true;
return spd_pause(_connection) == -1;
}
bool LinuxTextToSpeechManager::resume() {
- if (_speechState == READY || _speechState == SPEAKING)
- return false;
+ if (_speechState == READY || _speechState == SPEAKING || _speechState == BROKEN)
+ return true;
return spd_resume(_connection) == -1;
}
bool LinuxTextToSpeechManager::isSpeaking() {
- if (_speechState == SPEAKING)
- return true;
- return false;
+ return _speechState == SPEAKING;
+}
+
+bool LinuxTextToSpeechManager::isPaused() {
+ return _speechState == PAUSED;
+}
+
+bool LinuxTextToSpeechManager::isReady() {
+ return _speechState == READY;
}
void LinuxTextToSpeechManager::setVoice(Common::TTSVoice *voice) {
+ if (_speechState == BROKEN)
+ return;
assert(voice != nullptr && voice->getData() != nullptr);
spd_set_voice_type(_connection, *(SPDVoiceType *)(voice->getData()));
_ttsState->_activeVoice = voice;
}
void LinuxTextToSpeechManager::setRate(int rate) {
+ if (_speechState == BROKEN)
+ return;
assert(rate >= -100 && rate <= 100);
spd_set_voice_rate(_connection, rate);
_ttsState->_rate = rate;
}
void LinuxTextToSpeechManager::setPitch(int pitch) {
+ if (_speechState == BROKEN)
+ return;
assert(pitch >= -100 && pitch <= 100);
spd_set_voice_pitch(_connection, pitch);
_ttsState->_pitch = pitch;
}
void LinuxTextToSpeechManager::setVolume(int volume) {
+ if (_speechState == BROKEN)
+ return;
assert(volume >= -100 && volume <= 100);
spd_set_volume(_connection, volume);
_ttsState->_volume = volume;
}
void LinuxTextToSpeechManager::setLanguage(Common::String language) {
+ if (_speechState == BROKEN)
+ return;
spd_set_language(_connection, language.c_str());
_ttsState->_language = language;
if (_ttsState->_activeVoice)
@@ -157,6 +176,8 @@ void LinuxTextToSpeechManager::setLanguage(Common::String language) {
}
void LinuxTextToSpeechManager::updateVoices() {
+ if (_speechState == BROKEN)
+ return;
/* just use these voices:
SPD_MALE1, SPD_MALE2, SPD_MALE3,
SPD_FEMALE1, SPD_FEMALE2, SPD_FEMALE3,
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index b0f71d3..bb32979 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -35,7 +35,8 @@ public:
enum SpeechState {
READY,
PAUSED,
- SPEAKING
+ SPEAKING,
+ BROKEN
};
LinuxTextToSpeechManager();
@@ -48,6 +49,8 @@ public:
virtual bool resume();
virtual bool isSpeaking();
+ virtual bool isPaused();
+ virtual bool isReady();
virtual void setVoice(Common::TTSVoice *voice);
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 8d4b0a0..4bb8111 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -84,6 +84,8 @@ public:
virtual bool resume() { return false; }
virtual bool isSpeaking() { return false; }
+ virtual bool isPaused() { return false; }
+ virtual bool isReady() { return false; }
virtual void setVoice(TTSVoice *voice) {}
TTSVoice getVoice() { return *(_ttsState->_activeVoice); }
Commit: 62a011e28a4a7e253ad07319cef760566cba59cb
https://github.com/scummvm/scummvm/commit/62a011e28a4a7e253ad07319cef760566cba59cb
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add ttsStatus push and pop functions
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
common/text-to-speech.cpp
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 2586b16..02ca5a8 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -82,8 +82,9 @@ LinuxTextToSpeechManager::LinuxTextToSpeechManager()
_connection->callback_pause = speech_pause_callback;
spd_set_notification_on(_connection, SPD_PAUSE);
- setLanguage(Common::String("cs"));
updateVoices();
+ _ttsState->_activeVoice = 0;
+ setLanguage(Common::String("en"));
}
LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
@@ -134,12 +135,14 @@ bool LinuxTextToSpeechManager::isReady() {
return _speechState == READY;
}
-void LinuxTextToSpeechManager::setVoice(Common::TTSVoice *voice) {
+void LinuxTextToSpeechManager::setVoice(unsigned index) {
if (_speechState == BROKEN)
return;
- assert(voice != nullptr && voice->getData() != nullptr);
- spd_set_voice_type(_connection, *(SPDVoiceType *)(voice->getData()));
- _ttsState->_activeVoice = voice;
+ debug("%d < %d", index, _ttsState->_availaibleVoices.size());
+ assert(index < _ttsState->_availaibleVoices.size());
+ Common::TTSVoice voice = _ttsState->_availaibleVoices[index];
+ spd_set_voice_type(_connection, *(SPDVoiceType *)(voice.getData()));
+ _ttsState->_activeVoice = index;
}
void LinuxTextToSpeechManager::setRate(int rate) {
@@ -171,8 +174,7 @@ void LinuxTextToSpeechManager::setLanguage(Common::String language) {
return;
spd_set_language(_connection, language.c_str());
_ttsState->_language = language;
- if (_ttsState->_activeVoice)
- setVoice(_ttsState->_activeVoice);
+ setVoice(_ttsState->_activeVoice);
}
void LinuxTextToSpeechManager::updateVoices() {
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index bb32979..85f53eb 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -52,7 +52,7 @@ public:
virtual bool isPaused();
virtual bool isReady();
- virtual void setVoice(Common::TTSVoice *voice);
+ virtual void setVoice(unsigned index);
virtual void setRate(int rate);
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
index 1e2456d..459d470 100644
--- a/common/text-to-speech.cpp
+++ b/common/text-to-speech.cpp
@@ -30,7 +30,8 @@ TextToSpeechManager::TextToSpeechManager() {
_ttsState->_pitch = 0;
_ttsState->_volume = 0;
_ttsState->_rate = 0;
- _ttsState->_activeVoice = nullptr;
+ _ttsState->_activeVoice = 0;
+ _ttsState->_language = "en";
_ttsState->_next = nullptr;
}
@@ -46,5 +47,37 @@ TextToSpeechManager::~TextToSpeechManager() {
}
}
+void TextToSpeechManager::pushState() {
+ TTSState *newState = new TTSState;
+ newState->_pitch = _ttsState->_pitch;
+ newState->_volume = _ttsState->_volume;
+ newState->_rate = _ttsState->_rate;
+ newState->_activeVoice = _ttsState->_activeVoice;
+ newState->_language = _ttsState->_language;
+ newState->_next = _ttsState;
+ _ttsState = newState;
+ updateVoices();
+}
+
+bool TextToSpeechManager::popState() {
+ if (_ttsState->_next == nullptr)
+ return true;
+
+ for (TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
+ free(i->_data);
+ }
+
+ TTSState *oldState = _ttsState;
+ _ttsState = _ttsState->_next;
+
+ delete oldState;
+
+ setLanguage(_ttsState->_language);
+ setPitch(_ttsState->_pitch);
+ setVolume(_ttsState->_volume);
+ setRate(_ttsState->_rate);
+ return false;
+}
+
}
#endif
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 4bb8111..2c9b5c7 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -63,7 +63,7 @@ struct TTSState {
int _pitch;
int _volume;
String _language;
- TTSVoice *_activeVoice;
+ int _activeVoice;
Array<TTSVoice> _availaibleVoices;
TTSState *_next;
};
@@ -87,8 +87,8 @@ public:
virtual bool isPaused() { return false; }
virtual bool isReady() { return false; }
- virtual void setVoice(TTSVoice *voice) {}
- TTSVoice getVoice() { return *(_ttsState->_activeVoice); }
+ virtual void setVoice(unsigned index) {}
+ TTSVoice getVoice() { return _ttsState->_availaibleVoices[_ttsState->_activeVoice]; }
virtual void setRate(int rate) {}
int getRate() { return _ttsState->_rate; }
@@ -104,6 +104,9 @@ public:
Array<TTSVoice> getVoicesArray() { return _ttsState->_availaibleVoices; }
+ void pushState();
+ bool popState();
+
protected:
TTSState *_ttsState;
Commit: fa6faca76a35028e288753c3d74f2c81228ec5bb
https://github.com/scummvm/scummvm/commit/fa6faca76a35028e288753c3d74f2c81228ec5bb
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add TTS checkbox to Options
Probably works only in the builtin theme right now.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
gui/options.cpp
gui/options.h
gui/themes/default.inc
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 02ca5a8..7ddd4ac 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -138,7 +138,6 @@ bool LinuxTextToSpeechManager::isReady() {
void LinuxTextToSpeechManager::setVoice(unsigned index) {
if (_speechState == BROKEN)
return;
- debug("%d < %d", index, _ttsState->_availaibleVoices.size());
assert(index < _ttsState->_availaibleVoices.size());
Common::TTSVoice voice = _ttsState->_availaibleVoices[index];
spd_set_voice_type(_connection, *(SPDVoiceType *)(voice.getData()));
diff --git a/gui/options.cpp b/gui/options.cpp
index 9243862..ebfa400 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -425,6 +425,7 @@ void OptionsDialog::build() {
_subSpeedSlider->setValue(speed);
_subSpeedLabel->setValue(speed);
}
+
}
void OptionsDialog::clean() {
@@ -1523,6 +1524,10 @@ GlobalOptionsDialog::GlobalOptionsDialog(LauncherDialog *launcher)
_serverWasRunning = false;
#endif
#endif
+#ifdef USE_TTS
+ _enableTTS = false;
+ _ttsCheckbox = 0;
+#endif
}
GlobalOptionsDialog::~GlobalOptionsDialog() {
@@ -1779,6 +1784,21 @@ void GlobalOptionsDialog::build() {
#endif // USE_SDL_NET
#endif // USE_CLOUD
+ //Accessibility
+#ifdef USE_TTS
+ if (g_system->getOverlayWidth() > 320)
+ tab->addTab(_("Accessibility"));
+ else
+ tab->addTab(_c("Accessibility", "lowres"));
+ _ttsCheckbox = new CheckboxWidget(tab, "GlobalOptions_Accessibility.TTSCheckbox",
+ _("Use Text to speech"), _("Will read text in gui on mouse over."));
+ if (ConfMan.hasKey("ttsEnabled"))
+ _ttsCheckbox->setState(ConfMan.getBool("ttsEnabled", _domain));
+ else
+ _ttsCheckbox->setState(false);
+
+#endif // USE_TTS
+
// Activate the first tab
tab->setActiveTab(0);
_tabWidget = tab;
@@ -2114,6 +2134,9 @@ void GlobalOptionsDialog::apply() {
MessageDialog error(errorMessage);
error.runModal();
}
+#ifdef USE_TTS
+ ConfMan.setBool("ttsEnabled", _ttsCheckbox->getState(), _domain);
+#endif
if (isRebuildNeeded) {
rebuild();
diff --git a/gui/options.h b/gui/options.h
index 04b5a56..5f7a6b3 100644
--- a/gui/options.h
+++ b/gui/options.h
@@ -223,6 +223,7 @@ private:
CheckboxWidget *_muteCheckbox;
+
protected:
//
// Game GUI options
@@ -354,6 +355,13 @@ protected:
#endif // USE_SDL_NET
#endif // USE_CLOUD
+ //
+ // Accessibility controls
+ //
+#ifdef USE_TTS
+ bool _enableTTS;
+ CheckboxWidget *_ttsCheckbox;
+#endif
};
} // End of namespace GUI
diff --git a/gui/themes/default.inc b/gui/themes/default.inc
index 49f5e05..603567d 100644
--- a/gui/themes/default.inc
+++ b/gui/themes/default.inc
@@ -1553,6 +1553,13 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"/>"
"</layout>"
"</dialog>"
+"<dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='16'>"
+"<widget name='TTSCheckbox' "
+"type='Checkbox' "
+"/>"
+"</layout>"
+"</dialog>"
"<dialog name='GlobalMenu' overlays='screen_center'>"
"<layout type='vertical' padding='16,16,16,16' center='true'>"
"<widget name='Title' "
Commit: b5cebcbeaed5b1b860f2686379fc288c137a4c2f
https://github.com/scummvm/scummvm/commit/b5cebcbeaed5b1b860f2686379fc288c137a4c2f
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add text to speech to the GUI.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
common/text-to-speech.h
gui/dialog.h
gui/options.cpp
gui/widget.cpp
gui/widget.h
gui/widgets/list.cpp
gui/widgets/list.h
gui/widgets/popup.cpp
gui/widgets/popup.h
gui/widgets/tab.cpp
gui/widgets/tab.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 7ddd4ac..47c441a 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -160,14 +160,18 @@ void LinuxTextToSpeechManager::setPitch(int pitch) {
_ttsState->_pitch = pitch;
}
-void LinuxTextToSpeechManager::setVolume(int volume) {
+void LinuxTextToSpeechManager::setVolume(unsigned volume) {
if (_speechState == BROKEN)
return;
- assert(volume >= -100 && volume <= 100);
- spd_set_volume(_connection, volume);
+ assert(volume <= 100);
+ spd_set_volume(_connection, (volume - 50) * 2);
_ttsState->_volume = volume;
}
+int LinuxTextToSpeechManager::getVolume() {
+ return (_ttsState->_volume - 50) * 2;
+}
+
void LinuxTextToSpeechManager::setLanguage(Common::String language) {
if (_speechState == BROKEN)
return;
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 85f53eb..faa6a9d 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -58,7 +58,8 @@ public:
virtual void setPitch(int pitch);
- virtual void setVolume(int volume);
+ virtual void setVolume(unsigned volume);
+ virtual int getVolume();
virtual void setLanguage(Common::String language);
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 2c9b5c7..8d0021e 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -96,8 +96,8 @@ public:
virtual void setPitch(int pitch) {}
int getPitch() { return _ttsState->_pitch; }
- virtual void setVolume(int volume) {}
- int getVolume() { return _ttsState->_volume; }
+ virtual void setVolume(unsigned volume) {}
+ virtual int getVolume() { return _ttsState->_volume; }
virtual void setLanguage(String language) {}
String getLanguage() { return _ttsState->_language; }
diff --git a/gui/dialog.h b/gui/dialog.h
index 70b1791..1089434 100644
--- a/gui/dialog.h
+++ b/gui/dialog.h
@@ -102,6 +102,7 @@ protected:
virtual void handleKeyDown(Common::KeyState state);
virtual void handleKeyUp(Common::KeyState state);
virtual void handleMouseMoved(int x, int y, int button);
+ virtual void handleMouseLeft(int button) {};
virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
virtual void handleOtherEvent(Common::Event evt);
diff --git a/gui/options.cpp b/gui/options.cpp
index ebfa400..1d4f6c7 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -1792,8 +1792,8 @@ void GlobalOptionsDialog::build() {
tab->addTab(_c("Accessibility", "lowres"));
_ttsCheckbox = new CheckboxWidget(tab, "GlobalOptions_Accessibility.TTSCheckbox",
_("Use Text to speech"), _("Will read text in gui on mouse over."));
- if (ConfMan.hasKey("ttsEnabled"))
- _ttsCheckbox->setState(ConfMan.getBool("ttsEnabled", _domain));
+ if (ConfMan.hasKey("tts_enabled"))
+ _ttsCheckbox->setState(ConfMan.getBool("tts_enabled", _domain));
else
_ttsCheckbox->setState(false);
@@ -2135,7 +2135,7 @@ void GlobalOptionsDialog::apply() {
error.runModal();
}
#ifdef USE_TTS
- ConfMan.setBool("ttsEnabled", _ttsCheckbox->getState(), _domain);
+ ConfMan.setBool("tts_enabled", _ttsCheckbox->getState(), _domain);
#endif
if (isRebuildNeeded) {
diff --git a/gui/widget.cpp b/gui/widget.cpp
index 2ac73dd..d02d64f 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -256,6 +256,19 @@ Common::String Widget::cleanupHotkey(const Common::String &label) {
return res;
}
+void Widget::read(Common::String str) {
+#ifdef USE_TTS
+ if (ConfMan.hasKey("tts_enabled", "scummvm") &&
+ ConfMan.getBool("tts_enabled", "scummvm")) {
+ int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
+ if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm"))
+ volume = 0;
+ g_system->getTextToSpeechManager()->setVolume(volume);
+ g_system->getTextToSpeechManager()->say(str);
+ }
+#endif
+}
+
#pragma mark -
StaticTextWidget::StaticTextWidget(GuiObject *boss, int x, int y, int w, int h, const Common::String &text, Graphics::TextAlign align, const char *tooltip, ThemeEngine::FontStyle font)
diff --git a/gui/widget.h b/gui/widget.h
index bcab3b4..06df496 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -33,6 +33,7 @@
#include "gui/ThemeEngine.h"
#include "common/text-to-speech.h"
#include "common/system.h"
+#include "common/config-manager.h"
namespace GUI {
@@ -168,6 +169,8 @@ public:
virtual bool containsWidget(Widget *) const { return false; }
+ void read(Common::String str);
+
protected:
void updateState(int oldFlags, int newFlags);
@@ -195,9 +198,11 @@ public:
StaticTextWidget(GuiObject *boss, const Common::String &name, const Common::String &text, const char *tooltip = 0, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleBold);
void setValue(int value);
void setLabel(const Common::String &label);
+ void handleMouseEntered(int button) { readLabel(); }
const Common::String &getLabel() const { return _label; }
void setAlign(Graphics::TextAlign align);
Graphics::TextAlign getAlign() const { return _align; }
+ void readLabel() { read(_label); }
protected:
void drawWidget();
@@ -220,7 +225,7 @@ public:
void handleMouseUp(int x, int y, int button, int clickCount);
void handleMouseDown(int x, int y, int button, int clickCount);
- void handleMouseEntered(int button) { g_system->getTextToSpeechManager()->say(_label); if (_duringPress) { setFlags(WIDGET_PRESSED); } else { setFlags(WIDGET_HILITED); } markAsDirty(); }
+ void handleMouseEntered(int button) { readLabel(); if (_duringPress) { setFlags(WIDGET_PRESSED); } else { setFlags(WIDGET_HILITED); } markAsDirty(); }
void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED | WIDGET_PRESSED); markAsDirty(); }
void setHighLighted(bool enable);
@@ -265,7 +270,7 @@ public:
CheckboxWidget(GuiObject *boss, const Common::String &name, const Common::String &label, const char *tooltip = 0, uint32 cmd = 0, uint8 hotkey = 0);
void handleMouseUp(int x, int y, int button, int clickCount);
- virtual void handleMouseEntered(int button) { setFlags(WIDGET_HILITED); markAsDirty(); }
+ virtual void handleMouseEntered(int button) { readLabel(); setFlags(WIDGET_HILITED); markAsDirty(); }
virtual void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED); markAsDirty(); }
void setState(bool state);
@@ -311,7 +316,7 @@ public:
RadiobuttonWidget(GuiObject *boss, const Common::String &name, RadiobuttonGroup *group, int value, const Common::String &label, const char *tooltip = 0, uint8 hotkey = 0);
void handleMouseUp(int x, int y, int button, int clickCount);
- virtual void handleMouseEntered(int button) { setFlags(WIDGET_HILITED); markAsDirty(); }
+ virtual void handleMouseEntered(int button) { readLabel(); setFlags(WIDGET_HILITED); markAsDirty(); }
virtual void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED); markAsDirty(); }
void setState(bool state, bool setGroup = true);
diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp
index 74239f8..26edd0a 100644
--- a/gui/widgets/list.cpp
+++ b/gui/widgets/list.cpp
@@ -44,7 +44,7 @@ ListWidget::ListWidget(Dialog *boss, const String &name, const char *tooltip, ui
_scrollBar = new ScrollBarWidget(this, _w - _scrollBarWidth, 0, _scrollBarWidth, _h);
_scrollBar->setTarget(this);
- setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+ setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE | WIDGET_TRACK_MOUSE);
_type = kListWidget;
_editMode = false;
_numberingMode = kListNumberingOne;
@@ -93,6 +93,8 @@ ListWidget::ListWidget(Dialog *boss, int x, int y, int w, int h, const char *too
_quickSelect = true;
_editColor = ThemeEngine::kFontColorNormal;
+
+ _lastRead = -1;
}
bool ListWidget::containsWidget(Widget *w) const {
@@ -262,6 +264,27 @@ void ListWidget::handleMouseWheel(int x, int y, int direction) {
_scrollBar->handleMouseWheel(x, y, direction);
}
+void ListWidget::handleMouseMoved(int x, int y, int button) {
+ if (!isEnabled())
+ return;
+
+ // First check whether the selection changed
+ int item = findItem(x, y);
+
+ if (item != -1) {
+ if(_lastRead != item) {
+ read(_dataList[item]);
+ _lastRead = item;
+ }
+ }
+ else
+ _lastRead = -1;
+}
+
+void ListWidget::handleMouseLeft(int button) {
+ _lastRead = -1;
+}
+
int ListWidget::findItem(int x, int y) const {
if (y < _topPadding) return -1;
diff --git a/gui/widgets/list.h b/gui/widgets/list.h
index 57e677e..eeaad30 100644
--- a/gui/widgets/list.h
+++ b/gui/widgets/list.h
@@ -84,6 +84,8 @@ protected:
ThemeEngine::FontColor _editColor;
+ int _lastRead;
+
public:
ListWidget(Dialog *boss, const String &name, const char *tooltip = 0, uint32 cmd = 0);
ListWidget(Dialog *boss, int x, int y, int w, int h, const char *tooltip = 0, uint32 cmd = 0);
@@ -125,6 +127,8 @@ public:
virtual void handleMouseDown(int x, int y, int button, int clickCount);
virtual void handleMouseUp(int x, int y, int button, int clickCount);
virtual void handleMouseWheel(int x, int y, int direction);
+ virtual void handleMouseMoved(int x, int y, int button);
+ virtual void handleMouseLeft(int button);
virtual bool handleKeyDown(Common::KeyState state);
virtual bool handleKeyUp(Common::KeyState state);
virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
diff --git a/gui/widgets/popup.cpp b/gui/widgets/popup.cpp
index a36a652..fe51b39 100644
--- a/gui/widgets/popup.cpp
+++ b/gui/widgets/popup.cpp
@@ -45,6 +45,8 @@ protected:
int _leftPadding;
int _rightPadding;
+ int _lastRead;
+
public:
PopUpDialog(PopUpWidget *boss, int clickX, int clickY);
@@ -53,6 +55,7 @@ public:
void handleMouseUp(int x, int y, int button, int clickCount) override;
void handleMouseWheel(int x, int y, int direction) override; // Scroll through entries with scroll wheel
void handleMouseMoved(int x, int y, int button) override; // Redraw selections depending on mouse position
+ void handleMouseLeft(int button) override;
void handleKeyDown(Common::KeyState state) override; // Scroll through entries with arrow keys etc.
protected:
@@ -64,6 +67,7 @@ protected:
void moveUp();
void moveDown();
+ void read(Common::String);
};
PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY)
@@ -145,6 +149,8 @@ PopUpDialog::PopUpDialog(PopUpWidget *boss, int clickX, int clickY)
// Remember original mouse position
_clickX = clickX - _x;
_clickY = clickY - _y;
+
+ _lastRead = -1;
}
void PopUpDialog::drawDialog(DrawLayer layerToDraw) {
@@ -207,6 +213,27 @@ void PopUpDialog::handleMouseMoved(int x, int y, int button) {
// ...and update the selection accordingly
setSelection(item);
+ if (_lastRead != item) {
+ read(_popUpBoss->_entries[item].name);
+ _lastRead = item;
+ }
+}
+
+void PopUpDialog::handleMouseLeft(int button) {
+ _lastRead = -1;
+}
+
+void PopUpDialog::read(Common::String str) {
+#ifdef USE_TTS
+ if (ConfMan.hasKey("tts_enabled", "scummvm") &&
+ ConfMan.getBool("tts_enabled", "scummvm")) {
+ int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
+ if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm"))
+ volume = 0;
+ g_system->getTextToSpeechManager()->setVolume(volume);
+ g_system->getTextToSpeechManager()->say(str);
+ }
+#endif
}
void PopUpDialog::handleKeyDown(Common::KeyState state) {
diff --git a/gui/widgets/popup.h b/gui/widgets/popup.h
index d2b1f1c..a898479 100644
--- a/gui/widgets/popup.h
+++ b/gui/widgets/popup.h
@@ -77,7 +77,7 @@ public:
uint32 getSelectedTag() const { return (_selectedItem >= 0) ? _entries[_selectedItem].tag : (uint32)-1; }
// const String& getSelectedString() const { return (_selectedItem >= 0) ? _entries[_selectedItem].name : String::emptyString; }
- void handleMouseEntered(int button) { setFlags(WIDGET_HILITED); markAsDirty(); }
+ void handleMouseEntered(int button) { read(_entries[_selectedItem].name); setFlags(WIDGET_HILITED); markAsDirty(); }
void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED); markAsDirty(); }
virtual void reflowLayout();
diff --git a/gui/widgets/tab.cpp b/gui/widgets/tab.cpp
index d34214b..103f572 100644
--- a/gui/widgets/tab.cpp
+++ b/gui/widgets/tab.cpp
@@ -46,7 +46,7 @@ TabWidget::TabWidget(GuiObject *boss, const String &name)
}
void TabWidget::init() {
- setFlags(WIDGET_ENABLED);
+ setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE);
_type = kTabWidget;
_activeTab = -1;
_firstVisibleTab = 0;
@@ -71,6 +71,7 @@ void TabWidget::init() {
int y = _butTP - _tabHeight;
_navLeft = new ButtonWidget(this, x, y, _butW, _butH, "<", 0, kCmdLeft);
_navRight = new ButtonWidget(this, x + _butW + 2, y, _butW, _butH, ">", 0, kCmdRight);
+ _lastRead = -1;
}
TabWidget::~TabWidget() {
@@ -216,6 +217,30 @@ void TabWidget::handleMouseDown(int x, int y, int button, int clickCount) {
setActiveTab(tabID);
}
+void TabWidget::handleMouseMoved(int x, int y, int button) {
+ assert(y < _tabHeight);
+
+ if (x < 0)
+ return;
+
+ // Determine which tab the mouse is on
+ int tabID;
+ for (tabID = _firstVisibleTab; tabID <= _lastVisibleTab; ++tabID) {
+ x -= _tabs[tabID]._tabWidth;
+ if (x < 0)
+ break;
+ }
+
+ if (tabID <= _lastVisibleTab) {
+ if (tabID != _lastRead) {
+ read(_tabs[tabID].title);
+ _lastRead = tabID;
+ }
+ }
+ else
+ _lastRead = -1;
+}
+
bool TabWidget::handleKeyDown(Common::KeyState state) {
if (state.hasFlags(Common::KBD_SHIFT) && state.keycode == Common::KEYCODE_TAB)
adjustTabs(kTabBackwards);
diff --git a/gui/widgets/tab.h b/gui/widgets/tab.h
index f85d30a..8411509 100644
--- a/gui/widgets/tab.h
+++ b/gui/widgets/tab.h
@@ -60,6 +60,7 @@ protected:
ButtonWidget *_navLeft, *_navRight;
bool _navButtonsVisible;
+ int _lastRead;
public:
TabWidget(GuiObject *boss, int x, int y, int w, int h);
@@ -102,6 +103,8 @@ public:
}
virtual void handleMouseDown(int x, int y, int button, int clickCount) override;
+ virtual void handleMouseMoved(int x, int y, int button);
+ virtual void handleMouseLeft(int button) { _lastRead = -1; };
virtual bool handleKeyDown(Common::KeyState state) override;
virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
virtual int getFirstVisible() const;
Commit: 8bd7e392657989dd49da592d8b0bf6e14fe50166
https://github.com/scummvm/scummvm/commit/8bd7e392657989dd49da592d8b0bf6e14fe50166
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add voice selection to options
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
common/text-to-speech.h
gui/gui-manager.cpp
gui/gui-manager.h
gui/options.cpp
gui/options.h
gui/themes/default.inc
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 47c441a..49aefdb 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -180,6 +180,12 @@ void LinuxTextToSpeechManager::setLanguage(Common::String language) {
setVoice(_ttsState->_activeVoice);
}
+void LinuxTextToSpeechManager::createVoice(int typeNumber, Common::TTSVoice::Gender gender, char *description) {
+ SPDVoiceType *type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
+ *type = static_cast<SPDVoiceType>(typeNumber);
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(gender, (void *) type, description));
+}
+
void LinuxTextToSpeechManager::updateVoices() {
if (_speechState == BROKEN)
return;
@@ -192,37 +198,17 @@ void LinuxTextToSpeechManager::updateVoices() {
configuration
*/
- SPDVoiceType *type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
- *type = SPD_MALE1;
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::MALE, (void *) type));
-
- type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
- *type = SPD_MALE2;
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::MALE, (void *) type));
-
- type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
- *type = SPD_MALE3;
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::MALE, (void *) type));
-
- type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
- *type = SPD_FEMALE1;
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::FEMALE, (void *) type));
-
- type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
- *type = SPD_FEMALE2;
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::FEMALE, (void *) type));
-
- type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
- *type = SPD_FEMALE3;
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::FEMALE, (void *) type));
+ char **voiceInfo = spd_list_voices(_connection);
- type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
- *type = SPD_CHILD_MALE;
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::MALE, (void *) type));
+ createVoice(SPD_MALE1, Common::TTSVoice::MALE, voiceInfo[0]);
+ createVoice(SPD_MALE2, Common::TTSVoice::MALE, voiceInfo[1]);
+ createVoice(SPD_MALE3, Common::TTSVoice::MALE, voiceInfo[2]);
+ createVoice(SPD_FEMALE1, Common::TTSVoice::FEMALE, voiceInfo[3]);
+ createVoice(SPD_FEMALE2, Common::TTSVoice::FEMALE, voiceInfo[4]);
+ createVoice(SPD_FEMALE3, Common::TTSVoice::FEMALE, voiceInfo[5]);
+ createVoice(SPD_CHILD_MALE, Common::TTSVoice::MALE, voiceInfo[6]);
+ createVoice(SPD_CHILD_FEMALE, Common::TTSVoice::FEMALE, voiceInfo[7]);
- type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
- *type = SPD_CHILD_FEMALE;
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(Common::TTSVoice::FEMALE, (void *) type));
}
#endif
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index faa6a9d..41c4a64 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -67,6 +67,7 @@ public:
private:
virtual void updateVoices();
+ void createVoice(int typeNumber, Common::TTSVoice::Gender, char *description);
SpeechState _speechState;
};
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 8d0021e..4e66cf2 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -44,18 +44,22 @@ class TTSVoice {
public:
TTSVoice()
: _gender(UNKNOWN)
- , _data(nullptr) {}
- TTSVoice(Gender gender, void *data)
+ , _data(nullptr)
+ , _description("") {}
+ TTSVoice(Gender gender, void *data, String description)
: _gender(gender)
- , _data(data) {}
+ , _data(data)
+ , _description(description) {}
Gender getGender() { return _gender; };
void setGender(Gender gender) { _gender = gender; };
void setData(void *data) { _data = data; };
void *getData() { return _data; };
+ String getDescription() { return _description; };
protected:
Gender _gender;
void *_data;
+ String _description;
};
struct TTSState {
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index cff8e8c..89d7d45 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -74,6 +74,10 @@ GuiManager::GuiManager() : _redrawStatus(kRedrawDisabled), _stateIsSaved(false),
TransMan.setLanguage(ConfMan.get("gui_language").c_str());
#endif // USE_TRANSLATION
+#ifdef USE_TTS
+ initTextToSpeech();
+#endif // USE_TTS
+
ConfMan.registerDefault("gui_theme", "scummremastered");
Common::String themefile(ConfMan.get("gui_theme"));
@@ -619,4 +623,15 @@ void GuiManager::setLastMousePos(int16 x, int16 y) {
_lastMousePosition.time = _system->getMillis(true);
}
+#ifdef USE_TTS
+void GuiManager::initTextToSpeech() {
+ int voice;
+ if(ConfMan.hasKey("tts_voice"))
+ voice = ConfMan.getInt("tts_voice", "scummvm");
+ else
+ voice = 0;
+ g_system->getTextToSpeechManager()->setVoice(voice);
+}
+#endif
+
} // End of namespace GUI
diff --git a/gui/gui-manager.h b/gui/gui-manager.h
index 07ea474..fa5e715 100644
--- a/gui/gui-manager.h
+++ b/gui/gui-manager.h
@@ -176,6 +176,8 @@ protected:
void giveFocusToDialog(Dialog *dialog);
void setLastMousePos(int16 x, int16 y);
+
+ void initTextToSpeech();
};
} // End of namespace GUI
diff --git a/gui/options.cpp b/gui/options.cpp
index 1d4f6c7..b871e92 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -39,6 +39,7 @@
#include "common/translation.h"
#include "common/updates.h"
#include "common/util.h"
+#include "common/text-to-speech.h"
#include "audio/mididrv.h"
#include "audio/musicplugin.h"
@@ -1527,6 +1528,7 @@ GlobalOptionsDialog::GlobalOptionsDialog(LauncherDialog *launcher)
#ifdef USE_TTS
_enableTTS = false;
_ttsCheckbox = 0;
+ _ttsVoiceSelectionPopUp = 0;
#endif
}
@@ -1797,6 +1799,18 @@ void GlobalOptionsDialog::build() {
else
_ttsCheckbox->setState(false);
+ _ttsVoiceSelectionPopUp = new PopUpWidget(tab, "GlobalOptions_Accessibility.TTSVoiceSelection");
+ Common::Array<Common::TTSVoice> voices = g_system->getTextToSpeechManager()->getVoicesArray();
+
+ for(unsigned i = 0; i < voices.size(); i++) {
+ _ttsVoiceSelectionPopUp->appendEntry(voices[i].getDescription(), i);
+ }
+
+ if (ConfMan.hasKey("tts_voice"))
+ _ttsVoiceSelectionPopUp->setSelectedTag(ConfMan.getInt("tts_voice", _domain)) ;
+ else
+ _ttsVoiceSelectionPopUp->setSelectedTag(0);
+
#endif // USE_TTS
// Activate the first tab
@@ -2136,6 +2150,9 @@ void GlobalOptionsDialog::apply() {
}
#ifdef USE_TTS
ConfMan.setBool("tts_enabled", _ttsCheckbox->getState(), _domain);
+ int selectedVoice = _ttsVoiceSelectionPopUp->getSelectedTag();
+ ConfMan.setInt("tts_voice", selectedVoice, _domain);
+ g_system->getTextToSpeechManager()->setVoice(selectedVoice);
#endif
if (isRebuildNeeded) {
diff --git a/gui/options.h b/gui/options.h
index 5f7a6b3..76ad4ef 100644
--- a/gui/options.h
+++ b/gui/options.h
@@ -361,6 +361,7 @@ protected:
#ifdef USE_TTS
bool _enableTTS;
CheckboxWidget *_ttsCheckbox;
+ PopUpWidget *_ttsVoiceSelectionPopUp;
#endif
};
diff --git a/gui/themes/default.inc b/gui/themes/default.inc
index 603567d..2982bf2 100644
--- a/gui/themes/default.inc
+++ b/gui/themes/default.inc
@@ -1558,6 +1558,9 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"<widget name='TTSCheckbox' "
"type='Checkbox' "
"/>"
+"<widget name='TTSVoiceSelection' "
+"type='PopUp' "
+"/>"
"</layout>"
"</dialog>"
"<dialog name='GlobalMenu' overlays='screen_center'>"
Commit: 7d72fc0d60fef9babbb5a8579b152a42d7f5d3d2
https://github.com/scummvm/scummvm/commit/7d72fc0d60fef9babbb5a8579b152a42d7f5d3d2
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Restrict TTS on linux to only english
Unfortunatedly the encoding used by ScummVM breaks the
speech-dispatcher, so after trying to say non-ascii character
the connection has to be restarted. So for now I am restricting
the GUI TTS to english only.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
gui/gui-manager.cpp
gui/options.cpp
gui/widget.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 49aefdb..770a56c 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -101,6 +101,7 @@ bool LinuxTextToSpeechManager::say(Common::String str) {
return true;
if (isSpeaking())
stop();
+ debug("say: %s", str.c_str());
return spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1;
}
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 89d7d45..f33fc39 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -625,6 +625,10 @@ void GuiManager::setLastMousePos(int16 x, int16 y) {
#ifdef USE_TTS
void GuiManager::initTextToSpeech() {
+#if defined(USE_TRANSLATION) && defined(USE_LINUX_TTS)
+ if(ConfMan.hasKey("gui_language") && ConfMan.get("gui_language") != "C")
+ warning("TTS on linux is supported only for english");
+#endif
int voice;
if(ConfMan.hasKey("tts_voice"))
voice = ConfMan.getInt("tts_voice", "scummvm");
diff --git a/gui/options.cpp b/gui/options.cpp
index b871e92..e0e0cdf 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -2149,6 +2149,15 @@ void GlobalOptionsDialog::apply() {
error.runModal();
}
#ifdef USE_TTS
+ bool ttsCheckboxChanged = _ttsCheckbox->getState() &&
+ (!ConfMan.hasKey("tts_enabled") || !ConfMan.getBool("tts_enabled"));
+ bool languageChanged = (newLang != oldLang);
+ if (ttsCheckboxChanged || languageChanged) {
+#if defined(USE_TRANSLATION) && defined(USE_LINUX_TTS)
+ if (ConfMan.get("gui_language") != "C")
+ warning("TTS on linux is supported only for english");
+#endif
+ }
ConfMan.setBool("tts_enabled", _ttsCheckbox->getState(), _domain);
int selectedVoice = _ttsVoiceSelectionPopUp->getSelectedTag();
ConfMan.setInt("tts_voice", selectedVoice, _domain);
diff --git a/gui/widget.cpp b/gui/widget.cpp
index d02d64f..1a3986e 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -258,6 +258,10 @@ Common::String Widget::cleanupHotkey(const Common::String &label) {
void Widget::read(Common::String str) {
#ifdef USE_TTS
+#if defined(USE_LINUX_TTS) && defined(USE_TRANSLATION)
+ if (ConfMan.get("gui_language") != "C")
+ return;
+#endif
if (ConfMan.hasKey("tts_enabled", "scummvm") &&
ConfMan.getBool("tts_enabled", "scummvm")) {
int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
Commit: a9eaf46c421e7cb5731b99b138f982fdc9bb3833
https://github.com/scummvm/scummvm/commit/a9eaf46c421e7cb5731b99b138f982fdc9bb3833
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add warning if the TTS could not be inited
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 770a56c..110bb83 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -68,6 +68,7 @@ LinuxTextToSpeechManager::LinuxTextToSpeechManager()
_connection = spd_open("ScummVM", "main", NULL, SPD_MODE_THREADED);
if (_connection == 0) {
_speechState = BROKEN;
+ warning("Couldn't initialize text to speech through speech-dispatcher");
return;
}
Commit: e801e83f4385b4c4652fa30b66af4b396e45c0da
https://github.com/scummvm/scummvm/commit/e801e83f4385b4c4652fa30b66af4b396e45c0da
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Restart spd connection on speak error.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 110bb83..3445ca9 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -65,6 +65,10 @@ void speech_pause_callback(size_t msg_id, size_t client_id, SPDNotificationType
LinuxTextToSpeechManager::LinuxTextToSpeechManager()
: _speechState(READY) {
+ init();
+}
+
+void LinuxTextToSpeechManager::init() {
_connection = spd_open("ScummVM", "main", NULL, SPD_MODE_THREADED);
if (_connection == 0) {
_speechState = BROKEN;
@@ -103,7 +107,14 @@ bool LinuxTextToSpeechManager::say(Common::String str) {
if (isSpeaking())
stop();
debug("say: %s", str.c_str());
- return spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1;
+ if(spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1) {
+ //restart the connection
+ if (_connection != 0)
+ spd_close(_connection);
+ init();
+ return true;
+ }
+ return false;
}
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 41c4a64..d08da49 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -66,6 +66,7 @@ public:
void updateState(SpeechState state);
private:
+ void init();
virtual void updateVoices();
void createVoice(int typeNumber, Common::TTSVoice::Gender, char *description);
SpeechState _speechState;
Commit: c899f5fbc3dfbaf8886c2b4f2438e372bbe18f2d
https://github.com/scummvm/scummvm/commit/c899f5fbc3dfbaf8886c2b4f2438e372bbe18f2d
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add Accessibility tab to all themes
Changed paths:
gui/themes/default.inc
gui/themes/scummclassic.zip
gui/themes/scummclassic/classic_layout.stx
gui/themes/scummclassic/classic_layout_lowres.stx
gui/themes/scummmodern.zip
gui/themes/scummmodern/scummmodern_layout.stx
gui/themes/scummmodern/scummmodern_layout_lowres.stx
gui/themes/scummremastered.zip
gui/themes/scummremastered/remastered_layout.stx
gui/themes/scummremastered/remastered_layout_lowres.stx
diff --git a/gui/themes/default.inc b/gui/themes/default.inc
index 2982bf2..3e09b49 100644
--- a/gui/themes/default.inc
+++ b/gui/themes/default.inc
@@ -644,12 +644,21 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"<widget name='SmallLabel' "
"size='24,Globals.Line.Height' "
"/>"
+"<widget name='CloudTabLabel' "
+"size='200,Globals.Line.Height' "
+"/>"
+"<widget name='CloudTabLabelValue' "
+"size='200,Globals.Line.Height' "
+"/>"
"<widget name='ShortOptionsLabel' "
"size='60,Globals.Line.Height' "
"/>"
"<widget name='Button' "
"size='108,24' "
"/>"
+"<widget name='WideButton' "
+"size='216,24' "
+"/>"
"<widget name='Slider' "
"size='128,18' "
"/>"
@@ -1170,49 +1179,136 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"</dialog>"
"<dialog name='GlobalOptions_Cloud_Container' overlays='GlobalOptions_Cloud.Container'>"
"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
-"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<layout type='horizontal' padding='-19,7,0,0' spacing='10'>"
+"<layout type='vertical' padding='0,0,2,0' spacing='2'>"
"<widget name='StoragePopupDesc' "
"type='OptionsLabel' "
+"height='Globals.Line.Height' "
"/>"
+"</layout>"
+"<layout type='vertical' padding='0,0,0,0' spacing='2'>"
"<widget name='StoragePopup' "
"type='PopUp' "
"/>"
"</layout>"
+"</layout>"
"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<layout type='vertical' padding='0,0,8,0' spacing='4'>"
+"<widget name='StorageDisabledHint' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='StorageEnableButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<layout type='vertical' padding='0,0,6,0' spacing='2'>"
"<widget name='StorageUsernameDesc' "
-"type='OptionsLabel' "
+"type='CloudTabLabel' "
"/>"
"<widget name='StorageUsernameLabel' "
-"height='Globals.Line.Height' "
+"type='CloudTabLabelValue' "
"/>"
"</layout>"
-"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<layout type='vertical' padding='0,0,6,0' spacing='2'>"
"<widget name='StorageUsedSpaceDesc' "
-"type='OptionsLabel' "
+"type='CloudTabLabel' "
"/>"
"<widget name='StorageUsedSpaceLabel' "
-"height='Globals.Line.Height' "
+"type='CloudTabLabelValue' "
"/>"
"</layout>"
+"</layout>"
"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<layout type='vertical' padding='0,0,6,0' spacing='2'>"
"<widget name='StorageLastSyncDesc' "
-"type='OptionsLabel' "
+"type='CloudTabLabel' "
"/>"
"<widget name='StorageLastSyncLabel' "
+"type='CloudTabLabelValue' "
+"/>"
+"</layout>"
+"<layout type='vertical' padding='0,0,7,0' spacing='2'>"
+"<widget name='SyncSavesButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,-4,0' spacing='10' center='true'>"
+"<widget name='StorageSyncHint' "
"height='Globals.Line.Height' "
"/>"
"</layout>"
"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
-"<widget name='ConnectButton' "
+"<layout type='vertical' padding='0,0,6,0' spacing='4'>"
+"<widget name='StorageDownloadHint' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='DownloadButton' "
+"type='WideButton' "
+"/>"
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<layout type='vertical' padding='0,0,8,0' spacing='4'>"
+"<widget name='StorageDisconnectHint' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='DisconnectButton' "
"type='Button' "
"/>"
-"<widget name='RefreshButton' "
+"</layout>"
+"</layout>"
+"<layout type='vertical' padding='0,0,6,0' spacing='2'>"
+"<widget name='StorageWizardNotConnectedHint' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,-2,0' spacing='10' center='true'>"
+"<layout type='vertical' padding='0,0,2,0' spacing='4'>"
+"<widget name='StorageWizardOpenLinkHint' "
+"width='106' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='vertical' padding='0,0,1,0' spacing='4'>"
+"<widget name='StorageWizardLink' "
+"width='192' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,-2,0' spacing='10' center='true'>"
+"<widget name='StorageWizardCodeHint' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<layout type='vertical' padding='0,0,-2,0' spacing='2'>"
+"<widget name='StorageWizardCodeBox' "
+"width='108' "
+"height='24' "
+"/>"
+"</layout>"
+"<layout type='vertical' padding='0,0,-2,0' spacing='2'>"
+"<widget name='StorageWizardPasteButton' "
"type='Button' "
"/>"
-"<widget name='DownloadButton' "
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,-2,0' spacing='10' center='true'>"
+"<widget name='StorageWizardConnectButton' "
"type='Button' "
"/>"
+"<widget name='StorageWizardConnectionStatusHint' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Network' overlays='Dialog.GlobalOptions.TabWidget'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
"<widget name='RunServerButton' "
"type='Button' "
@@ -1246,6 +1342,14 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"width='Globals.Line.Height' "
"/>"
"</layout>"
+"<layout type='vertical' padding='0,0,0,0' spacing='4' center='true'>"
+"<widget name='FeatureDescriptionLine1' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='FeatureDescriptionLine2' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
"</layout>"
"</dialog>"
"<dialog name='GlobalOptions_Cloud_DownloadDialog' overlays='Dialog.GlobalOptions'>"
@@ -1373,6 +1477,16 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"</layout>"
"</layout>"
"</dialog>"
+"<dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='16'>"
+"<widget name='TTSCheckbox' "
+"type='Checkbox' "
+"/>"
+"<widget name='TTSVoiceSelection' "
+"type='PopUp' "
+"/>"
+"</layout>"
+"</dialog>"
"<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>"
"<layout type='vertical' padding='8,8,8,8' center='true'>"
"<widget name='Action' "
@@ -1553,16 +1667,6 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"/>"
"</layout>"
"</dialog>"
-"<dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>"
-"<layout type='vertical' padding='16,16,16,16' spacing='16'>"
-"<widget name='TTSCheckbox' "
-"type='Checkbox' "
-"/>"
-"<widget name='TTSVoiceSelection' "
-"type='PopUp' "
-"/>"
-"</layout>"
-"</dialog>"
"<dialog name='GlobalMenu' overlays='screen_center'>"
"<layout type='vertical' padding='16,16,16,16' center='true'>"
"<widget name='Title' "
@@ -2263,6 +2367,9 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"<widget name='Button' "
"size='72,16' "
"/>"
+"<widget name='WideButton' "
+"size='144,16' "
+"/>"
"<widget name='Slider' "
"size='85,12' "
"/>"
@@ -2273,6 +2380,12 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"<widget name='SmallLabel' "
"size='18,Globals.Line.Height' "
"/>"
+"<widget name='CloudTabLabel' "
+"size='180,Globals.Line.Height' "
+"/>"
+"<widget name='CloudTabLabelValue' "
+"size='180,Globals.Line.Height' "
+"/>"
"<widget name='PopUp' "
"size='-1,15' "
"/>"
@@ -2795,58 +2908,138 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"</layout>"
"</dialog>"
"<dialog name='GlobalOptions_Cloud_Container' overlays='GlobalOptions_Cloud.Container'>"
-"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
-"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<layout type='vertical' padding='10,13,10,10' spacing='8'>"
+"<layout type='horizontal' padding='-10,1,0,0' spacing='6'>"
+"<layout type='vertical' padding='0,0,1,0' spacing='1'>"
"<widget name='StoragePopupDesc' "
-"width='80' "
+"width='100' "
"height='Globals.Line.Height' "
"textalign='right' "
"/>"
+"</layout>"
+"<layout type='vertical' padding='0,0,0,0' spacing='1'>"
"<widget name='StoragePopup' "
"type='PopUp' "
"/>"
"</layout>"
+"</layout>"
"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
-"<widget name='StorageUsernameDesc' "
-"width='80' "
+"<layout type='vertical' padding='0,0,3,0' spacing='4'>"
+"<widget name='StorageDisabledHint' "
"height='Globals.Line.Height' "
-"textalign='right' "
"/>"
-"<widget name='StorageUsernameLabel' "
-"height='Globals.Line.Height' "
+"<widget name='StorageEnableButton' "
+"type='Button' "
"/>"
"</layout>"
+"</layout>"
"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<layout type='vertical' padding='0,0,3,0' spacing='1'>"
+"<widget name='StorageUsernameDesc' "
+"type='CloudTabLabel' "
+"/>"
+"<widget name='StorageUsernameLabel' "
+"type='CloudTabLabelValue' "
+"/>"
+"</layout>"
+"<layout type='vertical' padding='0,0,3,0' spacing='1'>"
"<widget name='StorageUsedSpaceDesc' "
-"width='80' "
-"height='Globals.Line.Height' "
-"textalign='right' "
+"type='CloudTabLabel' "
"/>"
"<widget name='StorageUsedSpaceLabel' "
-"height='Globals.Line.Height' "
+"type='CloudTabLabelValue' "
"/>"
"</layout>"
+"</layout>"
"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<layout type='vertical' padding='0,0,3,0' spacing='1'>"
"<widget name='StorageLastSyncDesc' "
-"width='80' "
-"height='Globals.Line.Height' "
-"textalign='right' "
+"type='CloudTabLabel' "
"/>"
"<widget name='StorageLastSyncLabel' "
+"type='CloudTabLabelValue' "
+"/>"
+"</layout>"
+"<layout type='vertical' padding='0,0,5,0' spacing='1'>"
+"<widget name='SyncSavesButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,-3,0' spacing='6' center='true'>"
+"<widget name='StorageSyncHint' "
"height='Globals.Line.Height' "
"/>"
"</layout>"
"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
-"<widget name='ConnectButton' "
+"<layout type='vertical' padding='0,0,3,0' spacing='4'>"
+"<widget name='StorageDownloadHint' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='DownloadButton' "
+"type='WideButton' "
+"/>"
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<layout type='vertical' padding='0,0,3,0' spacing='4'>"
+"<widget name='StorageDisconnectHint' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='DisconnectButton' "
"type='Button' "
"/>"
-"<widget name='RefreshButton' "
+"</layout>"
+"</layout>"
+"<layout type='vertical' padding='0,0,3,0' spacing='1'>"
+"<widget name='StorageWizardNotConnectedHint' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,-3,0' spacing='6' center='true'>"
+"<layout type='vertical' padding='0,0,1,0' spacing='2'>"
+"<widget name='StorageWizardOpenLinkHint' "
+"width='90' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='vertical' padding='0,0,1,0' spacing='4'>"
+"<widget name='StorageWizardLink' "
+"width='150' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,-2,0' spacing='6' center='true'>"
+"<widget name='StorageWizardCodeHint' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<layout type='vertical' padding='0,0,-2,0' spacing='2'>"
+"<widget name='StorageWizardCodeBox' "
+"width='72' "
+"height='16' "
+"/>"
+"</layout>"
+"<layout type='vertical' padding='0,0,-2,0' spacing='2'>"
+"<widget name='StorageWizardPasteButton' "
"type='Button' "
"/>"
-"<widget name='DownloadButton' "
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,-2,0' spacing='6' center='true'>"
+"<widget name='StorageWizardConnectButton' "
"type='Button' "
"/>"
+"<widget name='StorageWizardConnectionStatusHint' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Network' overlays='Dialog.GlobalOptions.TabWidget'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
"<widget name='RunServerButton' "
"type='Button' "
@@ -2882,6 +3075,14 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"width='Globals.Line.Height' "
"/>"
"</layout>"
+"<layout type='vertical' padding='0,0,0,0' spacing='2' center='true'>"
+"<widget name='FeatureDescriptionLine1' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='FeatureDescriptionLine2' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
"</layout>"
"</dialog>"
"<dialog name='GlobalOptions_Cloud_DownloadDialog' overlays='Dialog.GlobalOptions'>"
@@ -3004,6 +3205,16 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"</layout>"
"</layout>"
"</dialog>"
+"<dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='16'>"
+"<widget name='TTSCheckbox' "
+"type='Checkbox' "
+"/>"
+"<widget name='TTSVoiceSelection' "
+"type='PopUp' "
+"/>"
+"</layout>"
+"</dialog>"
"<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>"
"<layout type='vertical' padding='8,8,8,8' center='true'>"
"<widget name='Action' "
diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip
index 0eba01a..dd5aa32 100644
Binary files a/gui/themes/scummclassic.zip and b/gui/themes/scummclassic.zip differ
diff --git a/gui/themes/scummclassic/classic_layout.stx b/gui/themes/scummclassic/classic_layout.stx
index 1ed4f6b..14f1bb7 100644
--- a/gui/themes/scummclassic/classic_layout.stx
+++ b/gui/themes/scummclassic/classic_layout.stx
@@ -823,6 +823,110 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '0'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Picture'
+ width = '109'
+ height = '109'
+ />
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox2'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox3'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox4'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox6'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox7'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox8'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '6' />
+ </layout>
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
+ <layout type='vertical' padding='16,16,16,16' spacing='16'>
+ <widget name='TTSCheckbox'
+ type='Checkbox'
+ />
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
+ />
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx
index d73327d..f2bbb00 100644
--- a/gui/themes/scummclassic/classic_layout_lowres.stx
+++ b/gui/themes/scummclassic/classic_layout_lowres.stx
@@ -830,6 +830,104 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '4'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox2'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox3'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox4'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox6'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox7'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox8'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ <space size = '6' />
+ <widget name = 'Picture' width = '1' height = '1' />
+ </layout>
+ </layout>
+ </dialog>
+ <dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
+ <layout type='vertical' padding='16,16,16,16' spacing='16'>
+ <widget name='TTSCheckbox'
+ type='Checkbox'
+ />
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
+ />
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip
index cf59388..64f42f5 100644
Binary files a/gui/themes/scummmodern.zip and b/gui/themes/scummmodern.zip differ
diff --git a/gui/themes/scummmodern/scummmodern_layout.stx b/gui/themes/scummmodern/scummmodern_layout.stx
index ad4f6d7..28a7265 100644
--- a/gui/themes/scummmodern/scummmodern_layout.stx
+++ b/gui/themes/scummmodern/scummmodern_layout.stx
@@ -837,6 +837,110 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '0'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Picture'
+ width = '109'
+ height = '109'
+ />
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox2'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox3'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox4'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox6'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox7'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox8'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '6' />
+ </layout>
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
+ <layout type='vertical' padding='16,16,16,16' spacing='16'>
+ <widget name='TTSCheckbox'
+ type='Checkbox'
+ />
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
+ />
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
diff --git a/gui/themes/scummmodern/scummmodern_layout_lowres.stx b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
index bb06082..8574534 100644
--- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx
+++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
@@ -828,6 +828,105 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '4'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox2'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox3'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox4'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox6'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox7'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox8'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ <space size = '6' />
+ <widget name = 'Picture' width = '1' height = '1' />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
+ <layout type='vertical' padding='16,16,16,16' spacing='16'>
+ <widget name='TTSCheckbox'
+ type='Checkbox'
+ />
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
+ />
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
diff --git a/gui/themes/scummremastered.zip b/gui/themes/scummremastered.zip
index f9bf2e4..f0e7839 100644
Binary files a/gui/themes/scummremastered.zip and b/gui/themes/scummremastered.zip differ
diff --git a/gui/themes/scummremastered/remastered_layout.stx b/gui/themes/scummremastered/remastered_layout.stx
index ad4f6d7..7543304 100644
--- a/gui/themes/scummremastered/remastered_layout.stx
+++ b/gui/themes/scummremastered/remastered_layout.stx
@@ -837,6 +837,109 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '0'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Picture'
+ width = '109'
+ height = '109'
+ />
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox2'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox3'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox4'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox6'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox7'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox8'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '6' />
+ </layout>
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
+ <layout type='vertical' padding='16,16,16,16' spacing='16'>
+ <widget name='TTSCheckbox'
+ type='Checkbox'
+ />
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
+ />
+ </layout>
+ </dialog>
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
diff --git a/gui/themes/scummremastered/remastered_layout_lowres.stx b/gui/themes/scummremastered/remastered_layout_lowres.stx
index 5a265e2..3edd2e2 100644
--- a/gui/themes/scummremastered/remastered_layout_lowres.stx
+++ b/gui/themes/scummremastered/remastered_layout_lowres.stx
@@ -828,6 +828,105 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '4'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox2'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox3'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox4'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox6'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox7'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox8'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ <space size = '6' />
+ <widget name = 'Picture' width = '1' height = '1' />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
+ <layout type='vertical' padding='16,16,16,16' spacing='16'>
+ <widget name='TTSCheckbox'
+ type='Checkbox'
+ />
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
+ />
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
Commit: 5ee30a1b7342c21db8b32d4e87bdfa300a27a2b4
https://github.com/scummvm/scummvm/commit/5ee30a1b7342c21db8b32d4e87bdfa300a27a2b4
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Convert strings to UTF-8
Conversion happens only for languages, that might needed (not
for english)
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
gui/widget.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 3445ca9..9f330bc 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -31,6 +31,8 @@
#include "common/translation.h"
#include "common/debug.h"
#include "common/system.h"
+#include "common/ustr.h"
+#include "common/config-manager.h"
SPDConnection *_connection;
void speech_begin_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
@@ -104,6 +106,10 @@ void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechState
bool LinuxTextToSpeechManager::say(Common::String str) {
if (_speechState == BROKEN)
return true;
+ //Convert string, that might have foreign characters to UTF-8
+ if (ConfMan.get("gui_language") != "C") {
+ str = Common::convertUtf32ToUtf8(Common::convertToU32String(str.c_str(), Common::kWindows1250)).c_str();
+ }
if (isSpeaking())
stop();
debug("say: %s", str.c_str());
diff --git a/gui/widget.cpp b/gui/widget.cpp
index 1a3986e..d02d64f 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -258,10 +258,6 @@ Common::String Widget::cleanupHotkey(const Common::String &label) {
void Widget::read(Common::String str) {
#ifdef USE_TTS
-#if defined(USE_LINUX_TTS) && defined(USE_TRANSLATION)
- if (ConfMan.get("gui_language") != "C")
- return;
-#endif
if (ConfMan.hasKey("tts_enabled", "scummvm") &&
ConfMan.getBool("tts_enabled", "scummvm")) {
int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
Commit: 5e44796d6b4c117daf95635588f7d4c83602791e
https://github.com/scummvm/scummvm/commit/5e44796d6b4c117daf95635588f7d4c83602791e
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add correct language switching
Changed paths:
gui/gui-manager.cpp
gui/options.cpp
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index f33fc39..8cb201d 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -625,16 +625,21 @@ void GuiManager::setLastMousePos(int16 x, int16 y) {
#ifdef USE_TTS
void GuiManager::initTextToSpeech() {
-#if defined(USE_TRANSLATION) && defined(USE_LINUX_TTS)
- if(ConfMan.hasKey("gui_language") && ConfMan.get("gui_language") != "C")
- warning("TTS on linux is supported only for english");
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+#ifdef USE_TRANSLATION
+ Common::String currentLanguage = TransMan.getCurrentLanguage();
+ if (currentLanguage != "C") {
+ currentLanguage.setChar('\0', 2);
+ debug("%s", TransMan.getCurrentCharset().c_str());
+ ttsMan->setLanguage(currentLanguage);
+ }
#endif
int voice;
if(ConfMan.hasKey("tts_voice"))
voice = ConfMan.getInt("tts_voice", "scummvm");
else
voice = 0;
- g_system->getTextToSpeechManager()->setVoice(voice);
+ ttsMan->setVoice(voice);
}
#endif
diff --git a/gui/options.cpp b/gui/options.cpp
index e0e0cdf..39bf616 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -2149,19 +2149,20 @@ void GlobalOptionsDialog::apply() {
error.runModal();
}
#ifdef USE_TTS
- bool ttsCheckboxChanged = _ttsCheckbox->getState() &&
- (!ConfMan.hasKey("tts_enabled") || !ConfMan.getBool("tts_enabled"));
- bool languageChanged = (newLang != oldLang);
- if (ttsCheckboxChanged || languageChanged) {
-#if defined(USE_TRANSLATION) && defined(USE_LINUX_TTS)
- if (ConfMan.get("gui_language") != "C")
- warning("TTS on linux is supported only for english");
-#endif
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (newLang != oldLang) {
+ if (newLang == "C")
+ ttsMan->setLanguage("en");
+ else {
+ Common::String guiLang = newLang;
+ guiLang.setChar('\0', 2);
+ ttsMan->setLanguage(guiLang);
+ }
}
ConfMan.setBool("tts_enabled", _ttsCheckbox->getState(), _domain);
int selectedVoice = _ttsVoiceSelectionPopUp->getSelectedTag();
ConfMan.setInt("tts_voice", selectedVoice, _domain);
- g_system->getTextToSpeechManager()->setVoice(selectedVoice);
+ ttsMan->setVoice(selectedVoice);
#endif
if (isRebuildNeeded) {
Commit: 53e0f21adc92908beb1f2308268d4b3e04876887
https://github.com/scummvm/scummvm/commit/53e0f21adc92908beb1f2308268d4b3e04876887
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Reformat tts volume setting for GUI
Changed paths:
gui/gui-manager.cpp
gui/options.cpp
gui/widget.cpp
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 8cb201d..666e079 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -634,6 +634,11 @@ void GuiManager::initTextToSpeech() {
ttsMan->setLanguage(currentLanguage);
}
#endif
+ int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
+ if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm"))
+ volume = 0;
+ ttsMan->setVolume(volume);
+
int voice;
if(ConfMan.hasKey("tts_voice"))
voice = ConfMan.getInt("tts_voice", "scummvm");
diff --git a/gui/options.cpp b/gui/options.cpp
index 39bf616..3195694 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -2159,6 +2159,10 @@ void GlobalOptionsDialog::apply() {
ttsMan->setLanguage(guiLang);
}
}
+ int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
+ if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm"))
+ volume = 0;
+ ttsMan->setVolume(volume);
ConfMan.setBool("tts_enabled", _ttsCheckbox->getState(), _domain);
int selectedVoice = _ttsVoiceSelectionPopUp->getSelectedTag();
ConfMan.setInt("tts_voice", selectedVoice, _domain);
diff --git a/gui/widget.cpp b/gui/widget.cpp
index d02d64f..3269a5f 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -260,10 +260,6 @@ void Widget::read(Common::String str) {
#ifdef USE_TTS
if (ConfMan.hasKey("tts_enabled", "scummvm") &&
ConfMan.getBool("tts_enabled", "scummvm")) {
- int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
- if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm"))
- volume = 0;
- g_system->getTextToSpeechManager()->setVolume(volume);
g_system->getTextToSpeechManager()->say(str);
}
#endif
Commit: 6303f522e180f1474434c75c0e5a4d44b141fe3a
https://github.com/scummvm/scummvm/commit/6303f522e180f1474434c75c0e5a4d44b141fe3a
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Refactor tts in configure
Changed paths:
configure
diff --git a/configure b/configure
index af65184..503766d 100755
--- a/configure
+++ b/configure
@@ -5363,7 +5363,7 @@ define_in_config_if_yes $_dialogs 'USE_SYSDIALOGS'
#
# Check whether to build TTS integration support
#
-echo_n "Building text-to-speech integration support... "
+echo_n "Building text-to-speech support... "
if test "$_tts" = "no"; then
echo "no"
else
@@ -5372,14 +5372,16 @@ else
echo "linux"
_tts=yes
_linux_tts=yes
+ define_in_config_if_yes $_linux_tts 'USE_LINUX_TTS'
+ append_var LIBS '-lspeechd'
;;
*)
+ echo "no"
+ _tts=no
;;
esac
fi
define_in_config_if_yes $_tts 'USE_TTS'
-define_in_config_if_yes $_linux_tts 'USE_LINUX_TTS'
-append_var LIBS '-lspeechd'
#
# Check whether to build Bink video support
Commit: 8357e8e6bf909353c3db720ba8fda9cdb0a41933
https://github.com/scummvm/scummvm/commit/8357e8e6bf909353c3db720ba8fda9cdb0a41933
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Prepare for windows TTS
Add windows configuration in configure
Add basic skeleton to backends
Check if ttsMan is initialized in GUI
Changed paths:
A backends/text-to-speech/windows/sphelper-scummvm.h
A backends/text-to-speech/windows/windows-text-to-speech.cpp
A backends/text-to-speech/windows/windows-text-to-speech.h
backends/module.mk
backends/platform/sdl/win32/win32.cpp
configure
gui/gui-manager.cpp
gui/options.cpp
gui/widget.cpp
gui/widgets/popup.cpp
diff --git a/backends/module.mk b/backends/module.mk
index 42c8404..04b38bf 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -348,6 +348,10 @@ ifdef USE_LINUX_TTS
MODULE_OBJS += \
text-to-speech/linux/linux-text-to-speech.o
endif
+ifdef USE_WINDOWS_TTS
+MODULE_OBJS += \
+ text-to-speech/windows/windows-text-to-speech.o
+endif
# Include common rules
include $(srcdir)/rules.mk
diff --git a/backends/platform/sdl/win32/win32.cpp b/backends/platform/sdl/win32/win32.cpp
index 16b7a1f..4b0961b 100644
--- a/backends/platform/sdl/win32/win32.cpp
+++ b/backends/platform/sdl/win32/win32.cpp
@@ -54,6 +54,10 @@
#include "common/ustr.h"
#include "common/encoding.h"
+#ifdef USE_WINDOWS_TTS
+#include "backends/text-to-speech/windows/windows-text-to-speech.h"
+#endif
+
#define DEFAULT_CONFIG_FILE "scummvm.ini"
void OSystem_Win32::init() {
@@ -115,6 +119,9 @@ void OSystem_Win32::initBackend() {
// Initialize updates manager
_updateManager = new Win32UpdateManager();
#endif
+#ifdef USE_WINDOWS_TTS
+ _textToSpeechManager = new WindowsTextToSpeechManager();
+#endif
// Invoke parent implementation of this method
OSystem_SDL::initBackend();
diff --git a/backends/text-to-speech/windows/sphelper-scummvm.h b/backends/text-to-speech/windows/sphelper-scummvm.h
new file mode 100644
index 0000000..d910a65
--- /dev/null
+++ b/backends/text-to-speech/windows/sphelper-scummvm.h
@@ -0,0 +1,2648 @@
+/*******************************************************************************
+* SPHelper.h *
+*------------*
+* Description:
+* This is the header file for core helper functions implementation.
+*-------------------------------------------------------------------------------
+* Copyright (c) Microsoft Corporation. All rights reserved.
+*******************************************************************************/
+#ifndef SPHelper_h
+#define SPHelper_h
+
+#include <iostream>
+#ifndef _INC_MALLOC
+#include <malloc.h>
+#endif
+
+#ifndef __sapi_h__
+#include <sapi.h>
+#endif
+
+#ifndef __sapiddk_h__
+#include <sapiddk.h>
+#endif
+
+#ifndef SPError_h
+#include <SPError.h>
+#endif
+
+#ifndef _INC_LIMITS
+#include <limits.h>
+#endif
+
+#ifndef _INC_MMSYSTEM
+#include <mmsystem.h>
+#endif
+
+#ifndef __comcat_h__
+#include <comcat.h>
+#endif
+
+#ifndef _INC_MMREG
+#include <mmreg.h>
+#endif
+
+#include <cwctype>
+//=== Constants ==============================================================
+#define sp_countof(x) ((sizeof(x) / sizeof(*(x))))
+
+/*** CSpDynamicString helper class
+*
+*/
+class CSpDynamicString
+{
+public:
+
+ WCHAR * m_psz;
+ CSpDynamicString()
+ {
+ m_psz = NULL;
+ }
+ CSpDynamicString(ULONG cchReserve)
+ {
+ m_psz = (WCHAR *)::CoTaskMemAlloc(cchReserve * sizeof(WCHAR));
+ }
+ WCHAR * operator=(const CSpDynamicString& src)
+ {
+ if (m_psz != src.m_psz)
+ {
+ ::CoTaskMemFree(m_psz);
+ m_psz = src.Copy();
+ }
+ return m_psz;
+ }
+ WCHAR * operator=(const WCHAR * pSrc)
+ {
+ Clear();
+ if (pSrc)
+ {
+ ULONG cbNeeded = (wcslen(pSrc) + 1) * sizeof(WCHAR);
+ m_psz = (WCHAR *)::CoTaskMemAlloc(cbNeeded);
+ if (m_psz)
+ {
+ memcpy(m_psz, pSrc, cbNeeded);
+ }
+ }
+ return m_psz;
+ }
+
+ WCHAR * operator=(const char * pSrc)
+ {
+ Clear();
+ if (pSrc)
+ {
+ ULONG cbNeeded = (lstrlenA(pSrc) + 1) * sizeof(WCHAR);
+ m_psz = (WCHAR *)::CoTaskMemAlloc(cbNeeded);
+ if (m_psz)
+ {
+ ::MultiByteToWideChar(CP_ACP, 0, pSrc, -1, m_psz, cbNeeded/sizeof(WCHAR));
+ }
+ }
+ return m_psz;
+ }
+
+ WCHAR * operator=(REFGUID rguid)
+ {
+ Clear();
+ ::StringFromCLSID(rguid, &m_psz);
+ return m_psz;
+ }
+
+
+ /*explicit*/ CSpDynamicString(const WCHAR * pSrc)
+ {
+ m_psz = NULL;
+ operator=(pSrc);
+ }
+ /*explicit*/ CSpDynamicString(const char * pSrc)
+ {
+ m_psz = NULL;
+ operator=(pSrc);
+ }
+ /*explicit*/ CSpDynamicString(const CSpDynamicString& src)
+ {
+ m_psz = src.Copy();
+ }
+ /*explicit*/ CSpDynamicString(REFGUID rguid)
+ {
+ ::StringFromCLSID(rguid, &m_psz);
+ }
+
+
+ ~CSpDynamicString()
+ {
+ ::CoTaskMemFree(m_psz);
+ }
+ unsigned int Length() const
+ {
+ return (m_psz == NULL)? 0 : wcslen(m_psz);
+ }
+
+ operator WCHAR * () const
+ {
+ return m_psz;
+ }
+ //The assert on operator& usually indicates a bug. If this is really
+ //what is needed, however, take the address of the m_psz member explicitly.
+ WCHAR ** operator&()
+ {
+ return &m_psz;
+ }
+
+ WCHAR * Append(const WCHAR * pszSrc)
+ {
+ if (pszSrc)
+ {
+ ULONG lenSrc = wcslen(pszSrc);
+ if (lenSrc)
+ {
+ ULONG lenMe = Length();
+ WCHAR *pszNew = (WCHAR *)::CoTaskMemAlloc((lenMe + lenSrc + 1) * sizeof(WCHAR));
+ if (pszNew)
+ {
+ if (m_psz) // Could append to an empty string so check...
+ {
+ if (lenMe)
+ {
+ memcpy(pszNew, m_psz, lenMe * sizeof(WCHAR));
+ }
+ ::CoTaskMemFree(m_psz);
+ }
+ memcpy(pszNew + lenMe, pszSrc, (lenSrc + 1) * sizeof(WCHAR));
+ m_psz = pszNew;
+ }
+ else
+ {
+ }
+ }
+ }
+ return m_psz;
+ }
+
+ WCHAR * Append(const WCHAR * pszSrc, const ULONG lenSrc)
+ {
+ if (pszSrc && lenSrc)
+ {
+ ULONG lenMe = Length();
+ WCHAR *pszNew = (WCHAR *)::CoTaskMemAlloc((lenMe + lenSrc + 1) * sizeof(WCHAR));
+ if (pszNew)
+ {
+ if (m_psz) // Could append to an empty string so check...
+ {
+ if (lenMe)
+ {
+ memcpy(pszNew, m_psz, lenMe * sizeof(WCHAR));
+ }
+ ::CoTaskMemFree(m_psz);
+ }
+ memcpy(pszNew + lenMe, pszSrc, lenSrc * sizeof(WCHAR));
+ *(pszNew + lenMe + lenSrc) = L'\0';
+ m_psz = pszNew;
+ }
+ else
+ {
+ }
+ }
+ return m_psz;
+ }
+
+ WCHAR * Append2(const WCHAR * pszSrc1, const WCHAR * pszSrc2)
+ {
+ ULONG lenSrc1 = pszSrc1 ? wcslen(pszSrc1) : 0;
+ ULONG lenSrc2 = pszSrc2 ? wcslen(pszSrc2) : 0;
+
+ if (lenSrc1 || lenSrc2)
+ {
+ ULONG lenMe = Length();
+ WCHAR *pszNew = (WCHAR *)::CoTaskMemAlloc((lenMe + lenSrc1 + lenSrc2 + 1) * sizeof(WCHAR));
+ if (pszNew)
+ {
+ if (m_psz) // Could append to an empty string so check...
+ {
+ if (lenMe)
+ {
+ memcpy(pszNew, m_psz, lenMe * sizeof(WCHAR));
+ }
+ ::CoTaskMemFree(m_psz);
+ }
+ // In both of these cases, we copy the trailing NULL so that we're sure it gets
+ // there (if lenSrc2 is 0 then we better copy it from pszSrc1).
+ if (lenSrc1)
+ {
+ memcpy(pszNew + lenMe, pszSrc1, (lenSrc1 + 1) * sizeof(WCHAR));
+ }
+ if (lenSrc2)
+ {
+ memcpy(pszNew + lenMe + lenSrc1, pszSrc2, (lenSrc2 + 1) * sizeof(WCHAR));
+ }
+ m_psz = pszNew;
+ }
+ else
+ {
+ }
+ }
+ return m_psz;
+ }
+ WCHAR * Copy() const
+ {
+ if (m_psz)
+ {
+ CSpDynamicString szNew(m_psz);
+ return szNew.Detach();
+ }
+ return NULL;
+ }
+ CHAR * CopyToChar() const
+ {
+ if (m_psz)
+ {
+ CHAR* psz;
+ ULONG cbNeeded = ::WideCharToMultiByte(CP_ACP, 0, m_psz, -1, NULL, NULL, NULL, NULL);
+ psz = (CHAR *)::CoTaskMemAlloc(cbNeeded);
+ if (psz)
+ {
+ ::WideCharToMultiByte(CP_ACP, 0, m_psz, -1, psz, cbNeeded/sizeof(CHAR), NULL, NULL);
+ }
+ return psz;
+ }
+ return NULL;
+ }
+ void Attach(WCHAR * pszSrc)
+ {
+ m_psz = pszSrc;
+ }
+ WCHAR * Detach()
+ {
+ WCHAR * s = m_psz;
+ m_psz = NULL;
+ return s;
+ }
+ void Clear()
+ {
+ ::CoTaskMemFree(m_psz);
+ m_psz = NULL;
+ }
+ bool operator!() const
+ {
+ return (m_psz == NULL);
+ }
+ HRESULT CopyToBSTR(BSTR * pbstr)
+ {
+ if (m_psz)
+ {
+ *pbstr = ::SysAllocString(m_psz);
+ if (*pbstr == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+ else
+ {
+ *pbstr = NULL;
+ }
+ return S_OK;
+ }
+ void TrimToSize(ULONG ulNumChars)
+ {
+ if (m_psz && ulNumChars < Length())
+ {
+ m_psz[ulNumChars] = 0;
+ }
+ }
+ WCHAR * Compact()
+ {
+ if (m_psz)
+ {
+ ULONG cch = wcslen(m_psz);
+ m_psz = (WCHAR *)::CoTaskMemRealloc(m_psz, (cch + 1) * sizeof(WCHAR));
+ }
+ return m_psz;
+ }
+ WCHAR * ClearAndGrowTo(ULONG cch)
+ {
+ if (m_psz)
+ {
+ Clear();
+ }
+ m_psz = (WCHAR *)::CoTaskMemAlloc(cch * sizeof(WCHAR));
+ return m_psz;
+ }
+ WCHAR * LTrim()
+ {
+ if (m_psz)
+ {
+ WCHAR * pszRead = m_psz;
+ while (iswspace(*pszRead))
+ {
+ pszRead++;
+ }
+ if (pszRead != m_psz)
+ {
+ WCHAR * pszWrite = m_psz;
+ while (*pszRead)
+ {
+ *pszWrite++ = *pszRead++;
+ }
+ *pszWrite = '\0';
+ }
+ }
+ return m_psz;
+ }
+ WCHAR * RTrim()
+ {
+ if (m_psz)
+ {
+ WCHAR * pszTail = m_psz + wcslen(m_psz);
+ WCHAR * pszZeroTerm = pszTail;
+ while (pszZeroTerm > m_psz && iswspace(pszZeroTerm[-1]))
+ {
+ pszZeroTerm--;
+ }
+ if (pszZeroTerm != pszTail)
+ {
+ *pszZeroTerm = '\0';
+ }
+ }
+ return m_psz;
+ }
+ WCHAR * TrimBoth()
+ {
+ RTrim();
+ return LTrim();
+ }
+};
+
+
+
+//
+// Simple inline function converts a ulong to a hex string.
+//
+inline void SpHexFromUlong(WCHAR * psz, ULONG ul)
+{
+ const static WCHAR szHexChars[] = L"0123456789ABCDEF";
+ if (ul == 0)
+ {
+ psz[0] = L'0';
+ psz[1] = 0;
+ }
+ else
+ {
+ ULONG ulChars = 1;
+ psz[0] = 0;
+ while (ul)
+ {
+ memmove(psz + 1, psz, ulChars * sizeof(WCHAR));
+ psz[0] = szHexChars[ul % 16];
+ ul /= 16;
+ ulChars++;
+ }
+ }
+}
+
+
+//=== Token helpers
+
+inline HRESULT SpGetTokenFromId(
+ const WCHAR * pszTokenId,
+ ISpObjectToken ** ppToken,
+ BOOL fCreateIfNotExist = FALSE)
+{
+
+ LPUNKNOWN pUnkOuter = nullptr;
+ HRESULT hr = ::CoCreateInstance(CLSID_SpObjectToken, pUnkOuter, CLSCTX_ALL, IID_ISpObjectToken,reinterpret_cast<void **>(ppToken));
+
+ if (SUCCEEDED(hr))
+ {
+ (*ppToken)->SetId(NULL, pszTokenId, fCreateIfNotExist);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpGetCategoryFromId(
+ const WCHAR * pszCategoryId,
+ ISpObjectTokenCategory ** ppCategory,
+ BOOL fCreateIfNotExist = FALSE)
+{
+ LPUNKNOWN pUnkOuter = nullptr;
+ HRESULT hr = ::CoCreateInstance(CLSID_SpObjectTokenCategory, pUnkOuter, CLSCTX_ALL, IID_ISpObjectTokenCategory, reinterpret_cast<void **>(ppCategory));
+
+ if (SUCCEEDED(hr))
+ {
+ hr = (*ppCategory)->SetId(pszCategoryId, fCreateIfNotExist);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpGetDefaultTokenIdFromCategoryId(
+ const WCHAR * pszCategoryId,
+ WCHAR ** ppszTokenId)
+{
+ HRESULT hr;
+
+ ISpObjectTokenCategory *cpCategory;
+ hr = SpGetCategoryFromId(pszCategoryId, &cpCategory);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = cpCategory->GetDefaultTokenId(ppszTokenId);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpSetDefaultTokenIdForCategoryId(
+ const WCHAR * pszCategoryId,
+ const WCHAR * pszTokenId)
+{
+ HRESULT hr;
+
+ ISpObjectTokenCategory *cpCategory;
+ hr = SpGetCategoryFromId(pszCategoryId, &cpCategory);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = cpCategory->SetDefaultTokenId(pszTokenId);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpGetDefaultTokenFromCategoryId(
+ const WCHAR * pszCategoryId,
+ ISpObjectToken ** ppToken,
+ BOOL fCreateCategoryIfNotExist = TRUE)
+{
+ HRESULT hr;
+
+ ISpObjectTokenCategory *cpCategory;
+ hr = SpGetCategoryFromId(pszCategoryId, &cpCategory, fCreateCategoryIfNotExist);
+
+ if (SUCCEEDED(hr))
+ {
+ WCHAR * pszTokenId;
+ hr = cpCategory->GetDefaultTokenId(&pszTokenId);
+ if (SUCCEEDED(hr))
+ {
+ hr = SpGetTokenFromId(pszTokenId, ppToken);
+ ::CoTaskMemFree(pszTokenId);
+ }
+ }
+
+ return hr;
+}
+
+inline HRESULT SpSetDefaultTokenForCategoryId(
+ const WCHAR * pszCategoryId,
+ ISpObjectToken * pToken)
+{
+ HRESULT hr;
+
+ WCHAR * pszTokenId;
+ hr = pToken->GetId(&pszTokenId);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = SpSetDefaultTokenIdForCategoryId(pszCategoryId, pszTokenId);
+ ::CoTaskMemFree(pszTokenId);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpSetCommonTokenData(
+ ISpObjectToken * pToken,
+ const CLSID * pclsid,
+ const WCHAR * pszLangIndependentName,
+ LANGID langid,
+ const WCHAR * pszLangDependentName,
+ ISpDataKey ** ppDataKeyAttribs)
+{
+ HRESULT hr = S_OK;
+
+ // Set the new token's CLSID (if specified)
+ if (SUCCEEDED(hr) && pclsid)
+ {
+ CSpDynamicString dstrClsid;
+ hr = StringFromCLSID(*pclsid, &dstrClsid);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = pToken->SetStringValue(SPTOKENVALUE_CLSID, dstrClsid);
+ }
+ }
+
+ // Set the token's lang independent name
+ if (SUCCEEDED(hr) && pszLangIndependentName)
+ {
+ hr = pToken->SetStringValue(NULL, pszLangIndependentName);
+ }
+
+ // Set the token's lang dependent name
+ if (SUCCEEDED(hr) && pszLangDependentName)
+ {
+ WCHAR szLangId[10];
+ SpHexFromUlong(szLangId, langid);
+
+ hr = pToken->SetStringValue(szLangId, pszLangDependentName);
+ }
+
+ // Open the attributes key if requested
+ if (SUCCEEDED(hr) && ppDataKeyAttribs)
+ {
+ hr = pToken->CreateKey(L"Attributes", ppDataKeyAttribs);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpCreateNewToken(
+ const WCHAR * pszTokenId,
+ ISpObjectToken ** ppToken)
+{
+ HRESULT hr;
+
+ // Forcefully create the token
+ hr = SpGetTokenFromId(pszTokenId, ppToken, TRUE);
+
+ return hr;
+}
+
+inline HRESULT SpCreateNewToken(
+ const WCHAR * pszCategoryId,
+ const WCHAR * pszTokenKeyName,
+ ISpObjectToken ** ppToken)
+{
+ HRESULT hr;
+
+ // Forcefully create the category
+ ISpObjectTokenCategory *cpCategory;
+ hr = SpGetCategoryFromId(pszCategoryId, &cpCategory, TRUE);
+
+ // Come up with a token key name if one wasn't specified
+ CSpDynamicString dstrTokenKeyName;
+ if (SUCCEEDED(hr))
+ {
+ if (pszTokenKeyName == NULL)
+ {
+ GUID guidTokenKeyName;
+ hr = CoCreateGuid(&guidTokenKeyName);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = StringFromCLSID(guidTokenKeyName, &dstrTokenKeyName);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ pszTokenKeyName = dstrTokenKeyName;
+ }
+ }
+ }
+
+ // Build the token id
+ CSpDynamicString dstrTokenId;
+ if (SUCCEEDED(hr))
+ {
+ dstrTokenId = pszCategoryId;
+ dstrTokenId.Append2(L"\\Tokens\\", pszTokenKeyName);
+ }
+
+ // Forcefully create the token
+ if (SUCCEEDED(hr))
+ {
+ hr = SpGetTokenFromId(dstrTokenId, ppToken, TRUE);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpCreateNewTokenEx(
+ const WCHAR * pszCategoryId,
+ const WCHAR * pszTokenKeyName,
+ const CLSID * pclsid,
+ const WCHAR * pszLangIndependentName,
+ LANGID langid,
+ const WCHAR * pszLangDependentName,
+ ISpObjectToken ** ppToken,
+ ISpDataKey ** ppDataKeyAttribs)
+{
+ HRESULT hr;
+
+ // Create the new token
+ hr = SpCreateNewToken(pszCategoryId, pszTokenKeyName, ppToken);
+
+ // Now set the extra data
+ if (SUCCEEDED(hr))
+ {
+ hr = SpSetCommonTokenData(
+ *ppToken,
+ pclsid,
+ pszLangIndependentName,
+ langid,
+ pszLangDependentName,
+ ppDataKeyAttribs);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpCreateNewTokenEx(
+ const WCHAR * pszTokenId,
+ const CLSID * pclsid,
+ const WCHAR * pszLangIndependentName,
+ LANGID langid,
+ const WCHAR * pszLangDependentName,
+ ISpObjectToken ** ppToken,
+ ISpDataKey ** ppDataKeyAttribs)
+{
+ HRESULT hr;
+
+ // Create the new token
+ hr = SpCreateNewToken(pszTokenId, ppToken);
+
+ // Now set the extra data
+ if (SUCCEEDED(hr))
+ {
+ hr = SpSetCommonTokenData(
+ *ppToken,
+ pclsid,
+ pszLangIndependentName,
+ langid,
+ pszLangDependentName,
+ ppDataKeyAttribs);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpEnumTokens(
+ const WCHAR * pszCategoryId,
+ const WCHAR * pszReqAttribs,
+ const WCHAR * pszOptAttribs,
+ IEnumSpObjectTokens ** ppEnum)
+{
+ HRESULT hr = S_OK;
+
+ ISpObjectTokenCategory *cpCategory;
+ hr = SpGetCategoryFromId(pszCategoryId, &cpCategory);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = cpCategory->EnumTokens(
+ pszReqAttribs,
+ pszOptAttribs,
+ ppEnum);
+ }
+
+ return hr;
+}
+
+inline HRESULT SpFindBestToken(
+ const WCHAR * pszCategoryId,
+ const WCHAR * pszReqAttribs,
+ const WCHAR * pszOptAttribs,
+ ISpObjectToken **ppObjectToken)
+{
+ HRESULT hr = S_OK;
+
+ const WCHAR *pszVendorPreferred = L"VendorPreferred";
+ const unsigned long LenVendorPreferred = wcslen(pszVendorPreferred);
+
+ // append VendorPreferred to the end of pszOptAttribs to force this preference
+ ULONG ulLen = pszOptAttribs ? wcslen(pszOptAttribs) + LenVendorPreferred + 1 : LenVendorPreferred;
+ WCHAR *pszOptAttribsVendorPref = (WCHAR*)_alloca((ulLen+1)*sizeof(WCHAR));
+ if (pszOptAttribsVendorPref)
+ {
+ if (pszOptAttribs)
+ {
+ wcscpy(pszOptAttribsVendorPref, pszOptAttribs);
+ wcscat(pszOptAttribsVendorPref, L";");
+ wcscat(pszOptAttribsVendorPref, pszVendorPreferred);
+ }
+ else
+ {
+ wcscpy(pszOptAttribsVendorPref, pszVendorPreferred);
+ }
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ IEnumSpObjectTokens *cpEnum;
+ if (SUCCEEDED(hr))
+ {
+ hr = SpEnumTokens(pszCategoryId, pszReqAttribs, pszOptAttribsVendorPref, &cpEnum);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = cpEnum->Next(1, ppObjectToken, NULL);
+ if (hr == S_FALSE)
+ {
+ *ppObjectToken = NULL;
+ hr = SPERR_NOT_FOUND;
+ }
+ }
+
+ if (hr != SPERR_NOT_FOUND)
+ {
+ }
+
+ return hr;
+}
+
+/*template<class T>
+HRESULT SpCreateObjectFromToken(ISpObjectToken * pToken, T ** ppObject,
+ IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL)
+{
+ HRESULT hr;
+
+ hr = pToken->CreateInstance(pUnkOuter, dwClsCtxt, __uuidof(T), (void **)ppObject);
+
+ return hr;
+}
+
+template<class T>
+HRESULT SpCreateObjectFromTokenId(const WCHAR * pszTokenId, T ** ppObject,
+ IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL)
+{
+
+ ISpObjectToken * pToken;
+ HRESULT hr = SpGetTokenFromId(pszTokenId, &pToken);
+ if (SUCCEEDED(hr))
+ {
+ hr = SpCreateObjectFromToken(pToken, ppObject, pUnkOuter, dwClsCtxt);
+ pToken->Release();
+ }
+
+ return hr;
+}*/
+/*
+template<class T>
+HRESULT SpCreateDefaultObjectFromCategoryId(const WCHAR * pszCategoryId, T ** ppObject,
+ IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL)
+{
+
+ ISpObjectToken * pToken;
+ HRESULT hr = SpGetDefaultTokenFromCategoryId(pszCategoryId, &pToken);
+ if (SUCCEEDED(hr))
+ {
+ hr = SpCreateObjectFromToken(pToken, ppObject, pUnkOuter, dwClsCtxt);
+ pToken->Release();
+ }
+
+ return hr;
+}
+
+template<class T>
+HRESULT SpCreateBestObject(
+ const WCHAR * pszCategoryId,
+ const WCHAR * pszReqAttribs,
+ const WCHAR * pszOptAttribs,
+ T ** ppObject,
+ IUnknown * pUnkOuter = NULL,
+ DWORD dwClsCtxt = CLSCTX_ALL)
+{
+ HRESULT hr;
+
+ ISpObjectToken *cpToken;
+ hr = SpFindBestToken(pszCategoryId, pszReqAttribs, pszOptAttribs, &cpToken);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = SpCreateObjectFromToken(cpToken, ppObject, pUnkOuter, dwClsCtxt);
+ }
+
+ if (hr != SPERR_NOT_FOUND)
+ {
+ }
+
+ return hr;
+}*/
+
+/*HRESULT SpCreateBestObject(
+ const WCHAR * pszCategoryId,
+ const WCHAR * pszReqAttribs,
+ const WCHAR * pszOptAttribs,
+ ISpPhoneConverter ** ** ppObject,
+ IUnknown * pUnkOuter = NULL,
+ DWORD dwClsCtxt = CLSCTX_ALL)
+{
+ HRESULT hr;
+
+ ISpObjectToken *cpToken;
+ hr = SpFindBestToken(pszCategoryId, pszReqAttribs, pszOptAttribs, &cpToken);
+
+ if (SUCCEEDED(hr))
+ {
+ hr = SpCreateObjectFromToken(cpToken, ppObject, pUnkOuter, dwClsCtxt);
+ }
+
+ if (hr != SPERR_NOT_FOUND)
+ {
+ }
+
+ return hr;
+}*/
+
+/*inline HRESULT SpCreatePhoneConverter(
+ LANGID LangID,
+ const WCHAR * pszReqAttribs,
+ const WCHAR * pszOptAttribs,
+ ISpPhoneConverter ** ppPhoneConverter)
+{
+ HRESULT hr;
+
+ if (LangID == 0)
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ CSpDynamicString dstrReqAttribs;
+ if (pszReqAttribs)
+ {
+ dstrReqAttribs = pszReqAttribs;
+ dstrReqAttribs.Append(L";");
+ }
+
+ WCHAR szLang[MAX_PATH];
+
+ SpHexFromUlong(szLang, LangID);
+
+ WCHAR szLangCondition[MAX_PATH];
+ wcscpy(szLangCondition, L"Language=");
+ wcscat(szLangCondition, szLang);
+
+ dstrReqAttribs.Append(szLangCondition);
+
+ hr = SpCreateBestObject(SPCAT_PHONECONVERTERS, dstrReqAttribs, pszOptAttribs, ppPhoneConverter);
+ }
+
+ if (hr != SPERR_NOT_FOUND)
+ {
+ }
+
+ return hr;
+}*/
+
+/****************************************************************************
+* SpHrFromWin32 *
+*---------------*
+* Description:
+* This inline function works around a basic problem with the macro
+* HRESULT_FROM_WIN32. The macro forces the expresion in ( ) to be evaluated
+* two times. By using this inline function, the expression will only be
+* evaluated once.
+*
+* Returns:
+* HRESULT of converted Win32 error code
+*
+*****************************************************************************/
+
+inline HRESULT SpHrFromWin32(DWORD dwErr)
+{
+ return HRESULT_FROM_WIN32(dwErr);
+}
+
+
+/****************************************************************************
+* SpHrFromLastWin32Error *
+*------------------------*
+* Description:
+* This simple inline function is used to return a converted HRESULT
+* from the Win32 function ::GetLastError. Note that using HRESULT_FROM_WIN32
+* will evaluate the error code twice so we don't want to use:
+*
+* HRESULT_FROM_WIN32(::GetLastError())
+*
+* since that will call GetLastError twice.
+* On Win98 and WinMe ::GetLastError() returns 0 for some functions (see MSDN).
+* We therefore check for that and return E_FAIL. This function should only be
+* called in an error case since it will always return an error code!
+*
+* Returns:
+* HRESULT for ::GetLastError()
+*
+*****************************************************************************/
+
+inline HRESULT SpHrFromLastWin32Error()
+{
+ DWORD dw = ::GetLastError();
+ return (dw == 0) ? E_FAIL : SpHrFromWin32(dw);
+}
+
+
+/****************************************************************************
+* SpGetUserDefaultUILanguage *
+*----------------------------*
+* Description:
+* Returns the default user interface language, using a method
+* appropriate to the platform (Windows 9x, Windows NT, or Windows 2000)
+*
+* Returns:
+* Default UI language
+*
+*****************************************************************************/
+
+inline LANGID SpGetUserDefaultUILanguage(void)
+{
+ HRESULT hr = S_OK;
+ LANGID wUILang = 0;
+
+ OSVERSIONINFO Osv ;
+ Osv.dwOSVersionInfoSize = sizeof(Osv) ;
+ if(!GetVersionEx(&Osv))
+ {
+ hr = SpHrFromLastWin32Error();
+ }
+ // Get the UI language by one of three methods, depending on the system
+ else if(Osv.dwPlatformId != VER_PLATFORM_WIN32_NT)
+ {
+ // Case 1: Running on Windows 9x. Get the system UI language from registry:
+ CHAR szData[32];
+ DWORD dwSize = sizeof(szData) ;
+ HKEY hKey;
+
+ long lRet = RegOpenKeyEx(
+ HKEY_USERS,
+ ".Default\\Control Panel\\desktop\\ResourceLocale",
+ 0,
+ KEY_READ,
+ &hKey);
+
+#ifdef _WIN32_WCE_BUG_10655
+ if (lRet == ERROR_INVALID_PARAMETER)
+ {
+ lRet = ERROR_FILE_NOT_FOUND;
+ }
+#endif // _WIN32_WCE_BUG_10655
+
+ hr = SpHrFromWin32(lRet);
+
+ if (SUCCEEDED(hr))
+ {
+ lRet = RegQueryValueEx(
+ hKey,
+ "",
+ NULL,
+ NULL,
+ (BYTE *)szData,
+ &dwSize);
+
+#ifdef _WIN32_WCE_BUG_10655
+ if(lRet == ERROR_INVALID_PARAMETER)
+ {
+ lRet = ERROR_FILE_NOT_FOUND;
+ }
+#endif //_WIN32_WCE_BUG_10655
+
+ hr = SpHrFromWin32(lRet);
+ ::RegCloseKey(hKey) ;
+ }
+ if (SUCCEEDED(hr))
+ {
+ // Convert string to number
+ wUILang = (LANGID) strtol(szData, NULL, 16) ;
+ }
+ }
+ else if (Osv.dwMajorVersion >= 5.0)
+ {
+ // Case 2: Running on Windows 2000 or later. Use GetUserDefaultUILanguage to find
+ // the user's prefered UI language
+
+
+ HMODULE hMKernel32 = ::LoadLibraryW(L"kernel32.dll") ;
+ if (hMKernel32 == NULL)
+ {
+ hr = SpHrFromLastWin32Error();
+ }
+ else
+ {
+
+ LANGID (WINAPI *pfnGetUserDefaultUILanguage) () =
+ (LANGID (WINAPI *)(void))
+#ifdef _WIN32_WCE
+ GetProcAddress(hMKernel32, L"GetUserDefaultUILanguage") ;
+#else
+ GetProcAddress(hMKernel32, "GetUserDefaultUILanguage") ;
+#endif
+
+ if(NULL != pfnGetUserDefaultUILanguage)
+ {
+ wUILang = pfnGetUserDefaultUILanguage() ;
+ }
+ else
+ { // GetProcAddress failed
+ hr = SpHrFromLastWin32Error();
+ }
+ ::FreeLibrary(hMKernel32);
+ }
+ }
+ else {
+ // Case 3: Running on Windows NT 4.0 or earlier. Get UI language
+ // from locale of .default user in registry:
+ // HKEY_USERS\.DEFAULT\Control Panel\International\Locale
+
+ WCHAR szData[32] ;
+ DWORD dwSize = sizeof(szData) ;
+ HKEY hKey ;
+
+ LONG lRet = RegOpenKeyEx(HKEY_USERS,
+ ".DEFAULT\\Control Panel\\International",
+ 0,
+ KEY_READ,
+ &hKey);
+#ifdef _WIN32_WCE_BUG_10655
+ if(lRet == ERROR_INVALID_PARAMETER)
+ {
+ lRet = ERROR_FILE_NOT_FOUND;
+ }
+#endif //_WIN32_WCE_BUG_10655
+
+ hr = SpHrFromWin32(lRet);
+
+ if (SUCCEEDED(hr))
+ {
+ lRet = RegQueryValueEx(
+ hKey,
+ "Locale",
+ NULL,
+ NULL,
+ (BYTE *)szData,
+ &dwSize);
+
+#ifdef _WIN32_WCE_BUG_10655
+ if(lRet == ERROR_INVALID_PARAMETER)
+ {
+ lRet = ERROR_FILE_NOT_FOUND;
+ }
+#endif //_WIN32_WCE_BUG_10655
+
+ hr = SpHrFromWin32(lRet);
+ ::RegCloseKey(hKey);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ // Convert string to number
+ wUILang = (LANGID) wcstol(szData, NULL, 16) ;
+
+ if(0x0401 == wUILang || // Arabic
+ 0x040d == wUILang || // Hebrew
+ 0x041e == wUILang // Thai
+ )
+ {
+ // Special case these to the English UI.
+ // These versions of Windows NT 4.0 were enabled only, i.e., the
+ // UI was English. However, the registry setting
+ // HKEY_USERS\.DEFAULT\Control Panel\International\Locale was set
+ // to the respective locale for application compatibility.
+ wUILang = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US) ;
+ }
+ }
+ }
+
+ return (wUILang ? wUILang : ::GetUserDefaultLangID()); // In failure case, try our best!
+}
+
+
+inline HRESULT SpGetDescription(ISpObjectToken * pObjToken, WCHAR ** ppszDescription, LANGID Language = SpGetUserDefaultUILanguage())
+{
+ WCHAR szLangId[10];
+ SpHexFromUlong(szLangId, Language);
+ HRESULT hr = pObjToken->GetStringValue(szLangId, ppszDescription);
+ if (hr == SPERR_NOT_FOUND)
+ {
+ hr = pObjToken->GetStringValue(NULL, ppszDescription);
+ }
+ return hr;
+}
+
+
+inline HRESULT SpSetDescription(ISpObjectToken * pObjToken, const WCHAR * pszDescription, LANGID Language = SpGetUserDefaultUILanguage(), BOOL fSetLangIndependentId = TRUE)
+{
+ WCHAR szLangId[10];
+ SpHexFromUlong(szLangId, Language);
+ HRESULT hr = pObjToken->SetStringValue(szLangId, pszDescription);
+ if (SUCCEEDED(hr) && fSetLangIndependentId)
+ {
+ hr = pObjToken->SetStringValue(NULL, pszDescription);
+ }
+ return hr;
+}
+
+/****************************************************************************
+* SpConvertStreamFormatEnum *
+*---------------------------*
+* Description:
+* This method converts the specified stream format into a wave format
+* structure.
+*
+*****************************************************************************/
+inline HRESULT SpConvertStreamFormatEnum(SPSTREAMFORMAT eFormat, GUID * pFormatId, WAVEFORMATEX ** ppCoMemWaveFormatEx)
+{
+ HRESULT hr = S_OK;
+
+ if(pFormatId==NULL || ::IsBadWritePtr(pFormatId, sizeof(*pFormatId))
+ || ppCoMemWaveFormatEx==NULL || ::IsBadWritePtr(ppCoMemWaveFormatEx, sizeof(*ppCoMemWaveFormatEx)))
+ {
+ return E_INVALIDARG;
+ }
+
+ const GUID * pFmtGuid = &GUID_NULL; // Assume failure case
+ if( eFormat >= SPSF_8kHz8BitMono && eFormat <= SPSF_48kHz16BitStereo )
+ {
+ WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc(sizeof(WAVEFORMATEX));
+ *ppCoMemWaveFormatEx = pwfex;
+ if (pwfex)
+ {
+ DWORD dwIndex = eFormat - SPSF_8kHz8BitMono;
+ BOOL bIsStereo = dwIndex & 0x1;
+ BOOL bIs16 = dwIndex & 0x2;
+ DWORD dwKHZ = (dwIndex & 0x3c) >> 2;
+ static const DWORD adwKHZ[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 };
+ pwfex->wFormatTag = WAVE_FORMAT_PCM;
+ pwfex->nChannels = pwfex->nBlockAlign = (WORD)(bIsStereo ? 2 : 1);
+ pwfex->nSamplesPerSec = adwKHZ[dwKHZ];
+ pwfex->wBitsPerSample = 8;
+ if (bIs16)
+ {
+ pwfex->wBitsPerSample *= 2;
+ pwfex->nBlockAlign *= 2;
+ }
+ pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nBlockAlign;
+ pwfex->cbSize = 0;
+ pFmtGuid = &SPDFID_WaveFormatEx;
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ else if( eFormat == SPSF_TrueSpeech_8kHz1BitMono )
+ {
+ int NumBytes = sizeof( WAVEFORMATEX ) + 32;
+ WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( NumBytes );
+ *ppCoMemWaveFormatEx = pwfex;
+ if( pwfex )
+ {
+ memset( pwfex, 0, NumBytes );
+ pwfex->wFormatTag = WAVE_FORMAT_DSPGROUP_TRUESPEECH;
+ pwfex->nChannels = 1;
+ pwfex->nSamplesPerSec = 8000;
+ pwfex->nAvgBytesPerSec = 1067;
+ pwfex->nBlockAlign = 32;
+ pwfex->wBitsPerSample = 1;
+ pwfex->cbSize = 32;
+ BYTE* pExtra = ((BYTE*)pwfex) + sizeof( WAVEFORMATEX );
+ pExtra[0] = 1;
+ pExtra[2] = 0xF0;
+ pFmtGuid = &SPDFID_WaveFormatEx;
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ else if( (eFormat >= SPSF_CCITT_ALaw_8kHzMono ) &&
+ (eFormat <= SPSF_CCITT_ALaw_44kHzStereo ) )
+ {
+ WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( sizeof(WAVEFORMATEX) );
+ *ppCoMemWaveFormatEx = pwfex;
+ if( pwfex )
+ {
+ memset( pwfex, 0, sizeof(WAVEFORMATEX) );
+ DWORD dwIndex = eFormat - SPSF_CCITT_ALaw_8kHzMono;
+ DWORD dwKHZ = dwIndex / 2;
+ static const DWORD adwKHZ[] = { 8000, 11025, 22050, 44100 };
+ BOOL bIsStereo = dwIndex & 0x1;
+ pwfex->wFormatTag = WAVE_FORMAT_ALAW;
+ pwfex->nChannels = pwfex->nBlockAlign = (WORD)(bIsStereo ? 2 : 1);
+ pwfex->nSamplesPerSec = adwKHZ[dwKHZ];
+ pwfex->wBitsPerSample = 8;
+ pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nBlockAlign;
+ pwfex->cbSize = 0;
+ pFmtGuid = &SPDFID_WaveFormatEx;
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ else if( (eFormat >= SPSF_CCITT_uLaw_8kHzMono ) &&
+ (eFormat <= SPSF_CCITT_uLaw_44kHzStereo ) )
+ {
+ WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( sizeof(WAVEFORMATEX) );
+ *ppCoMemWaveFormatEx = pwfex;
+ if( pwfex )
+ {
+ memset( pwfex, 0, sizeof(WAVEFORMATEX) );
+ DWORD dwIndex = eFormat - SPSF_CCITT_uLaw_8kHzMono;
+ DWORD dwKHZ = dwIndex / 2;
+ static const DWORD adwKHZ[] = { 8000, 11025, 22050, 44100 };
+ BOOL bIsStereo = dwIndex & 0x1;
+ pwfex->wFormatTag = WAVE_FORMAT_MULAW;
+ pwfex->nChannels = pwfex->nBlockAlign = (WORD)(bIsStereo ? 2 : 1);
+ pwfex->nSamplesPerSec = adwKHZ[dwKHZ];
+ pwfex->wBitsPerSample = 8;
+ pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nBlockAlign;
+ pwfex->cbSize = 0;
+ pFmtGuid = &SPDFID_WaveFormatEx;
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ else if( (eFormat >= SPSF_ADPCM_8kHzMono ) &&
+ (eFormat <= SPSF_ADPCM_44kHzStereo ) )
+ {
+ int NumBytes = sizeof( WAVEFORMATEX ) + 32;
+ WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( NumBytes );
+ *ppCoMemWaveFormatEx = pwfex;
+ if( pwfex )
+ {
+ //--- Some of these values seem odd. We used what the codec told us.
+ static const DWORD adwKHZ[] = { 8000, 11025, 22050, 44100 };
+ static const DWORD BytesPerSec[] = { 4096, 8192, 5644, 11289, 11155, 22311, 22179, 44359 };
+ static const DWORD BlockAlign[] = { 256, 256, 512, 1024 };
+ static const BYTE Extra811[32] =
+ {
+ 0xF4, 0x01, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
+ 0xC0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0x00, 0x00,
+ 0xCC, 0x01, 0x30, 0xFF, 0x88, 0x01, 0x18, 0xFF
+ };
+
+ static const BYTE Extra22[32] =
+ {
+ 0xF4, 0x03, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
+ 0xC0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0x00, 0x00,
+ 0xCC, 0x01, 0x30, 0xFF, 0x88, 0x01, 0x18, 0xFF
+ };
+
+ static const BYTE Extra44[32] =
+ {
+ 0xF4, 0x07, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
+ 0xC0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0x00, 0x00,
+ 0xCC, 0x01, 0x30, 0xFF, 0x88, 0x01, 0x18, 0xFF
+ };
+
+ static const BYTE* Extra[4] = { Extra811, Extra811, Extra22, Extra44 };
+ memset( pwfex, 0, NumBytes );
+ DWORD dwIndex = eFormat - SPSF_ADPCM_8kHzMono;
+ DWORD dwKHZ = dwIndex / 2;
+ BOOL bIsStereo = dwIndex & 0x1;
+ pwfex->wFormatTag = WAVE_FORMAT_ADPCM;
+ pwfex->nChannels = (WORD)(bIsStereo ? 2 : 1);
+ pwfex->nSamplesPerSec = adwKHZ[dwKHZ];
+ pwfex->nAvgBytesPerSec = BytesPerSec[dwIndex];
+ pwfex->nBlockAlign = (WORD)(BlockAlign[dwKHZ] * pwfex->nChannels);
+ pwfex->wBitsPerSample = 4;
+ pwfex->cbSize = 32;
+ BYTE* pExtra = ((BYTE*)pwfex) + sizeof( WAVEFORMATEX );
+ memcpy( pExtra, Extra[dwKHZ], 32 );
+ pFmtGuid = &SPDFID_WaveFormatEx;
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ else if( (eFormat >= SPSF_GSM610_8kHzMono ) &&
+ (eFormat <= SPSF_GSM610_44kHzMono ) )
+ {
+ int NumBytes = sizeof( WAVEFORMATEX ) + 2;
+ WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( NumBytes );
+ *ppCoMemWaveFormatEx = pwfex;
+ if( pwfex )
+ {
+ //--- Some of these values seem odd. We used what the codec told us.
+ static const DWORD adwKHZ[] = { 8000, 11025, 22050, 44100 };
+ static const DWORD BytesPerSec[] = { 1625, 2239, 4478, 8957 };
+ memset( pwfex, 0, NumBytes );
+ DWORD dwIndex = eFormat - SPSF_GSM610_8kHzMono;
+ pwfex->wFormatTag = WAVE_FORMAT_GSM610;
+ pwfex->nChannels = 1;
+ pwfex->nSamplesPerSec = adwKHZ[dwIndex];
+ pwfex->nAvgBytesPerSec = BytesPerSec[dwIndex];
+ pwfex->nBlockAlign = 65;
+ pwfex->wBitsPerSample = 0;
+ pwfex->cbSize = 2;
+ BYTE* pExtra = ((BYTE*)pwfex) + sizeof( WAVEFORMATEX );
+ pExtra[0] = 0x40;
+ pExtra[1] = 0x01;
+ pFmtGuid = &SPDFID_WaveFormatEx;
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ else
+ {
+ *ppCoMemWaveFormatEx = NULL;
+ switch (eFormat)
+ {
+ case SPSF_NoAssignedFormat:
+ break;
+ case SPSF_Text:
+ pFmtGuid = &SPDFID_Text;
+ break;
+ default:
+ hr = E_INVALIDARG;
+ break;
+ }
+ }
+ *pFormatId = *pFmtGuid;
+ return hr;
+}
+
+class CSpStreamFormat
+{
+public:
+ GUID m_guidFormatId;
+ WAVEFORMATEX * m_pCoMemWaveFormatEx;
+
+
+ static CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
+ {
+ ULONG cb = sizeof(WAVEFORMATEX) + pSrc->cbSize;
+ *ppCoMemWFEX = (WAVEFORMATEX *)::CoTaskMemAlloc(cb);
+ if (*ppCoMemWFEX)
+ {
+ memcpy(*ppCoMemWFEX, pSrc, cb);
+ return S_OK;
+ }
+ else
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+
+ CSpStreamFormat()
+ {
+ m_guidFormatId = GUID_NULL;
+ m_pCoMemWaveFormatEx = NULL;
+ }
+
+ CSpStreamFormat(SPSTREAMFORMAT eFormat, HRESULT * phr)
+ {
+ *phr = SpConvertStreamFormatEnum(eFormat, &m_guidFormatId, &m_pCoMemWaveFormatEx);
+ }
+
+ CSpStreamFormat(const WAVEFORMATEX * pWaveFormatEx, HRESULT * phr)
+ {
+ *phr = CoMemCopyWFEX(pWaveFormatEx, &m_pCoMemWaveFormatEx);
+ m_guidFormatId = SUCCEEDED(*phr) ? SPDFID_WaveFormatEx : GUID_NULL;
+ }
+
+ ~CSpStreamFormat()
+ {
+ ::CoTaskMemFree(m_pCoMemWaveFormatEx);
+ }
+
+ void Clear()
+ {
+ ::CoTaskMemFree(m_pCoMemWaveFormatEx);
+ m_pCoMemWaveFormatEx = NULL;
+ memset(&m_guidFormatId, 0, sizeof(m_guidFormatId));
+ }
+
+ const GUID & FormatId() const
+ {
+ return m_guidFormatId;
+ }
+
+ const WAVEFORMATEX * WaveFormatExPtr() const
+ {
+ return m_pCoMemWaveFormatEx;
+ }
+
+
+ HRESULT AssignFormat(SPSTREAMFORMAT eFormat)
+ {
+ ::CoTaskMemFree(m_pCoMemWaveFormatEx);
+ return SpConvertStreamFormatEnum(eFormat, &m_guidFormatId, &m_pCoMemWaveFormatEx);
+ }
+
+ HRESULT AssignFormat(ISpStreamFormat * pStream)
+ {
+ ::CoTaskMemFree(m_pCoMemWaveFormatEx);
+ m_pCoMemWaveFormatEx = NULL;
+ return pStream->GetFormat(&m_guidFormatId, &m_pCoMemWaveFormatEx);
+ }
+
+ HRESULT AssignFormat(const WAVEFORMATEX * pWaveFormatEx)
+ {
+ ::CoTaskMemFree(m_pCoMemWaveFormatEx);
+ HRESULT hr = CoMemCopyWFEX(pWaveFormatEx, &m_pCoMemWaveFormatEx);
+ m_guidFormatId = SUCCEEDED(hr) ? SPDFID_WaveFormatEx : GUID_NULL;
+ return hr;
+ }
+
+ HRESULT AssignFormat(REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx)
+ {
+ HRESULT hr = S_OK;
+
+ m_guidFormatId = rguidFormatId;
+ ::CoTaskMemFree(m_pCoMemWaveFormatEx);
+ m_pCoMemWaveFormatEx = NULL;
+
+ if (rguidFormatId == SPDFID_WaveFormatEx)
+ {
+ if (::IsBadReadPtr(pWaveFormatEx, sizeof(*pWaveFormatEx)))
+ {
+ hr = E_INVALIDARG;
+ }
+ else
+ {
+ hr = CoMemCopyWFEX(pWaveFormatEx, &m_pCoMemWaveFormatEx);
+ }
+
+ if (FAILED(hr))
+ {
+ m_guidFormatId = GUID_NULL;
+ }
+ }
+
+ return hr;
+ }
+
+
+ BOOL IsEqual(REFGUID rguidFormatId, const WAVEFORMATEX * pwfex) const
+ {
+ if (rguidFormatId == m_guidFormatId)
+ {
+ if (m_pCoMemWaveFormatEx)
+ {
+ if (pwfex &&
+ pwfex->cbSize == m_pCoMemWaveFormatEx->cbSize &&
+ memcmp(m_pCoMemWaveFormatEx, pwfex, sizeof(WAVEFORMATEX) + pwfex->cbSize) == 0)
+ {
+ return TRUE;
+ }
+ }
+ else
+ {
+ return (pwfex == NULL);
+ }
+ }
+ return FALSE;
+ }
+
+
+
+ HRESULT ParamValidateAssignFormat(REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx, BOOL fRequireWaveFormat = FALSE)
+ {
+ if ((pWaveFormatEx && (::IsBadReadPtr(pWaveFormatEx, sizeof(*pWaveFormatEx)) || rguidFormatId != SPDFID_WaveFormatEx)) ||
+ (fRequireWaveFormat && pWaveFormatEx == NULL))
+ {
+ return E_INVALIDARG;
+ }
+ return AssignFormat(rguidFormatId, pWaveFormatEx);
+ }
+
+ SPSTREAMFORMAT ComputeFormatEnum()
+ {
+ if (m_guidFormatId == GUID_NULL)
+ {
+ return SPSF_NoAssignedFormat;
+ }
+ if (m_guidFormatId == SPDFID_Text)
+ {
+ return SPSF_Text;
+ }
+ if (m_guidFormatId != SPDFID_WaveFormatEx)
+ {
+ return SPSF_NonStandardFormat;
+ }
+ //
+ // It is a WAVEFORMATEX. Now determine which type it is and convert.
+ //
+ DWORD dwIndex = 0;
+ switch (m_pCoMemWaveFormatEx->wFormatTag)
+ {
+ case WAVE_FORMAT_PCM:
+ {
+ switch (m_pCoMemWaveFormatEx->nChannels)
+ {
+ case 1:
+ break;
+ case 2:
+ dwIndex |= 1;
+ break;
+ default:
+ return SPSF_ExtendedAudioFormat;
+ }
+
+ switch (m_pCoMemWaveFormatEx->wBitsPerSample)
+ {
+ case 8:
+ break;
+ case 16:
+ dwIndex |= 2;
+ break;
+ default:
+ return SPSF_ExtendedAudioFormat;
+ }
+
+ switch (m_pCoMemWaveFormatEx->nSamplesPerSec)
+ {
+ case 48000:
+ dwIndex += 4; // Fall through
+ case 44100:
+ dwIndex += 4; // Fall through
+ case 32000:
+ dwIndex += 4; // Fall through
+ case 24000:
+ dwIndex += 4; // Fall through
+ case 22050:
+ dwIndex += 4; // Fall through
+ case 16000:
+ dwIndex += 4; // Fall through
+ case 12000:
+ dwIndex += 4; // Fall through
+ case 11025:
+ dwIndex += 4; // Fall through
+ case 8000:
+ break;
+ default:
+ return SPSF_ExtendedAudioFormat;
+ }
+
+ return static_cast<SPSTREAMFORMAT>(SPSF_8kHz8BitMono + dwIndex);
+ }
+
+ case WAVE_FORMAT_DSPGROUP_TRUESPEECH:
+ {
+ return SPSF_TrueSpeech_8kHz1BitMono;
+ }
+
+ case WAVE_FORMAT_ALAW: // fall through
+ case WAVE_FORMAT_MULAW:
+ case WAVE_FORMAT_ADPCM:
+ {
+ switch (m_pCoMemWaveFormatEx->nChannels)
+ {
+ case 1:
+ break;
+ case 2:
+ dwIndex |= 1;
+ break;
+ default:
+ return SPSF_ExtendedAudioFormat;
+ }
+
+ if(m_pCoMemWaveFormatEx->wFormatTag == WAVE_FORMAT_ADPCM)
+ {
+ if(m_pCoMemWaveFormatEx->wBitsPerSample != 4)
+ {
+ return SPSF_ExtendedAudioFormat;
+ }
+ }
+ else if(m_pCoMemWaveFormatEx->wBitsPerSample != 8)
+ {
+ return SPSF_ExtendedAudioFormat;
+ }
+
+ switch (m_pCoMemWaveFormatEx->nSamplesPerSec)
+ {
+ case 44100:
+ dwIndex += 2; // Fall through
+ case 22050:
+ dwIndex += 2; // Fall through
+ case 11025:
+ dwIndex += 2; // Fall through
+ case 8000:
+ break;
+ default:
+ return SPSF_ExtendedAudioFormat;
+ }
+
+ switch( m_pCoMemWaveFormatEx->wFormatTag )
+ {
+ case WAVE_FORMAT_ALAW:
+ return static_cast<SPSTREAMFORMAT>(SPSF_CCITT_ALaw_8kHzMono + dwIndex);
+ case WAVE_FORMAT_MULAW:
+ return static_cast<SPSTREAMFORMAT>(SPSF_CCITT_uLaw_8kHzMono + dwIndex);
+ case WAVE_FORMAT_ADPCM:
+ return static_cast<SPSTREAMFORMAT>(SPSF_ADPCM_8kHzMono + dwIndex);
+ }
+ }
+
+ case WAVE_FORMAT_GSM610:
+ {
+ if( m_pCoMemWaveFormatEx->nChannels != 1 )
+ {
+ return SPSF_ExtendedAudioFormat;
+ }
+
+ switch (m_pCoMemWaveFormatEx->nSamplesPerSec)
+ {
+ case 44100:
+ dwIndex = 3;
+ break;
+ case 22050:
+ dwIndex = 2;
+ break;
+ case 11025:
+ dwIndex = 1;
+ break;
+ case 8000:
+ dwIndex = 0;
+ break;
+ default:
+ return SPSF_ExtendedAudioFormat;
+ }
+
+ return static_cast<SPSTREAMFORMAT>(SPSF_GSM610_8kHzMono + dwIndex);
+ }
+
+ default:
+ return SPSF_ExtendedAudioFormat;
+ break;
+ }
+ }
+
+ void DetachTo(CSpStreamFormat & Other)
+ {
+ ::CoTaskMemFree(Other.m_pCoMemWaveFormatEx);
+ Other.m_guidFormatId = m_guidFormatId;
+ Other.m_pCoMemWaveFormatEx = m_pCoMemWaveFormatEx;
+ m_pCoMemWaveFormatEx = NULL;
+ memset(&m_guidFormatId, 0, sizeof(m_guidFormatId));
+ }
+
+ void DetachTo(GUID * pFormatId, WAVEFORMATEX ** ppCoMemWaveFormatEx)
+ {
+ *pFormatId = m_guidFormatId;
+ *ppCoMemWaveFormatEx = m_pCoMemWaveFormatEx;
+ m_pCoMemWaveFormatEx = NULL;
+ memset(&m_guidFormatId, 0, sizeof(m_guidFormatId));
+ }
+
+ HRESULT CopyTo(GUID * pFormatId, WAVEFORMATEX ** ppCoMemWFEX) const
+ {
+ HRESULT hr = S_OK;
+ *pFormatId = m_guidFormatId;
+ if (m_pCoMemWaveFormatEx)
+ {
+ hr = CoMemCopyWFEX(m_pCoMemWaveFormatEx, ppCoMemWFEX);
+ if (FAILED(hr))
+ {
+ memset(pFormatId, 0, sizeof(*pFormatId));
+ }
+ }
+ else
+ {
+ *ppCoMemWFEX = NULL;
+ }
+ return hr;
+ }
+
+ HRESULT CopyTo(CSpStreamFormat & Other) const
+ {
+ ::CoTaskMemFree(Other.m_pCoMemWaveFormatEx);
+ return CopyTo(&Other.m_guidFormatId, &Other.m_pCoMemWaveFormatEx);
+ }
+
+ HRESULT AssignFormat(const CSpStreamFormat & Src)
+ {
+ return Src.CopyTo(*this);
+ }
+
+
+ HRESULT ParamValidateCopyTo(GUID * pFormatId, WAVEFORMATEX ** ppCoMemWFEX) const
+ {
+ if (::IsBadWritePtr(pFormatId, sizeof(*pFormatId)) ||
+ ::IsBadWritePtr(ppCoMemWFEX, sizeof(*ppCoMemWFEX)))
+ {
+ return E_POINTER;
+ }
+ return CopyTo(pFormatId, ppCoMemWFEX);
+ }
+
+ BOOL operator==(const CSpStreamFormat & Other) const
+ {
+ return IsEqual(Other.m_guidFormatId, Other.m_pCoMemWaveFormatEx);
+ }
+ BOOL operator!=(const CSpStreamFormat & Other) const
+ {
+ return !IsEqual(Other.m_guidFormatId, Other.m_pCoMemWaveFormatEx);
+ }
+
+ ULONG SerializeSize() const
+ {
+ ULONG cb = sizeof(ULONG) + sizeof(m_guidFormatId);
+ if (m_pCoMemWaveFormatEx)
+ {
+ cb += sizeof(WAVEFORMATEX) + m_pCoMemWaveFormatEx->cbSize + 3; // Add 3 to round up
+ cb -= cb % 4; // Round to DWORD
+ }
+ return cb;
+ }
+
+ ULONG Serialize(BYTE * pBuffer) const
+ {
+ ULONG cb = SerializeSize();
+ *((UNALIGNED ULONG *)pBuffer) = cb;
+ pBuffer += sizeof(ULONG);
+ *((UNALIGNED GUID *)pBuffer) = m_guidFormatId;
+ if (m_pCoMemWaveFormatEx)
+ {
+ pBuffer += sizeof(m_guidFormatId);
+ memcpy(pBuffer, m_pCoMemWaveFormatEx, sizeof(WAVEFORMATEX) + m_pCoMemWaveFormatEx->cbSize);
+ }
+ return cb;
+ }
+
+ HRESULT Deserialize(const BYTE * pBuffer, ULONG * pcbUsed)
+ {
+ HRESULT hr = S_OK;
+ ::CoTaskMemFree(m_pCoMemWaveFormatEx);
+ m_pCoMemWaveFormatEx = NULL;
+ *pcbUsed = *((UNALIGNED ULONG *)pBuffer);
+ pBuffer += sizeof(ULONG);
+ // Misaligment exception is generated for SHx platform.
+ // Marking pointer as UNALIGNED does not help.
+#ifndef _WIN32_WCE
+ m_guidFormatId = *((UNALIGNED GUID *)pBuffer);
+#else
+ memcpy(&m_guidFormatId, pBuffer, sizeof(GUID));
+#endif
+ if (*pcbUsed > sizeof(GUID) + sizeof(ULONG))
+ {
+ pBuffer += sizeof(m_guidFormatId);
+ hr = CoMemCopyWFEX((const WAVEFORMATEX *)pBuffer, &m_pCoMemWaveFormatEx);
+ if (FAILED(hr))
+ {
+ m_guidFormatId = GUID_NULL;
+ }
+ }
+ return hr;
+ }
+
+};
+
+
+
+// Return the default codepage given a LCID.
+// Note some of the newer locales do not have associated Windows codepages. For these, we return UTF-8.
+
+inline UINT SpCodePageFromLcid(LCID lcid)
+{
+ char achCodePage[6];
+
+ return (0 != GetLocaleInfoA(lcid, LOCALE_IDEFAULTANSICODEPAGE, achCodePage, sizeof(achCodePage))) ? atoi(achCodePage) : 65001;
+}
+
+
+/****************************************************************************
+* SpClearEvent *
+*--------------*
+* Description:
+* Helper function that can be used by clients that do not use the CSpEvent
+* class.
+*
+* Returns:
+*
+*****************************************************************************/
+
+inline void SpClearEvent(SPEVENT * pe)
+{
+ if( pe->elParamType != SPEI_UNDEFINED)
+ {
+ if( pe->elParamType == SPET_LPARAM_IS_POINTER ||
+ pe->elParamType == SPET_LPARAM_IS_STRING)
+ {
+ ::CoTaskMemFree((void *)pe->lParam);
+ }
+ else if (pe->elParamType == SPET_LPARAM_IS_TOKEN ||
+ pe->elParamType == SPET_LPARAM_IS_OBJECT)
+ {
+ ((IUnknown*)pe->lParam)->Release();
+ }
+ }
+ memset(pe, 0, sizeof(*pe));
+}
+
+/****************************************************************************
+* SpInitEvent *
+*-------------*
+* Description:
+*
+* Returns:
+*
+*****************************************************************************/
+
+inline void SpInitEvent(SPEVENT * pe)
+{
+ memset(pe, 0, sizeof(*pe));
+}
+
+/****************************************************************************
+* SpEventSerializeSize *
+*----------------------*
+* Description:
+* Computes the required size of a buffer to serialize an event. The caller
+* must specify which type of serialized event is desired -- either SPSERIALIZEDEVENT
+* or SPSERIALIZEDEVENT64.
+*
+* Returns:
+* Size in bytes required to seriailze the event.
+*
+****************************************************************************/
+
+// WCE compiler does not work propertly with template
+#ifndef _WIN32_WCE
+template <class T>
+inline ULONG SpEventSerializeSize(const SPEVENT * pEvent)
+
+{
+ ULONG ulSize = sizeof(T);
+
+#else
+
+inline ULONG SpEventSerializeSize(const SPEVENT * pEvent, ULONG ulSize)
+{
+#endif //_WIN32_WCE
+
+ if( ( pEvent->elParamType == SPET_LPARAM_IS_POINTER ) && pEvent->lParam )
+ {
+ ulSize += ULONG(pEvent->wParam);
+ }
+ else if ((pEvent->elParamType == SPET_LPARAM_IS_STRING) && pEvent->lParam != NULL)
+ {
+ ulSize += (wcslen((WCHAR*)pEvent->lParam) + 1) * sizeof( WCHAR );
+ }
+ else if( pEvent->elParamType == SPET_LPARAM_IS_TOKEN )
+ {
+ CSpDynamicString dstrObjectId;
+ if( ((ISpObjectToken*)(pEvent->lParam))->GetId( &dstrObjectId ) == S_OK )
+ {
+ ulSize += (dstrObjectId.Length() + 1) * sizeof( WCHAR );
+ }
+ }
+ // Round up to nearest DWORD
+ ulSize += 3;
+ ulSize -= ulSize % 4;
+ return ulSize;
+}
+
+/****************************************************************************
+* SpSerializedEventSize *
+*-----------------------*
+* Description:
+* Returns the size, in bytes, used by a serialized event. The caller can
+* pass a pointer to either a SPSERIAILZEDEVENT or SPSERIALIZEDEVENT64 structure.
+*
+* Returns:
+* Number of bytes used by serizlied event
+*
+********************************************************************* RAL ***/
+
+// WCE compiler does not work propertly with template
+#ifndef _WIN32_WCE
+template <class T>
+inline ULONG SpSerializedEventSize(const T * pSerEvent)
+{
+ ULONG ulSize = sizeof(T);
+
+ if( ( pSerEvent->elParamType == SPET_LPARAM_IS_POINTER ) && pSerEvent->SerializedlParam )
+ {
+ ulSize += ULONG(pSerEvent->SerializedwParam);
+ }
+ else if ((pSerEvent->elParamType == SPET_LPARAM_IS_STRING || pSerEvent->elParamType == SPET_LPARAM_IS_TOKEN) &&
+ pSerEvent->SerializedlParam != NULL)
+ {
+ ulSize += (wcslen((WCHAR*)(pSerEvent + 1)) + 1) * sizeof( WCHAR );
+ }
+ // Round up to nearest DWORD
+ ulSize += 3;
+ ulSize -= ulSize % 4;
+ return ulSize;
+}
+
+#else //_WIN32_WCE
+
+inline ULONG SpSerializedEventSize(const SPSERIALIZEDEVENT * pSerEvent, ULONG ulSize)
+{
+ if( ( pSerEvent->elParamType == SPET_LPARAM_IS_POINTER ) && pSerEvent->SerializedlParam )
+ {
+ ulSize += ULONG(pSerEvent->SerializedwParam);
+ }
+ else if ((pSerEvent->elParamType == SPET_LPARAM_IS_STRING || pSerEvent->elParamType == SPET_LPARAM_IS_TOKEN) &&
+ pSerEvent->SerializedlParam != NULL)
+ {
+ ulSize += (wcslen((WCHAR*)(pSerEvent + 1)) + 1) * sizeof( WCHAR );
+ }
+ // Round up to nearest DWORD
+ ulSize += 3;
+ ulSize -= ulSize % 4;
+ return ulSize;
+}
+
+inline ULONG SpSerializedEventSize(const SPSERIALIZEDEVENT64 * pSerEvent, ULONG ulSize)
+{
+ if( ( pSerEvent->elParamType == SPET_LPARAM_IS_POINTER ) && pSerEvent->SerializedlParam )
+ {
+ ulSize += ULONG(pSerEvent->SerializedwParam);
+ }
+ else if ((pSerEvent->elParamType == SPET_LPARAM_IS_STRING || pSerEvent->elParamType == SPET_LPARAM_IS_TOKEN) &&
+ pSerEvent->SerializedlParam != NULL)
+ {
+ ulSize += (wcslen((WCHAR*)(pSerEvent + 1)) + 1) * sizeof( WCHAR );
+ }
+ // Round up to nearest DWORD
+ ulSize += 3;
+ ulSize -= ulSize % 4;
+ return ulSize;
+}
+
+#endif //_WIN32_WCE
+
+/*** CSpEvent helper class
+*
+*/
+class CSpEvent : public SPEVENT
+{
+public:
+ CSpEvent()
+ {
+ SpInitEvent(this);
+ }
+ ~CSpEvent()
+ {
+ SpClearEvent(this);
+ }
+ // If you need to take the address of a CSpEvent that is not const, use the AddrOf() method
+ // which will do debug checking of parameters. If you encounter this problem when calling
+ // GetEvents from an event source, you may want to use the GetFrom() method of this class.
+ const SPEVENT * operator&()
+ {
+ return this;
+ }
+ CSpEvent * AddrOf()
+ {
+ // Note: This method does not ASSERT since we assume the caller knows what they are doing.
+ return this;
+ }
+ void Clear()
+ {
+ SpClearEvent(this);
+ }
+ HRESULT CopyTo(SPEVENT * pDestEvent) const
+ {
+ memcpy(pDestEvent, this, sizeof(*pDestEvent));
+ if ((elParamType == SPET_LPARAM_IS_POINTER) && lParam)
+ {
+ pDestEvent->lParam = (LPARAM)::CoTaskMemAlloc(wParam);
+ if (pDestEvent->lParam)
+ {
+ memcpy((void *)pDestEvent->lParam, (void *)lParam, wParam);
+ }
+ else
+ {
+ pDestEvent->eEventId = SPEI_UNDEFINED;
+ return E_OUTOFMEMORY;
+ }
+ }
+ else if (elParamType == SPET_LPARAM_IS_STRING && lParam != NULL)
+ {
+ pDestEvent->lParam = (LPARAM)::CoTaskMemAlloc((wcslen((WCHAR*)lParam) + 1) * sizeof(WCHAR));
+ if (pDestEvent->lParam)
+ {
+ wcscpy((WCHAR*)pDestEvent->lParam, (WCHAR*)lParam);
+ }
+ else
+ {
+ pDestEvent->eEventId = SPEI_UNDEFINED;
+ return E_OUTOFMEMORY;
+ }
+ }
+ else if (elParamType == SPET_LPARAM_IS_TOKEN ||
+ elParamType == SPET_LPARAM_IS_OBJECT)
+ {
+ ((IUnknown*)lParam)->AddRef();
+ }
+ return S_OK;
+ }
+
+ HRESULT GetFrom(ISpEventSource * pEventSrc)
+ {
+ SpClearEvent(this);
+ return pEventSrc->GetEvents(1, this, NULL);
+ }
+ HRESULT CopyFrom(const SPEVENT * pSrcEvent)
+ {
+ SpClearEvent(this);
+ return static_cast<const CSpEvent *>(pSrcEvent)->CopyTo(this);
+ }
+ void Detach(SPEVENT * pDestEvent = NULL)
+ {
+ if (pDestEvent)
+ {
+ memcpy(pDestEvent, this, sizeof(*pDestEvent));
+ }
+ memset(this, 0, sizeof(*this));
+ }
+
+ template <class T>
+ ULONG SerializeSize() const
+ {
+ return SpEventSerializeSize<T>(this);
+ }
+
+ // Call this method with either SPSERIALIZEDEVENT or SPSERIALIZEDEVENT64
+ template <class T>
+ void Serialize(T * pSerEvent) const
+ {
+ pSerEvent->eEventId = this->eEventId;
+ pSerEvent->elParamType = this->elParamType;
+ pSerEvent->ulStreamNum = this->ulStreamNum;
+ pSerEvent->ullAudioStreamOffset = this->ullAudioStreamOffset;
+ pSerEvent->SerializedwParam = static_cast<ULONG>(this->wParam);
+ pSerEvent->SerializedlParam = static_cast<LONG>(this->lParam);
+ if (lParam)
+ {
+ switch(elParamType)
+ {
+ case SPET_LPARAM_IS_POINTER:
+ memcpy(pSerEvent + 1, (void *)lParam, wParam);
+ pSerEvent->SerializedlParam = sizeof(T);
+ break;
+
+ case SPET_LPARAM_IS_STRING:
+ wcscpy((WCHAR *)(pSerEvent + 1), (WCHAR*)lParam);
+ pSerEvent->SerializedlParam = sizeof(T);
+ break;
+
+ case SPET_LPARAM_IS_TOKEN:
+ {
+ CSpDynamicString dstrObjectId;
+ if( SUCCEEDED( ((ISpObjectToken*)lParam)->GetId( &dstrObjectId ) ) )
+ {
+ pSerEvent->SerializedwParam = (dstrObjectId.Length() + 1) * sizeof( WCHAR );;
+ memcpy( pSerEvent + 1, (void *)dstrObjectId.m_psz, static_cast<ULONG>(pSerEvent->SerializedwParam) );
+ }
+ pSerEvent->SerializedlParam = sizeof(T);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ template <class T>
+ HRESULT Serialize(T ** ppCoMemSerEvent, ULONG * pcbSerEvent) const
+ {
+// WCE compiler does not work propertly with template
+#ifndef _WIN32_WCE
+ *pcbSerEvent = SpEventSerializeSize<T>(this);
+#else
+ *pcbSerEvent = SpEventSerializeSize(this, sizeof(** ppCoMemSerEvent));
+#endif
+ *ppCoMemSerEvent = (T *)::CoTaskMemAlloc(*pcbSerEvent);
+ if (*ppCoMemSerEvent)
+ {
+ Serialize(*ppCoMemSerEvent);
+ return S_OK;
+ }
+ else
+ {
+ *pcbSerEvent = 0;
+ return E_OUTOFMEMORY;
+ }
+ }
+
+
+ // Call this method with either SPSERIALIZEDEVENT or SPSERIALIZEDEVENT64
+ template <class T>
+ HRESULT Deserialize(const T * pSerEvent, ULONG * pcbUsed = NULL)
+ {
+ Clear();
+ HRESULT hr = S_OK;
+ const UNALIGNED T * pTemp = pSerEvent;
+ this->eEventId = pTemp->eEventId;
+ this->elParamType = pTemp->elParamType;
+ this->ulStreamNum = pTemp->ulStreamNum;
+ this->ullAudioStreamOffset = pTemp->ullAudioStreamOffset;
+ this->wParam = static_cast<WPARAM>(pTemp->SerializedwParam);
+ this->lParam = static_cast<LPARAM>(pTemp->SerializedlParam);
+ if (pTemp->SerializedlParam)
+ {
+ ULONG cbAlloc = 0;
+ switch (pTemp->elParamType)
+ {
+ case SPET_LPARAM_IS_POINTER:
+ cbAlloc = static_cast<ULONG>(wParam);
+ break;
+
+ case SPET_LPARAM_IS_STRING:
+ cbAlloc = sizeof(WCHAR) * (1 + wcslen((const WCHAR *)(pTemp + 1)));
+ break;
+
+ case SPET_LPARAM_IS_TOKEN:
+ {
+ ULONG ulDataOffset = ULONG(lParam);
+ hr = SpGetTokenFromId( (const WCHAR*)(pTemp + 1),
+ (ISpObjectToken **)&lParam );
+ wParam = 0;
+ }
+ break;
+ }
+ if (cbAlloc)
+ {
+ void * pvBuff = ::CoTaskMemAlloc(cbAlloc);
+ this->lParam = (LPARAM)pvBuff;
+ if (pvBuff)
+ {
+ memcpy(pvBuff, pTemp + 1, cbAlloc);
+ }
+ else
+ {
+ hr = E_OUTOFMEMORY;
+ }
+ }
+ }
+
+ if( SUCCEEDED( hr ) && pcbUsed )
+ {
+// WCE compiler does not work propertly with template
+#ifndef _WIN32_WCE
+ *pcbUsed = SpEventSerializeSize<T>(this);
+#else
+ *pcbUsed = SpEventSerializeSize(this, sizeof(*pTemp));
+#endif
+ }
+ return hr;
+ }
+
+ //
+ // Helpers for access to events. Performs run-time checks in debug and casts
+ // data to the appropriate types
+ //
+ SPPHONEID Phoneme() const
+ {
+ return (SPPHONEID)LOWORD(lParam);
+ }
+ SPVISEMES Viseme() const
+ {
+ return (SPVISEMES)LOWORD(lParam);
+ }
+ ULONG InputWordPos() const
+ {
+ return ULONG(lParam);
+ }
+ ULONG InputWordLen() const
+ {
+ return ULONG(wParam);
+ }
+ ULONG InputSentPos() const
+ {
+ return ULONG(lParam);
+ }
+ ULONG InputSentLen() const
+ {
+ return ULONG(wParam);
+ }
+ ISpObjectToken * ObjectToken() const
+ {
+ return (ISpObjectToken *)lParam;
+ }
+ ISpObjectToken * VoiceToken() const // More explicit check than ObjectToken()
+ {
+ return ObjectToken();
+ }
+ BOOL PersistVoiceChange() const
+ {
+ return (BOOL)wParam;
+ }
+ IUnknown * Object() const
+ {
+ return (IUnknown*)lParam;
+ }
+ ISpRecoResult * RecoResult() const
+ {
+ return (ISpRecoResult *)Object();
+ }
+ BOOL IsPaused()
+ {
+ return (BOOL)(wParam & SPREF_AutoPause);
+ }
+ BOOL IsEmulated()
+ {
+ return (BOOL)(wParam & SPREF_Emulated);
+ }
+ const WCHAR * String() const
+ {
+ return (const WCHAR*)lParam;
+ }
+ const WCHAR * BookmarkName() const
+ {
+ return String();
+ }
+ const WCHAR * RequestTypeOfUI() const
+ {
+ return String();
+ }
+ SPRECOSTATE RecoState() const
+ {
+ return static_cast<SPRECOSTATE>(wParam);
+ }
+ const WCHAR * PropertyName() const
+ {
+ // Note: Don't use String() method here since in the case of string attributes, the elParamType
+ // field specifies LPARAM_IS_POINTER, but the attribute name IS the first string in this buffer
+ return (const WCHAR*)lParam;
+ }
+ const LONG PropertyNumValue() const
+ {
+ return static_cast<LONG>(wParam);
+ }
+ const WCHAR * PropertyStringValue() const
+ {
+ // Search for the first NULL and return pointer to the char past it.
+ const WCHAR *psz;
+ for (psz = (const WCHAR *)lParam; *psz; psz++) {}
+ return psz + 1;
+ }
+ SPINTERFERENCE Interference() const
+ {
+ return static_cast<SPINTERFERENCE>(lParam);
+ }
+ HRESULT EndStreamResult() const
+ {
+ return static_cast<HRESULT>(lParam);
+ }
+ BOOL InputStreamReleased() const
+ {
+ return (wParam & SPESF_STREAM_RELEASED) ? TRUE : FALSE;
+ }
+};
+
+class CSpPhrasePtr
+{
+public:
+ SPPHRASE * m_pPhrase;
+ CSpPhrasePtr() : m_pPhrase(NULL) {}
+ CSpPhrasePtr(ISpPhrase * pPhraseObj, HRESULT * phr)
+ {
+ *phr = pPhraseObj->GetPhrase(&m_pPhrase);
+ }
+ ~CSpPhrasePtr()
+ {
+ ::CoTaskMemFree(m_pPhrase);
+ }
+ //The assert on operator& usually indicates a bug. If this is really
+ //what is needed, however, take the address of the m_pPhrase member explicitly.
+ SPPHRASE ** operator&()
+ {
+ return &m_pPhrase;
+ }
+ operator SPPHRASE *() const
+ {
+ return m_pPhrase;
+ }
+ SPPHRASE & operator*() const
+ {
+ return *m_pPhrase;
+ }
+ SPPHRASE * operator->() const
+ {
+ return m_pPhrase;
+ }
+ bool operator!() const
+ {
+ return (m_pPhrase == NULL);
+ }
+ void Clear()
+ {
+ if (m_pPhrase)
+ {
+ ::CoTaskMemFree(m_pPhrase);
+ m_pPhrase = NULL;
+ }
+ }
+ HRESULT GetFrom(ISpPhrase * pPhraseObj)
+ {
+ Clear();
+ return pPhraseObj->GetPhrase(&m_pPhrase);
+ }
+};
+
+
+template <class T>
+class CSpCoTaskMemPtr
+{
+public:
+ T * m_pT;
+ CSpCoTaskMemPtr() : m_pT(NULL) {}
+ CSpCoTaskMemPtr(void * pv) : m_pT((T *)pv) {}
+ CSpCoTaskMemPtr(ULONG cElements, HRESULT * phr)
+ {
+ m_pT = (T *)::CoTaskMemAlloc(cElements * sizeof(T));
+ *phr = m_pT ? S_OK : E_OUTOFMEMORY;
+ }
+ ~CSpCoTaskMemPtr()
+ {
+ ::CoTaskMemFree(m_pT);
+ }
+ void Clear()
+ {
+ if (m_pT)
+ {
+ ::CoTaskMemFree(m_pT);
+ m_pT = NULL;
+ }
+ }
+ HRESULT Alloc(ULONG cArrayElements = 1)
+ {
+ m_pT = (T *)::CoTaskMemRealloc(m_pT, sizeof(T) * cArrayElements);
+ return (m_pT ? S_OK : E_OUTOFMEMORY);
+ }
+ void Attach(void * pv)
+ {
+ Clear();
+ m_pT = (T *)pv;
+ }
+ T * Detatch()
+ {
+ T * pT = m_pT;
+ m_pT = NULL;
+ return pT;
+ }
+ //The assert on operator& usually indicates a bug. If this is really
+ //what is needed, however, take the address of the m_pT member explicitly.
+ T ** operator&()
+ {
+ return &m_pT;
+ }
+ T * operator->()
+ {
+ return m_pT;
+ }
+ operator T *()
+ {
+ return m_pT;
+ }
+ bool operator!() const
+ {
+ return (m_pT == NULL);
+ }
+};
+
+/**** Helper function used to create a new phrase object from an array of
+ test words. Each word in the string is converted to a phrase element.
+ This is useful to create a phrase to pass to the EmulateRecognition method.
+ The method can convert standard words as well as words with the
+ "/display_text/lexical_form/pronounciation;" word format.
+ You can also specify the DisplayAttributes for each element if desired.
+ If prgDispAttribs is NULL then the DisplayAttribs for each element default to
+ SPAF_ONE_TRAILING_SPACE. ****/
+/*inline HRESULT CreatePhraseFromWordArray(const WCHAR ** ppWords, ULONG cWords,
+ SPDISPLYATTRIBUTES * prgDispAttribs,
+ ISpPhraseBuilder **ppResultPhrase,
+ LANGID LangId = 0,
+ ISpPhoneConverter *cpPhoneConv = NULL)
+{
+ HRESULT hr = S_OK;
+
+ if ( cWords == 0 || ppWords == NULL || ::IsBadReadPtr(ppWords, sizeof(*ppWords) * cWords ) )
+ {
+ return E_INVALIDARG;
+ }
+
+ if ( prgDispAttribs != NULL && ::IsBadReadPtr(prgDispAttribs, sizeof(*prgDispAttribs) * cWords ) )
+ {
+ return E_INVALIDARG;
+ }
+
+ ULONG cTotalChars = 0;
+ ULONG i;
+ WCHAR** pStringPtrArray = (WCHAR**)::CoTaskMemAlloc( cWords * sizeof(WCHAR *));
+ if ( !pStringPtrArray )
+ {
+ return E_OUTOFMEMORY;
+ }
+ for (i = 0; i < cWords; i++)
+ {
+ cTotalChars += wcslen(ppWords[i])+1;
+ }
+
+ CSpDynamicString dsText(cTotalChars);
+ if(dsText.m_psz == NULL)
+ {
+ ::CoTaskMemFree(pStringPtrArray);
+ return E_OUTOFMEMORY;
+ }
+ CSpDynamicString dsPhoneId(cTotalChars);
+ if(dsPhoneId.m_psz == NULL)
+ {
+ ::CoTaskMemFree(pStringPtrArray);
+ return E_OUTOFMEMORY;
+ }
+ SPPHONEID* pphoneId = dsPhoneId;
+
+ SPPHRASE Phrase;
+ memset(&Phrase, 0, sizeof(Phrase));
+ Phrase.cbSize = sizeof(Phrase);
+
+ if(LangId == 0)
+ {
+ LangId = SpGetUserDefaultUILanguage();
+ }
+
+ if(cpPhoneConv == NULL)
+ {
+ hr = SpCreatePhoneConverter(LangId, NULL, NULL, &cpPhoneConv);
+ if(FAILED(hr))
+ {
+ ::CoTaskMemFree(pStringPtrArray);
+ return hr;
+ }
+ }
+
+ SPPHRASEELEMENT *pPhraseElement = new SPPHRASEELEMENT[cWords];
+ if(pPhraseElement == NULL)
+ {
+ ::CoTaskMemFree(pStringPtrArray);
+ return E_OUTOFMEMORY;
+ }
+ memset(pPhraseElement, 0, sizeof(SPPHRASEELEMENT) * cWords); // !!!
+
+ WCHAR * pText = dsText;
+ for (i = 0; SUCCEEDED(hr) && i < cWords; i++)
+ {
+ WCHAR *p = pText;
+ pStringPtrArray[i] = pText;
+ wcscpy( pText, ppWords[i] );
+ pText += wcslen( p ) + 1;
+
+ if (*p == L'/')
+ {
+ //This is a compound word
+ WCHAR* pszFirstPart = ++p;
+ WCHAR* pszSecondPart = NULL;
+ WCHAR* pszThirdPart = NULL;
+
+ while (*p && *p != L'/')
+ {
+ p++;
+ }
+ if (*p == L'/')
+ {
+ //It means we stop at the second '/'
+ *p = L'\0';
+ pszSecondPart = ++p;
+ while (*p && *p != L'/')
+ {
+ p++;
+ }
+ if (*p == L'/')
+ {
+ //It means we stop at the third '/'
+ *p = L'\0';
+ pszThirdPart = ++p;
+ }
+ }
+
+ pPhraseElement[i].pszDisplayText = pszFirstPart;
+ pPhraseElement[i].pszLexicalForm = pszSecondPart ? pszSecondPart : pszFirstPart;
+
+ if ( pszThirdPart)
+ {
+ hr = cpPhoneConv->PhoneToId(pszThirdPart, pphoneId);
+ if (SUCCEEDED(hr))
+ {
+ pPhraseElement[i].pszPronunciation = pphoneId;
+ pphoneId += wcslen(pphoneId) + 1;
+ }
+ }
+ }
+ else
+ {
+ //It is the simple format, only have one form, use it for everything.
+ pPhraseElement[i].pszDisplayText = NULL;
+ pPhraseElement[i].pszLexicalForm = p;
+ pPhraseElement[i].pszPronunciation = NULL;
+ }
+
+ pPhraseElement[i].bDisplayAttributes = (BYTE)(prgDispAttribs ? prgDispAttribs[i] : SPAF_ONE_TRAILING_SPACE);
+ pPhraseElement[i].RequiredConfidence = SP_NORMAL_CONFIDENCE;
+ pPhraseElement[i].ActualConfidence = SP_NORMAL_CONFIDENCE;
+ }
+
+ Phrase.Rule.ulCountOfElements = cWords;
+ Phrase.pElements = pPhraseElement;
+ Phrase.LangID = LangId;
+
+ ISpPhraseBuilder *cpPhrase;
+ if (SUCCEEDED(hr))
+ {
+ hr = cpPhrase.CoCreateInstance(CLSID_SpPhraseBuilder);
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ hr = cpPhrase->InitFromPhrase(&Phrase);
+ }
+ if (SUCCEEDED(hr))
+ {
+ *ppResultPhrase = cpPhrase.Detach();
+ }
+
+ delete pPhraseElement;
+ ::CoTaskMemFree(pStringPtrArray);
+
+ return hr;
+}*/
+
+/**** Helper function used to create a new phrase object from a
+ test string. Each word in the string is converted to a phrase element.
+ This is useful to create a phrase to pass to the EmulateRecognition method.
+ The method can convert standard words as well as words with the
+ "/display_text/lexical_form/pronounciation;" word format ****/
+/*inline HRESULT CreatePhraseFromText(const WCHAR *pszOriginalText,
+ ISpPhraseBuilder **ppResultPhrase,
+ LANGID LangId = 0,
+ ISpPhoneConverter *cpPhoneConv = NULL)
+{
+ HRESULT hr = S_OK;
+
+ //We first trim the input text
+ CSpDynamicString dsText(pszOriginalText);
+ if(dsText.m_psz == NULL)
+ {
+ return E_OUTOFMEMORY;
+ }
+ dsText.TrimBoth();
+
+ ULONG cWords = 0;
+ BOOL fInCompoundword = FALSE;
+
+ // Set first array pointer (if *p).
+ WCHAR *p = dsText;
+ while (*p)
+ {
+ if( iswspace(*p) && !fInCompoundword)
+ {
+ cWords++;
+ *p++ = L'\0';
+ while (*p && iswspace(*p))
+ {
+ *p++ = L'\0';
+ }
+ // Add new array pointer. Use vector.
+ }
+ else if (*p == L'/' && !fInCompoundword)
+ {
+ fInCompoundword = TRUE;
+ }
+ else if (*p == L';' && fInCompoundword)
+ {
+ fInCompoundword = FALSE;
+ *p++ = L'\0';
+ // Add new array element.
+ }
+ else
+ {
+ p++;
+ }
+ }
+
+ cWords++;
+
+ WCHAR** pStringPtrArray = (WCHAR**)::CoTaskMemAlloc( cWords * sizeof(WCHAR *));
+ if ( !pStringPtrArray )
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ if ( SUCCEEDED( hr ) )
+ {
+ p = dsText;
+ for (ULONG i=0; i<cWords; i++)
+ {
+ pStringPtrArray[i] = p;
+ p += wcslen(p)+1;
+ }
+
+ hr = CreatePhraseFromWordArray((const WCHAR **)pStringPtrArray, cWords, NULL, ppResultPhrase, LangId, cpPhoneConv);
+
+ ::CoTaskMemFree(pStringPtrArray);
+ }
+ return hr;
+}*/
+
+#endif /* This must be the last line in the file */
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
new file mode 100644
index 0000000..cae1e6b
--- /dev/null
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -0,0 +1,109 @@
+/* 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.
+ *
+ */
+
+// Disable symbol overrides so that we can use system headers.
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#if defined(USE_WINDOWS_TTS)
+#include <basetyps.h>
+#include <windows.h>
+#include <Servprov.h>
+#include <sapi.h>
+#include "backends/text-to-speech/windows/sphelper-scummvm.h"
+
+#include "backends/text-to-speech/windows/windows-text-to-speech.h"
+
+
+#include "common/translation.h"
+#include "common/debug.h"
+#include "common/system.h"
+#include "common/ustr.h"
+#include "common/config-manager.h"
+
+WindowsTextToSpeechManager::WindowsTextToSpeechManager() {
+ debug("hi");
+ init();
+}
+
+void WindowsTextToSpeechManager::init() {
+}
+
+WindowsTextToSpeechManager::~WindowsTextToSpeechManager() {
+}
+
+bool WindowsTextToSpeechManager::say(Common::String str) {
+ return true;
+}
+
+bool WindowsTextToSpeechManager::stop() {
+ return true;
+}
+
+bool WindowsTextToSpeechManager::pause() {
+ return true;
+}
+
+bool WindowsTextToSpeechManager::resume() {
+ return true;
+}
+
+bool WindowsTextToSpeechManager::isSpeaking() {
+ return true;
+}
+
+bool WindowsTextToSpeechManager::isPaused() {
+ return true;
+}
+
+bool WindowsTextToSpeechManager::isReady() {
+ return true;
+}
+
+void WindowsTextToSpeechManager::setVoice(unsigned index) {
+}
+
+void WindowsTextToSpeechManager::setRate(int rate) {
+}
+
+void WindowsTextToSpeechManager::setPitch(int pitch) {
+}
+
+void WindowsTextToSpeechManager::setVolume(unsigned volume) {
+}
+
+int WindowsTextToSpeechManager::getVolume() {
+ return 0;
+}
+
+void WindowsTextToSpeechManager::setLanguage(Common::String language) {
+}
+
+void WindowsTextToSpeechManager::createVoice(int typeNumber, Common::TTSVoice::Gender gender, char *description) {
+}
+
+void WindowsTextToSpeechManager::updateVoices() {
+
+}
+
+#endif
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
new file mode 100644
index 0000000..a00ed58
--- /dev/null
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -0,0 +1,67 @@
+/* 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 BACKENDS_TEXT_TO_SPEECH_WINDOWS_H
+#define BACKENDS_TEXT_TO_SPEECH_WINDOWS_H
+
+#include "common/scummsys.h"
+
+#if defined(USE_WINDOWS_TTS)
+
+#include "common/text-to-speech.h"
+#include "common/str.h"
+
+class WindowsTextToSpeechManager : public Common::TextToSpeechManager {
+public:
+ WindowsTextToSpeechManager();
+ virtual ~WindowsTextToSpeechManager();
+
+ virtual bool say(Common::String str);
+
+ virtual bool stop();
+ virtual bool pause();
+ virtual bool resume();
+
+ virtual bool isSpeaking();
+ virtual bool isPaused();
+ virtual bool isReady();
+
+ virtual void setVoice(unsigned index);
+
+ virtual void setRate(int rate);
+
+ virtual void setPitch(int pitch);
+
+ virtual void setVolume(unsigned volume);
+ virtual int getVolume();
+
+ virtual void setLanguage(Common::String language);
+
+private:
+ void init();
+ virtual void updateVoices();
+ void createVoice(int typeNumber, Common::TTSVoice::Gender, char *description);
+};
+
+#endif
+
+#endif // BACKENDS_UPDATES_WINDOWS_H
diff --git a/configure b/configure
index 503766d..096e11d 100755
--- a/configure
+++ b/configure
@@ -168,6 +168,7 @@ _dialogs=auto
_iconv=auto
_tts=auto
_linux_tts=no
+_windows_tts=no
# Default option behavior yes/no
_debug_build=auto
_release_build=auto
@@ -5375,6 +5376,13 @@ else
define_in_config_if_yes $_linux_tts 'USE_LINUX_TTS'
append_var LIBS '-lspeechd'
;;
+ mingw*)
+ echo "win32"
+ _tts=yes
+ _windows_tts=yes
+ define_in_config_if_yes $_windows_tts 'USE_WINDOWS_TTS'
+ append_var LIBS '-lsapi -lole32'
+ ;;
*)
echo "no"
_tts=no
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 666e079..c77af7c 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -626,6 +626,8 @@ void GuiManager::setLastMousePos(int16 x, int16 y) {
#ifdef USE_TTS
void GuiManager::initTextToSpeech() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan == nullptr)
+ return;
#ifdef USE_TRANSLATION
Common::String currentLanguage = TransMan.getCurrentLanguage();
if (currentLanguage != "C") {
diff --git a/gui/options.cpp b/gui/options.cpp
index 3195694..71f8cad 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -1800,7 +1800,10 @@ void GlobalOptionsDialog::build() {
_ttsCheckbox->setState(false);
_ttsVoiceSelectionPopUp = new PopUpWidget(tab, "GlobalOptions_Accessibility.TTSVoiceSelection");
- Common::Array<Common::TTSVoice> voices = g_system->getTextToSpeechManager()->getVoicesArray();
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ Common::Array<Common::TTSVoice> voices;
+ if (ttsMan != nullptr)
+ voices = ttsMan->getVoicesArray();
for(unsigned i = 0; i < voices.size(); i++) {
_ttsVoiceSelectionPopUp->appendEntry(voices[i].getDescription(), i);
diff --git a/gui/widget.cpp b/gui/widget.cpp
index 3269a5f..f870eb5 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -260,7 +260,10 @@ void Widget::read(Common::String str) {
#ifdef USE_TTS
if (ConfMan.hasKey("tts_enabled", "scummvm") &&
ConfMan.getBool("tts_enabled", "scummvm")) {
- g_system->getTextToSpeechManager()->say(str);
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan == nullptr)
+ return;
+ ttsMan->say(str);
}
#endif
}
diff --git a/gui/widgets/popup.cpp b/gui/widgets/popup.cpp
index fe51b39..532127a 100644
--- a/gui/widgets/popup.cpp
+++ b/gui/widgets/popup.cpp
@@ -227,11 +227,8 @@ void PopUpDialog::read(Common::String str) {
#ifdef USE_TTS
if (ConfMan.hasKey("tts_enabled", "scummvm") &&
ConfMan.getBool("tts_enabled", "scummvm")) {
- int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
- if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm"))
- volume = 0;
- g_system->getTextToSpeechManager()->setVolume(volume);
- g_system->getTextToSpeechManager()->say(str);
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->say(str);
}
#endif
}
Commit: d2d34a4ecaabd7d436bfab942c9003d0e478ad2a
https://github.com/scummvm/scummvm/commit/d2d34a4ecaabd7d436bfab942c9003d0e478ad2a
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Start implementing windows TTS
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index cae1e6b..aad50c6 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -41,15 +41,30 @@
#include "common/ustr.h"
#include "common/config-manager.h"
-WindowsTextToSpeechManager::WindowsTextToSpeechManager() {
- debug("hi");
+ISpVoice *_voice;
+
+WindowsTextToSpeechManager::WindowsTextToSpeechManager()
+ : _speechState(BROKEN){
init();
}
void WindowsTextToSpeechManager::init() {
+ if (FAILED(::CoInitialize(NULL)))
+ return;
+
+ HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&_voice);
+ if (!SUCCEEDED(hr)) {
+ warning("Could not initialize TTS voice");
+ return;
+ }
+ updateVoices();
+ _speechState = READY;
}
WindowsTextToSpeechManager::~WindowsTextToSpeechManager() {
+ if (_voice)
+ _voice->Release();
+ ::CoUninitialize();
}
bool WindowsTextToSpeechManager::say(Common::String str) {
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index a00ed58..5daf57c 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -32,6 +32,13 @@
class WindowsTextToSpeechManager : public Common::TextToSpeechManager {
public:
+ enum SpeechState {
+ READY,
+ PAUSED,
+ SPEAKING,
+ BROKEN
+ };
+
WindowsTextToSpeechManager();
virtual ~WindowsTextToSpeechManager();
@@ -60,6 +67,7 @@ private:
void init();
virtual void updateVoices();
void createVoice(int typeNumber, Common::TTSVoice::Gender, char *description);
+ SpeechState _speechState;
};
#endif
Commit: 318c6d7ec6e5562d0fd3e9d70386d0fcde86cf12
https://github.com/scummvm/scummvm/commit/318c6d7ec6e5562d0fd3e9d70386d0fcde86cf12
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Finish implementing the Windows TTS manager
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
common/text-to-speech.cpp
common/text-to-speech.h
gui/options.cpp
gui/widgets/popup.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 9f330bc..1e92742 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -230,4 +230,25 @@ void LinuxTextToSpeechManager::updateVoices() {
}
+bool LinuxTextToSpeechManager::popState() {
+ if (_ttsState->_next == nullptr)
+ return true;
+
+ for (Common::TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
+ free(i->getData());
+ }
+
+ Common::TTSState *oldState = _ttsState;
+ _ttsState = _ttsState->_next;
+
+ delete oldState;
+
+ setLanguage(_ttsState->_language);
+ setPitch(_ttsState->_pitch);
+ setVolume(_ttsState->_volume);
+ setRate(_ttsState->_rate);
+ return false;
+}
+
+
#endif
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index d08da49..cd3fcf6 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -63,6 +63,8 @@ public:
virtual void setLanguage(Common::String language);
+ virtual bool popState();
+
void updateState(SpeechState state);
private:
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index aad50c6..edadf5e 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -31,6 +31,7 @@
#include <Servprov.h>
#include <sapi.h>
#include "backends/text-to-speech/windows/sphelper-scummvm.h"
+#include "backends/platform/sdl/win32/win32_wrapper.h"
#include "backends/text-to-speech/windows/windows-text-to-speech.h"
@@ -43,82 +44,307 @@
ISpVoice *_voice;
+// We need this pointer to be able to stop speech immediately.
+ISpAudio *_audio;
+
WindowsTextToSpeechManager::WindowsTextToSpeechManager()
: _speechState(BROKEN){
init();
}
void WindowsTextToSpeechManager::init() {
+ // init COM
if (FAILED(::CoInitialize(NULL)))
return;
+ // init voice
HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&_voice);
if (!SUCCEEDED(hr)) {
warning("Could not initialize TTS voice");
return;
}
- updateVoices();
- _speechState = READY;
+ setLanguage("en");
+
+ // init audio
+ CSpStreamFormat format;
+ format.AssignFormat(SPSF_11kHz8BitMono);
+ ISpObjectToken *pToken;
+ hr = SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOOUT, &pToken);
+ if (FAILED(hr)) {
+ warning("Could not initialize TTS audio");
+ return;
+ }
+ pToken->CreateInstance(NULL, CLSCTX_ALL, IID_ISpAudio, (void **)&_audio);
+ _audio->SetFormat(format.FormatId(), format.WaveFormatExPtr());
+ _voice->SetOutput(_audio, FALSE);
+
+ if(_ttsState->_availaibleVoices.size() > 0)
+ _speechState = READY;
+ else
+ _speechState = NO_VOICE;
}
WindowsTextToSpeechManager::~WindowsTextToSpeechManager() {
+ freeVoices();
if (_voice)
_voice->Release();
::CoUninitialize();
}
bool WindowsTextToSpeechManager::say(Common::String str) {
- return true;
+ if(_speechState == BROKEN || _speechState == NO_VOICE) {
+ warning("The tts cannot speak in this state");
+ return true;
+ }
+ if (isPaused()) {
+ resume();
+ }
+ _audio->SetState(SPAS_STOP, 0);
+ _audio->SetState(SPAS_RUN, 0);
+ // We have to set the pitch by prepending xml code at the start of the said string;
+ Common::String pitch= Common::String::format("<pitch absmiddle=\"%d\">", _ttsState->_pitch);
+ str.replace((uint32)0, 0, pitch);
+
+ WCHAR *strW = Win32::ansiToUnicode(str.c_str());
+ bool result = _voice->Speak(strW, SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL) != S_OK;
+ free(strW);
+ _speechState = SPEAKING;
+ return result;
}
bool WindowsTextToSpeechManager::stop() {
- return true;
+ if(_speechState == BROKEN || _speechState == NO_VOICE)
+ return true;
+ if (isPaused())
+ resume();
+ _audio->SetState(SPAS_STOP, 0);
+ _audio->SetState(SPAS_RUN, 0);
+ _voice->Speak(NULL, SPF_PURGEBEFORESPEAK | SPF_ASYNC | SPF_IS_NOT_XML, 0);
+ _speechState = READY;
+ return false;
}
bool WindowsTextToSpeechManager::pause() {
- return true;
+ if(_speechState == BROKEN || _speechState == NO_VOICE)
+ return true;
+ if (isPaused())
+ return false;
+ _voice->Pause();
+ _speechState = PAUSED;
+ return false;
}
bool WindowsTextToSpeechManager::resume() {
- return true;
+ if(_speechState == BROKEN || _speechState == NO_VOICE)
+ return true;
+ if (!isPaused())
+ return false;
+ _voice->Resume();
+ if (isSpeaking())
+ _speechState = SPEAKING;
+ else
+ _speechState = READY;
+ return false;
}
bool WindowsTextToSpeechManager::isSpeaking() {
- return true;
+ if(_speechState == BROKEN || _speechState == NO_VOICE)
+ return false;
+ SPVOICESTATUS eventStatus;
+ _voice->GetStatus(&eventStatus, NULL);
+ return eventStatus.dwRunningState == SPRS_IS_SPEAKING;
}
bool WindowsTextToSpeechManager::isPaused() {
- return true;
+ return _speechState == PAUSED;
}
bool WindowsTextToSpeechManager::isReady() {
- return true;
+ if(_speechState == BROKEN || _speechState == NO_VOICE)
+ return false;
+ if (_speechState != PAUSED && !isSpeaking())
+ return true;
+ else
+ return false;
}
void WindowsTextToSpeechManager::setVoice(unsigned index) {
+ if(_speechState == BROKEN || _speechState == NO_VOICE)
+ return;
+ _voice->SetVoice((ISpObjectToken *) _ttsState->_availaibleVoices[index].getData());
}
void WindowsTextToSpeechManager::setRate(int rate) {
+ if(_speechState == BROKEN || _speechState == NO_VOICE)
+ return;
+ assert(rate >= -10 && rate <= 10);
+ _voice->SetRate(rate);
+ _ttsState->_rate = rate;
}
void WindowsTextToSpeechManager::setPitch(int pitch) {
+ if(_speechState == BROKEN || _speechState == NO_VOICE)
+ return;
+ _ttsState->_pitch = pitch;
}
void WindowsTextToSpeechManager::setVolume(unsigned volume) {
+ if(_speechState == BROKEN || _speechState == NO_VOICE)
+ return;
+ assert(volume <= 100);
+ _voice->SetVolume(volume);
+ _ttsState->_volume = volume;
}
int WindowsTextToSpeechManager::getVolume() {
- return 0;
+ return _ttsState->_volume;
+}
+
+void WindowsTextToSpeechManager::freeVoices() {
+ for(Common::TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
+ ISpObjectToken *voiceData = (ISpObjectToken *)i->getData();
+ voiceData->Release();
+ }
+ _ttsState->_availaibleVoices.clear();
}
void WindowsTextToSpeechManager::setLanguage(Common::String language) {
+ if (language == "C")
+ language = "en";
+ _ttsState->_language = language;
+ updateVoices();
+ setVoice(0);
}
-void WindowsTextToSpeechManager::createVoice(int typeNumber, Common::TTSVoice::Gender gender, char *description) {
+void WindowsTextToSpeechManager::createVoice(void *cpVoiceToken) {
+ ISpObjectToken *voiceToken = (ISpObjectToken *) cpVoiceToken;
+
+ // description
+ WCHAR *descW;
+ SpGetDescription(voiceToken, &descW);
+ char *buffer = Win32::unicodeToAnsi(descW);
+ Common::String desc = buffer;
+ free(buffer);
+
+ // voice attributes
+ HRESULT hr = S_OK;
+ ISpDataKey *key = nullptr;
+ hr = voiceToken->OpenKey(L"Attributes", &key);
+
+ if (FAILED(hr)) {
+ voiceToken->Release();
+ warning("Could not open attribute key for voice: %s", desc.c_str());
+ return;
+ }
+ LPWSTR data;
+
+ // language
+ hr = key->GetStringValue(L"Language", &data);
+ if (FAILED(hr)) {
+ voiceToken->Release();
+ warning("Could not get the language attribute for voice: %s", desc.c_str());
+ return;
+ }
+ buffer = Win32::unicodeToAnsi(data);
+ Common::String language = lcidToLocale(buffer);
+ free(buffer);
+ CoTaskMemFree(data);
+
+ // only get the voices for the current language
+ if (language != _ttsState->_language) {
+ voiceToken->Release();
+ return;
+ }
+
+ // gender
+ hr = key->GetStringValue(L"Gender", &data);
+ if (FAILED(hr)) {
+ voiceToken->Release();
+ warning("Could not get the gender attribute for voice: %s", desc.c_str());
+ return;
+ }
+ buffer = Win32::unicodeToAnsi(data);
+ Common::TTSVoice::Gender gender = !strcmp(buffer, "Male") ? Common::TTSVoice::MALE : Common::TTSVoice::FEMALE;
+ free(buffer);
+ CoTaskMemFree(data);
+
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(gender, (void *) voiceToken, desc));
+}
+
+int strToInt(Common::String str) {
+ str.toUppercase();
+ int result = 0;
+ for(unsigned i = 0; i < str.size(); i++) {
+ if (str[i] < '0' || (str[i] > '9' && str[i] < 'A') || str[i] > 'F')
+ break;
+ int num = (str[i] <= '9') ? str[i] - '0' : str[i] - 55;
+ result = result * 16 + num;
+ }
+ return result;
+}
+
+Common::String WindowsTextToSpeechManager::lcidToLocale(Common::String lcid) {
+ LCID locale = strToInt(lcid);
+ int nchars = GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, NULL, 0);
+ wchar_t *languageCode = new wchar_t[nchars];
+ GetLocaleInfoW(locale, LOCALE_SISO639LANGNAME, languageCode, nchars);
+ char *resultTmp = Win32::unicodeToAnsi(languageCode);
+ Common::String result = resultTmp;
+ delete[] languageCode;
+ free(resultTmp);
+ return result;
}
void WindowsTextToSpeechManager::updateVoices() {
+ freeVoices();
+ HRESULT hr = S_OK;
+ ISpObjectToken *cpVoiceToken = nullptr;
+ IEnumSpObjectTokens *cpEnum = nullptr;
+ unsigned long ulCount = 0;
+
+ hr = SpEnumTokens(SPCAT_VOICES, NULL, NULL, &cpEnum);
+ if (SUCCEEDED(hr)) {
+ hr = cpEnum->GetCount(&ulCount);
+ }
+ _voice->SetVolume(0);
+ while (SUCCEEDED(hr) && ulCount--) {
+ hr = cpEnum->Next(1, &cpVoiceToken, NULL);
+ _voice->SetVoice(cpVoiceToken);
+ if(SUCCEEDED(_voice->Speak(L"hi, this is test", SPF_PURGEBEFORESPEAK | SPF_ASYNC | SPF_IS_NOT_XML, 0)))
+ createVoice(cpVoiceToken);
+ else
+ cpVoiceToken->Release();
+ }
+ _voice->SetVolume(_ttsState->_volume);
+ cpEnum->Release();
+
+ if(_ttsState->_availaibleVoices.size() == 0) {
+ _speechState = NO_VOICE;
+ warning("No voice is availaible");
+ } else if (_speechState == NO_VOICE)
+ _speechState = READY;
+}
+
+bool WindowsTextToSpeechManager::popState() {
+ if (_ttsState->_next == nullptr)
+ return true;
+
+ for (Common::TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
+ ISpObjectToken *voiceToken = (ISpObjectToken *) i->getData();
+ voiceToken->Release();
+ }
+
+ Common::TTSState *oldState = _ttsState;
+ _ttsState = _ttsState->_next;
+
+ delete oldState;
+ setLanguage(_ttsState->_language);
+ setPitch(_ttsState->_pitch);
+ setVolume(_ttsState->_volume);
+ setRate(_ttsState->_rate);
+ setVoice(_ttsState->_activeVoice);
+ return false;
}
#endif
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index 5daf57c..03a1806 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -36,7 +36,8 @@ public:
READY,
PAUSED,
SPEAKING,
- BROKEN
+ BROKEN,
+ NO_VOICE
};
WindowsTextToSpeechManager();
@@ -63,10 +64,14 @@ public:
virtual void setLanguage(Common::String language);
+ virtual bool popState();
+
private:
void init();
virtual void updateVoices();
- void createVoice(int typeNumber, Common::TTSVoice::Gender, char *description);
+ void createVoice(void *cpVoiceToken);
+ void freeVoices();
+ Common::String lcidToLocale(Common::String lcid);
SpeechState _speechState;
};
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
index 459d470..e8e0d9b 100644
--- a/common/text-to-speech.cpp
+++ b/common/text-to-speech.cpp
@@ -59,25 +59,5 @@ void TextToSpeechManager::pushState() {
updateVoices();
}
-bool TextToSpeechManager::popState() {
- if (_ttsState->_next == nullptr)
- return true;
-
- for (TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
- free(i->_data);
- }
-
- TTSState *oldState = _ttsState;
- _ttsState = _ttsState->_next;
-
- delete oldState;
-
- setLanguage(_ttsState->_language);
- setPitch(_ttsState->_pitch);
- setVolume(_ttsState->_volume);
- setRate(_ttsState->_rate);
- return false;
-}
-
}
#endif
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 4e66cf2..39d49e9 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -109,7 +109,7 @@ public:
Array<TTSVoice> getVoicesArray() { return _ttsState->_availaibleVoices; }
void pushState();
- bool popState();
+ virtual bool popState() { return true; }
protected:
TTSState *_ttsState;
diff --git a/gui/options.cpp b/gui/options.cpp
index 71f8cad..b82bfc6 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -2161,6 +2161,7 @@ void GlobalOptionsDialog::apply() {
guiLang.setChar('\0', 2);
ttsMan->setLanguage(guiLang);
}
+ _ttsVoiceSelectionPopUp->setSelectedTag(0);
}
int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm"))
diff --git a/gui/widgets/popup.h b/gui/widgets/popup.h
index a898479..3ccf878 100644
--- a/gui/widgets/popup.h
+++ b/gui/widgets/popup.h
@@ -77,7 +77,7 @@ public:
uint32 getSelectedTag() const { return (_selectedItem >= 0) ? _entries[_selectedItem].tag : (uint32)-1; }
// const String& getSelectedString() const { return (_selectedItem >= 0) ? _entries[_selectedItem].name : String::emptyString; }
- void handleMouseEntered(int button) { read(_entries[_selectedItem].name); setFlags(WIDGET_HILITED); markAsDirty(); }
+ void handleMouseEntered(int button) { if (_selectedItem != -1) read(_entries[_selectedItem].name); setFlags(WIDGET_HILITED); markAsDirty(); }
void handleMouseLeft(int button) { clearFlags(WIDGET_HILITED); markAsDirty(); }
virtual void reflowLayout();
Commit: 33f8aadfeb81ead60ce1264f0af681dcdbd4931f
https://github.com/scummvm/scummvm/commit/33f8aadfeb81ead60ce1264f0af681dcdbd4931f
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add age to TTSVoice
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.cpp
common/text-to-speech.h
gui/options.cpp
gui/widgets/popup.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 1e92742..758996d 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -199,10 +199,10 @@ void LinuxTextToSpeechManager::setLanguage(Common::String language) {
setVoice(_ttsState->_activeVoice);
}
-void LinuxTextToSpeechManager::createVoice(int typeNumber, Common::TTSVoice::Gender gender, char *description) {
+void LinuxTextToSpeechManager::createVoice(int typeNumber, Common::TTSVoice::Gender gender, Common::TTSVoice::Age age, char *description) {
SPDVoiceType *type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
*type = static_cast<SPDVoiceType>(typeNumber);
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(gender, (void *) type, description));
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(gender, age, (void *) type, description));
}
void LinuxTextToSpeechManager::updateVoices() {
@@ -219,14 +219,14 @@ void LinuxTextToSpeechManager::updateVoices() {
char **voiceInfo = spd_list_voices(_connection);
- createVoice(SPD_MALE1, Common::TTSVoice::MALE, voiceInfo[0]);
- createVoice(SPD_MALE2, Common::TTSVoice::MALE, voiceInfo[1]);
- createVoice(SPD_MALE3, Common::TTSVoice::MALE, voiceInfo[2]);
- createVoice(SPD_FEMALE1, Common::TTSVoice::FEMALE, voiceInfo[3]);
- createVoice(SPD_FEMALE2, Common::TTSVoice::FEMALE, voiceInfo[4]);
- createVoice(SPD_FEMALE3, Common::TTSVoice::FEMALE, voiceInfo[5]);
- createVoice(SPD_CHILD_MALE, Common::TTSVoice::MALE, voiceInfo[6]);
- createVoice(SPD_CHILD_FEMALE, Common::TTSVoice::FEMALE, voiceInfo[7]);
+ createVoice(SPD_MALE1, Common::TTSVoice::MALE, Common::TTSVoice::ADULT, voiceInfo[0]);
+ createVoice(SPD_MALE2, Common::TTSVoice::MALE, Common::TTSVoice::ADULT, voiceInfo[1]);
+ createVoice(SPD_MALE3, Common::TTSVoice::MALE, Common::TTSVoice::ADULT, voiceInfo[2]);
+ createVoice(SPD_FEMALE1, Common::TTSVoice::FEMALE, Common::TTSVoice::ADULT, voiceInfo[3]);
+ createVoice(SPD_FEMALE2, Common::TTSVoice::FEMALE, Common::TTSVoice::ADULT, voiceInfo[4]);
+ createVoice(SPD_FEMALE3, Common::TTSVoice::FEMALE, Common::TTSVoice::ADULT, voiceInfo[5]);
+ createVoice(SPD_CHILD_MALE, Common::TTSVoice::MALE, Common::TTSVoice::CHILD, voiceInfo[6]);
+ createVoice(SPD_CHILD_FEMALE, Common::TTSVoice::FEMALE, Common::TTSVoice::CHILD, voiceInfo[7]);
}
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index cd3fcf6..64c2371 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -70,7 +70,7 @@ public:
private:
void init();
virtual void updateVoices();
- void createVoice(int typeNumber, Common::TTSVoice::Gender, char *description);
+ void createVoice(int typeNumber, Common::TTSVoice::Gender, Common::TTSVoice::Age, char *description);
SpeechState _speechState;
};
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index edadf5e..6f621ed 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -49,6 +49,7 @@ ISpAudio *_audio;
WindowsTextToSpeechManager::WindowsTextToSpeechManager()
: _speechState(BROKEN){
+ ConfMan.setInt("tts_voice", 0);
init();
}
@@ -268,7 +269,19 @@ void WindowsTextToSpeechManager::createVoice(void *cpVoiceToken) {
free(buffer);
CoTaskMemFree(data);
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(gender, (void *) voiceToken, desc));
+ // age
+ hr = key->GetStringValue(L"Age", &data);
+ if (FAILED(hr)) {
+ voiceToken->Release();
+ warning("Could not get the age attribute for voice: %s", desc.c_str());
+ return;
+ }
+ buffer = Win32::unicodeToAnsi(data);
+ Common::TTSVoice::Age age = !strcmp(buffer, "Adult") ? Common::TTSVoice::ADULT : Common::TTSVoice::UNKNOWN_AGE;
+ free(buffer);
+ CoTaskMemFree(data);
+
+ _ttsState->_availaibleVoices.push_back(Common::TTSVoice(gender, Common::TTSVoice::ADULT, (void *) voiceToken, desc));
}
int strToInt(Common::String str) {
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 39d49e9..1283dfe 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -38,26 +38,37 @@ class TTSVoice {
enum Gender {
MALE,
FEMALE,
- UNKNOWN
+ UNKNOWN_GENDER
+ };
+
+ enum Age {
+ CHILD,
+ ADULT,
+ UNKNOWN_AGE
};
public:
TTSVoice()
- : _gender(UNKNOWN)
+ : _gender(UNKNOWN_GENDER)
+ , _age(UNKNOWN_AGE)
, _data(nullptr)
, _description("") {}
- TTSVoice(Gender gender, void *data, String description)
+ TTSVoice(Gender gender, Age age, void *data, String description)
: _gender(gender)
+ , _age(age)
, _data(data)
, _description(description) {}
Gender getGender() { return _gender; };
void setGender(Gender gender) { _gender = gender; };
+ Age getAge() { return _age; };
+ void setAge(Age age) { _age = age; };
void setData(void *data) { _data = data; };
void *getData() { return _data; };
String getDescription() { return _description; };
protected:
Gender _gender;
+ Age _age;
void *_data;
String _description;
};
diff --git a/gui/options.cpp b/gui/options.cpp
index b82bfc6..507ae34 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -1808,8 +1808,10 @@ void GlobalOptionsDialog::build() {
for(unsigned i = 0; i < voices.size(); i++) {
_ttsVoiceSelectionPopUp->appendEntry(voices[i].getDescription(), i);
}
+ if (voices.size() == 0)
+ _ttsVoiceSelectionPopUp->appendEntry("None", 0);
- if (ConfMan.hasKey("tts_voice"))
+ if (ConfMan.hasKey("tts_voice") && (unsigned) ConfMan.getInt("tts_voice", _domain) < voices.size())
_ttsVoiceSelectionPopUp->setSelectedTag(ConfMan.getInt("tts_voice", _domain)) ;
else
_ttsVoiceSelectionPopUp->setSelectedTag(0);
diff --git a/gui/widgets/popup.cpp b/gui/widgets/popup.cpp
index 532127a..2fce6e1 100644
--- a/gui/widgets/popup.cpp
+++ b/gui/widgets/popup.cpp
@@ -213,7 +213,7 @@ void PopUpDialog::handleMouseMoved(int x, int y, int button) {
// ...and update the selection accordingly
setSelection(item);
- if (_lastRead != item) {
+ if (_lastRead != item && _popUpBoss->_entries.size() > 0 && item != -1) {
read(_popUpBoss->_entries[item].name);
_lastRead = item;
}
Commit: ce64528129d4a1ba51e339bd8be3548c4d8cc3bf
https://github.com/scummvm/scummvm/commit/ce64528129d4a1ba51e339bd8be3548c4d8cc3bf
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Make sure that TTS lang matches transMan lang
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
base/main.cpp
gui/ThemeEngine.cpp
gui/gui-manager.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 758996d..633c107 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -91,7 +91,11 @@ void LinuxTextToSpeechManager::init() {
updateVoices();
_ttsState->_activeVoice = 0;
- setLanguage(Common::String("en"));
+#ifdef USE_TRANSLATION
+ setLanguage(TransMan.getCurrentLanguage());
+#else
+ setLanguage("en");
+#endif
}
LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
diff --git a/base/main.cpp b/base/main.cpp
index 352dbf9..656a24b 100644
--- a/base/main.cpp
+++ b/base/main.cpp
@@ -51,6 +51,7 @@
#include "common/textconsole.h"
#include "common/tokenizer.h"
#include "common/translation.h"
+#include "common/text-to-speech.h"
#include "common/osd_message_queue.h"
#include "gui/gui-manager.h"
@@ -260,8 +261,16 @@ static Common::Error runGame(const Plugin *plugin, OSystem &system, const Common
&& ConfMan.getBool("gui_use_game_language")
&& ConfMan.hasKey("language")) {
TransMan.setLanguage(ConfMan.get("language"));
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan;
+ if ((ttsMan = g_system->getTextToSpeechManager()) != nullptr) {
+ Common::String language = ConfMan.get("language");
+ language.setChar(2, '\0');
+ ttsMan->setLanguage(language);
+ }
+#endif // USE_TTS
}
-#endif
+#endif // USE_TRANSLATION
// Initialize any game-specific keymaps
engine->initKeymap();
@@ -289,7 +298,15 @@ static Common::Error runGame(const Plugin *plugin, OSystem &system, const Common
#ifdef USE_TRANSLATION
TransMan.setLanguage(previousLanguage);
-#endif
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan;
+ if ((ttsMan = g_system->getTextToSpeechManager()) != nullptr) {
+ Common::String language = ConfMan.get("language");
+ language.setChar(2, '\0');
+ ttsMan->setLanguage(language);
+ }
+#endif // USE_TTS
+#endif // USE_TRANSLATION
// Return result (== 0 means no error)
return result;
diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp
index 598d9c2..dd074cc 100644
--- a/gui/ThemeEngine.cpp
+++ b/gui/ThemeEngine.cpp
@@ -530,7 +530,12 @@ bool ThemeEngine::addFont(TextData textId, const Common::String &file, const Com
error("Couldn't load font '%s'/'%s'", file.c_str(), scalableFile.c_str());
#ifdef USE_TRANSLATION
TransMan.setLanguage("C");
-#endif
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan;
+ if ((ttsMan = g_system->getTextToSpeechManager()) != nullptr)
+ ttsMan->setLanguage("en");
+#endif // USE_TTS
+#endif // USE_TRANSLATION
return false; // fall-back attempt failed
}
// Success in fall-back attempt to standard (non-localized) font.
@@ -538,7 +543,12 @@ bool ThemeEngine::addFont(TextData textId, const Common::String &file, const Com
// FIXME If we return false anyway why would we attempt the fall-back in the first place?
#ifdef USE_TRANSLATION
TransMan.setLanguage("C");
-#endif
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan;
+ if ((ttsMan = g_system->getTextToSpeechManager()) != nullptr)
+ ttsMan->setLanguage("en");
+#endif // USE_TTS
+#endif // USE_TRANSLATION
// Returning true here, would allow falling back to standard fonts for the missing ones,
// but that leads to "garbage" glyphs being displayed on screen for non-Latin languages
return false;
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index c77af7c..b149908 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -632,7 +632,6 @@ void GuiManager::initTextToSpeech() {
Common::String currentLanguage = TransMan.getCurrentLanguage();
if (currentLanguage != "C") {
currentLanguage.setChar('\0', 2);
- debug("%s", TransMan.getCurrentCharset().c_str());
ttsMan->setLanguage(currentLanguage);
}
#endif
Commit: dae7d64d3887aed2ebbabfe459ffc5d34bf4fc48
https://github.com/scummvm/scummvm/commit/dae7d64d3887aed2ebbabfe459ffc5d34bf4fc48
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add documentation to common/text-to-speech.h
Changed paths:
common/text-to-speech.h
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 1283dfe..a1d8a31 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -31,6 +31,10 @@
#include "common/debug.h"
namespace Common {
+
+/**
+ * Text to speech voice class.
+ */
class TTSVoice {
friend class TextToSpeechManager;
@@ -58,12 +62,57 @@ class TTSVoice {
, _age(age)
, _data(data)
, _description(description) {}
+
+ /**
+ * Returns the gender of the used voice.
+ *
+ * @note The gender information is really platform specific, it may not be
+ * possible to find it out on some platforms. Sometimes it can be set by
+ * the user in the TTS engine configuration and so the information might be
+ * wrong
+ */
Gender getGender() { return _gender; };
+
+ /**
+ * Sets the voice gender, should probably be used only by the backends
+ * that are directly communicating with the TTS engine.
+ */
void setGender(Gender gender) { _gender = gender; };
+
+ /**
+ * Returns the age of the used voice.
+ *
+ * @note The age information is really platform specific, it may not be
+ * possible to find it out on some platforms. Sometimes it can be set by
+ * the user in the TTS engine configuration and so the information might be
+ * wrong
+ */
Age getAge() { return _age; };
+
+ /**
+ * Sets the voice age, should probably be used only by the backends
+ * that are directly communicating with the TTS engine.
+ */
void setAge(Age age) { _age = age; };
+
+ /**
+ * Returns the data about the voice, this is engine specific variable,
+ * it has close to no value for anything else then communicating with
+ * directly with the TTS engine, which should probably be done only by
+ * the backends.
+ */
void setData(void *data) { _data = data; };
+
+ /**
+ * Sets the voice age, should probably be used only by the backends
+ * that are directly communicating with the TTS engine.
+ */
void *getData() { return _data; };
+
+ /**
+ * Returns the voice description. This description is really tts engine
+ * specific and might be not be availaible with some tts engines.
+ */
String getDescription() { return _description; };
protected:
@@ -85,41 +134,131 @@ struct TTSState {
/**
* The TextToSpeechManager allows speech synthesis.
- *
*/
class TextToSpeechManager {
public:
+ /**
+ * The constructor sets the language to the translation manager language if
+ * USE_TRANSLATION is defined, or english when it isn't defined. It sets the rate,
+ * pitch and volume to their middle values.
+ */
TextToSpeechManager();
virtual ~TextToSpeechManager();
+ /**
+ * Says the given string
+ *
+ * @param str The string to say
+ */
virtual bool say(String str) { return false; }
+ /**
+ * Stops the speech
+ */
virtual bool stop() { return false; }
+
+ /**
+ * Pauses the speech
+ */
virtual bool pause() { return false; }
+
+ /**
+ * Resumes the speech
+ */
virtual bool resume() { return false; }
+ /**
+ * Returns true, if the TTS engine is speaking
+ */
virtual bool isSpeaking() { return false; }
+
+ /**
+ * Returns true, if the TTS engine is paused
+ */
virtual bool isPaused() { return false; }
+
+ /**
+ * Returns true, if the TTS engine is ready to speak (isn't speaking and isn't paused)
+ */
virtual bool isReady() { return false; }
+ /**
+ * Sets a voice to be used by the TTS.
+ *
+ * @param index The index of the voice inside the _ttsState->_availaibleVoices array
+ */
virtual void setVoice(unsigned index) {}
+
+ /**
+ * Returns the voice, that is used right now
+ */
TTSVoice getVoice() { return _ttsState->_availaibleVoices[_ttsState->_activeVoice]; }
+ /**
+ * Sets the speech rate
+ *
+ * @param rate Integer between -100 (slowest) and 100 (fastest)
+ */
virtual void setRate(int rate) {}
+
+ /**
+ * Returns the current speech rate
+ */
int getRate() { return _ttsState->_rate; }
+ /**
+ * Sets the pitch
+ *
+ * @param pitch Integer between -100 (lowest) and 100 (highest)
+ */
virtual void setPitch(int pitch) {}
+
+ /**
+ * Returns current speech pitch
+ */
int getPitch() { return _ttsState->_pitch; }
+ /**
+ * Sets the speech volume
+ *
+ * @param volume Volume as a percentage (0 means muted, 100 means as loud as possible)
+ */
virtual void setVolume(unsigned volume) {}
+
+ /**
+ * Returns the current voice volume
+ */
virtual int getVolume() { return _ttsState->_volume; }
+ /**
+ * Sets the speech language
+ *
+ * @param language The language identifier as defined by ISO (2 characters long string)
+ *
+ * @note After using this method, it is probably a good idea to use setVoice,
+ * because voices are usually language specific and so it is set to some platform
+ * specific default after switching languages.
+ */
virtual void setLanguage(String language) {}
+
+ /**
+ * Returns the current speech language
+ */
String getLanguage() { return _ttsState->_language; }
+ /**
+ * Returns array of availaible voices for the current language
+ */
Array<TTSVoice> getVoicesArray() { return _ttsState->_availaibleVoices; }
+ /**
+ * Pushes the current state of the TTS
+ */
void pushState();
+
+ /**
+ * Pops the TTS state
+ */
virtual bool popState() { return true; }
protected:
Commit: 62e219ece3e77bf8f1bdcd60eeb95392c497917d
https://github.com/scummvm/scummvm/commit/62e219ece3e77bf8f1bdcd60eeb95392c497917d
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement default behavior of TTS setters
Changed paths:
common/text-to-speech.h
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index a1d8a31..63ea1a9 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -199,7 +199,7 @@ public:
*
* @param rate Integer between -100 (slowest) and 100 (fastest)
*/
- virtual void setRate(int rate) {}
+ virtual void setRate(int rate) { _ttsState->_rate = rate; }
/**
* Returns the current speech rate
@@ -211,7 +211,7 @@ public:
*
* @param pitch Integer between -100 (lowest) and 100 (highest)
*/
- virtual void setPitch(int pitch) {}
+ virtual void setPitch(int pitch) { _ttsState->_pitch = pitch; }
/**
* Returns current speech pitch
@@ -223,7 +223,7 @@ public:
*
* @param volume Volume as a percentage (0 means muted, 100 means as loud as possible)
*/
- virtual void setVolume(unsigned volume) {}
+ virtual void setVolume(unsigned volume) { _ttsState->_volume = volume; }
/**
* Returns the current voice volume
@@ -239,7 +239,7 @@ public:
* because voices are usually language specific and so it is set to some platform
* specific default after switching languages.
*/
- virtual void setLanguage(String language) {}
+ virtual void setLanguage(String language) { _ttsState->_language = language; }
/**
* Returns the current speech language
Commit: 8e4a24f55e2986de6c0556ab1a0faeeca20c724f
https://github.com/scummvm/scummvm/commit/8e4a24f55e2986de6c0556ab1a0faeeca20c724f
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Unify setters between linux and win backends.
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 6f621ed..f688ec1 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -103,7 +103,7 @@ bool WindowsTextToSpeechManager::say(Common::String str) {
_audio->SetState(SPAS_STOP, 0);
_audio->SetState(SPAS_RUN, 0);
// We have to set the pitch by prepending xml code at the start of the said string;
- Common::String pitch= Common::String::format("<pitch absmiddle=\"%d\">", _ttsState->_pitch);
+ Common::String pitch= Common::String::format("<pitch absmiddle=\"%d\">", _ttsState->_pitch / 10);
str.replace((uint32)0, 0, pitch);
WCHAR *strW = Win32::ansiToUnicode(str.c_str());
@@ -178,14 +178,15 @@ void WindowsTextToSpeechManager::setVoice(unsigned index) {
void WindowsTextToSpeechManager::setRate(int rate) {
if(_speechState == BROKEN || _speechState == NO_VOICE)
return;
- assert(rate >= -10 && rate <= 10);
- _voice->SetRate(rate);
+ assert(rate >= -100 && rate <= 100);
+ _voice->SetRate(rate / 10);
_ttsState->_rate = rate;
}
void WindowsTextToSpeechManager::setPitch(int pitch) {
if(_speechState == BROKEN || _speechState == NO_VOICE)
return;
+ assert(pitch >= -100 && pitch <= 100);
_ttsState->_pitch = pitch;
}
Commit: 5d9f03e71d75a307c8f8e538803fe2f09f38c5ae
https://github.com/scummvm/scummvm/commit/5d9f03e71d75a307c8f8e538803fe2f09f38c5ae
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add reference counting to TTSVoice
Also refactor TTSVoice destruction to use this reference counting.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
common/text-to-speech.cpp
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 633c107..db036e0 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -206,7 +206,8 @@ void LinuxTextToSpeechManager::setLanguage(Common::String language) {
void LinuxTextToSpeechManager::createVoice(int typeNumber, Common::TTSVoice::Gender gender, Common::TTSVoice::Age age, char *description) {
SPDVoiceType *type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
*type = static_cast<SPDVoiceType>(typeNumber);
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(gender, age, (void *) type, description));
+ Common::TTSVoice voice(gender, age, (void *) type, description);
+ _ttsState->_availaibleVoices.push_back(voice);
}
void LinuxTextToSpeechManager::updateVoices() {
@@ -238,10 +239,6 @@ bool LinuxTextToSpeechManager::popState() {
if (_ttsState->_next == nullptr)
return true;
- for (Common::TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
- free(i->getData());
- }
-
Common::TTSState *oldState = _ttsState;
_ttsState = _ttsState->_next;
@@ -254,5 +251,9 @@ bool LinuxTextToSpeechManager::popState() {
return false;
}
+void LinuxTextToSpeechManager::freeVoiceData(void *data) {
+ free(data);
+}
+
#endif
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 64c2371..57b9aee 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -67,6 +67,8 @@ public:
void updateState(SpeechState state);
+ virtual void freeVoiceData(void *data);
+
private:
void init();
virtual void updateVoices();
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index f688ec1..fec5a40 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -360,5 +360,9 @@ bool WindowsTextToSpeechManager::popState() {
setVoice(_ttsState->_activeVoice);
return false;
}
+void WindowsTextToSpeechManager::freeVoiceData(void *data) {
+ ISpObjectToken *voiceToken = (ISpObjectToken *) data;
+ voiceToken->Release();
+}
#endif
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index 03a1806..2ae062c 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -66,6 +66,8 @@ public:
virtual bool popState();
+ virtual void freeVoiceData(void *data);
+
private:
void init();
virtual void updateVoices();
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
index e8e0d9b..ce9f4a9 100644
--- a/common/text-to-speech.cpp
+++ b/common/text-to-speech.cpp
@@ -22,9 +22,62 @@
#include "common/text-to-speech.h"
+#include "common/system.h"
#if defined(USE_TTS)
namespace Common {
+
+TTSVoice::TTSVoice()
+ : _gender(UNKNOWN_GENDER)
+ , _age(UNKNOWN_AGE)
+ , _data(nullptr)
+ , _description("") {
+ _refCount = new int;
+ *_refCount = 1;
+}
+
+TTSVoice::TTSVoice(Gender gender, Age age, void *data, String description)
+ : _gender(gender)
+ , _age(age)
+ , _data(data)
+ , _description(description) {
+ _refCount = new int;
+ *_refCount = 1;
+}
+
+TTSVoice::TTSVoice(const TTSVoice& voice)
+ : _gender(voice._gender)
+ , _age(voice._age)
+ , _data(voice._data)
+ , _refCount(voice._refCount)
+ , _description(voice._description) {
+ if (_data)
+ (*_refCount)++;
+}
+
+TTSVoice::~TTSVoice() {
+ // _data is a platform specific field and so it the
+ // way it is freed differs from platform to platform
+ if (--(*_refCount) == 0) {
+ if (_data)
+ g_system->getTextToSpeechManager()->freeVoiceData(_data);
+ delete _refCount;
+ }
+}
+
+TTSVoice& TTSVoice::operator=(const TTSVoice& voice) {
+ if (&voice != this) {
+ _gender = voice._gender;
+ _data = voice._data;
+ _age = voice._age;
+ _refCount = voice._refCount;
+ if (_data)
+ (*_refCount)++;
+ _description = voice._description;
+ }
+ return *this;
+}
+
TextToSpeechManager::TextToSpeechManager() {
_ttsState = new TTSState;
_ttsState->_pitch = 0;
@@ -39,9 +92,6 @@ TextToSpeechManager::~TextToSpeechManager() {
TTSState *tmp = _ttsState;
while (tmp != nullptr) {
tmp = _ttsState->_next;
- for (TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
- free(i->_data);
- }
delete _ttsState;
_ttsState = tmp;
}
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 63ea1a9..606f8d2 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -52,16 +52,15 @@ class TTSVoice {
};
public:
- TTSVoice()
- : _gender(UNKNOWN_GENDER)
- , _age(UNKNOWN_AGE)
- , _data(nullptr)
- , _description("") {}
- TTSVoice(Gender gender, Age age, void *data, String description)
- : _gender(gender)
- , _age(age)
- , _data(data)
- , _description(description) {}
+ TTSVoice();
+
+ TTSVoice(Gender gender, Age age, void *data, String description) ;
+
+ TTSVoice(const TTSVoice& voice);
+
+ ~TTSVoice();
+
+ TTSVoice& operator=(const TTSVoice& voice);
/**
* Returns the gender of the used voice.
@@ -120,6 +119,7 @@ class TTSVoice {
Age _age;
void *_data;
String _description;
+ int *_refCount;
};
struct TTSState {
@@ -261,6 +261,8 @@ public:
*/
virtual bool popState() { return true; }
+ virtual void freeVoiceData(void *data) {}
+
protected:
TTSState *_ttsState;
Commit: 4d9572073192a3cefe7b148a27197c2ca9f60087
https://github.com/scummvm/scummvm/commit/4d9572073192a3cefe7b148a27197c2ca9f60087
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Fix voice setting on startup
The ScummVM was crashing because of an assert, when there was less
voices availaible, than what was set in the ConfMan.
Now the voice just falls back to 0th voice, if there are not
enough voices.
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
gui/gui-manager.cpp
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index fec5a40..e9f28d9 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -49,7 +49,6 @@ ISpAudio *_audio;
WindowsTextToSpeechManager::WindowsTextToSpeechManager()
: _speechState(BROKEN){
- ConfMan.setInt("tts_voice", 0);
init();
}
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index b149908..82f7591 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -645,6 +645,8 @@ void GuiManager::initTextToSpeech() {
voice = ConfMan.getInt("tts_voice", "scummvm");
else
voice = 0;
+ if (voice >= ttsMan->getVoicesArray().size())
+ voice = 0;
ttsMan->setVoice(voice);
}
#endif
Commit: 5c1f562452e201a968063e176b55fc14d912aea1
https://github.com/scummvm/scummvm/commit/5c1f562452e201a968063e176b55fc14d912aea1
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement conversion to UTF-8 in say on linux
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index db036e0..4241b76 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -27,6 +27,7 @@
#if defined(USE_LINUX_TTS)
#include <speech-dispatcher/libspeechd.h>
+#include "backends/platform/sdl/sdl-sys.h"
#include "common/translation.h"
#include "common/debug.h"
@@ -107,13 +108,35 @@ void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechState
_speechState = state;
}
-bool LinuxTextToSpeechManager::say(Common::String str) {
+Common::String LinuxTextToSpeechManager::strToUtf8(Common::String str, Common::String charset) {
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+
+ char *conv_text = SDL_iconv_string("UTF-8", charset.c_str(), str.c_str(), str.size() + 1);
+ Common::String result;
+ if (conv_text) {
+ result = conv_text;
+ SDL_free(conv_text);
+ }
+
+ return result;
+#else
+ return Common::String();
+#endif
+}
+
+bool LinuxTextToSpeechManager::say(Common::String str, Common::String charset) {
if (_speechState == BROKEN)
return true;
- //Convert string, that might have foreign characters to UTF-8
- if (ConfMan.get("gui_language") != "C") {
- str = Common::convertUtf32ToUtf8(Common::convertToU32String(str.c_str(), Common::kWindows1250)).c_str();
+
+ if (charset == "") {
+#ifdef USE_TRANSLATION
+ charset = TransMan.getCurrentCharset();
+#else
+ charset = "ASCII";
+#endif
}
+ str = strToUtf8(str, charset);
+
if (isSpeaking())
stop();
debug("say: %s", str.c_str());
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 57b9aee..1bb3ad5 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -42,7 +42,7 @@ public:
LinuxTextToSpeechManager();
virtual ~LinuxTextToSpeechManager();
- virtual bool say(Common::String str);
+ virtual bool say(Common::String str, Common::String charset = "");
virtual bool stop();
virtual bool pause();
@@ -74,6 +74,7 @@ private:
virtual void updateVoices();
void createVoice(int typeNumber, Common::TTSVoice::Gender, Common::TTSVoice::Age, char *description);
SpeechState _speechState;
+ Common::String strToUtf8(Common::String str, Common::String charset);
};
#endif
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index e9f28d9..32087f5 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -91,11 +91,19 @@ WindowsTextToSpeechManager::~WindowsTextToSpeechManager() {
::CoUninitialize();
}
-bool WindowsTextToSpeechManager::say(Common::String str) {
+bool WindowsTextToSpeechManager::say(Common::String str, Common::String charset) {
if(_speechState == BROKEN || _speechState == NO_VOICE) {
warning("The tts cannot speak in this state");
return true;
}
+
+ if (charset == "") {
+#ifdef USE_TRANSLATION
+ charset = TransMan.getCurrentCharset();
+#else
+ charset = "ASCII";
+#endif
+ }
if (isPaused()) {
resume();
}
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index 2ae062c..848ae85 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -43,7 +43,7 @@ public:
WindowsTextToSpeechManager();
virtual ~WindowsTextToSpeechManager();
- virtual bool say(Common::String str);
+ virtual bool say(Common::String str, Common::String charset = "");
virtual bool stop();
virtual bool pause();
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 606f8d2..589b998 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -150,7 +150,7 @@ public:
*
* @param str The string to say
*/
- virtual bool say(String str) { return false; }
+ virtual bool say(String str, String charset = "") { return false; }
/**
* Stops the speech
Commit: d31ffb676a57966248581926366d5eff6dee2ae7
https://github.com/scummvm/scummvm/commit/d31ffb676a57966248581926366d5eff6dee2ae7
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement conversion to unicode in say on win
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 32087f5..c646b9b 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -32,6 +32,7 @@
#include <sapi.h>
#include "backends/text-to-speech/windows/sphelper-scummvm.h"
#include "backends/platform/sdl/win32/win32_wrapper.h"
+#include "backends/platform/sdl/win32/codepage.h"
#include "backends/text-to-speech/windows/windows-text-to-speech.h"
@@ -113,7 +114,7 @@ bool WindowsTextToSpeechManager::say(Common::String str, Common::String charset)
Common::String pitch= Common::String::format("<pitch absmiddle=\"%d\">", _ttsState->_pitch / 10);
str.replace((uint32)0, 0, pitch);
- WCHAR *strW = Win32::ansiToUnicode(str.c_str());
+ WCHAR *strW = Win32::ansiToUnicode(str.c_str(), Win32::getCodePageId(charset));
bool result = _voice->Speak(strW, SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL) != S_OK;
free(strW);
_speechState = SPEAKING;
Commit: 7c789129316e6afabe9c06fd2cac22d45f44cae4
https://github.com/scummvm/scummvm/commit/7c789129316e6afabe9c06fd2cac22d45f44cae4
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Move popState to the base class
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
common/text-to-speech.cpp
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 4241b76..85faec1 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -258,22 +258,6 @@ void LinuxTextToSpeechManager::updateVoices() {
}
-bool LinuxTextToSpeechManager::popState() {
- if (_ttsState->_next == nullptr)
- return true;
-
- Common::TTSState *oldState = _ttsState;
- _ttsState = _ttsState->_next;
-
- delete oldState;
-
- setLanguage(_ttsState->_language);
- setPitch(_ttsState->_pitch);
- setVolume(_ttsState->_volume);
- setRate(_ttsState->_rate);
- return false;
-}
-
void LinuxTextToSpeechManager::freeVoiceData(void *data) {
free(data);
}
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 1bb3ad5..feb5950 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -63,7 +63,7 @@ public:
virtual void setLanguage(Common::String language);
- virtual bool popState();
+ bool popState();
void updateState(SpeechState state);
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index c646b9b..506220a 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -347,27 +347,6 @@ void WindowsTextToSpeechManager::updateVoices() {
_speechState = READY;
}
-bool WindowsTextToSpeechManager::popState() {
- if (_ttsState->_next == nullptr)
- return true;
-
- for (Common::TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
- ISpObjectToken *voiceToken = (ISpObjectToken *) i->getData();
- voiceToken->Release();
- }
-
- Common::TTSState *oldState = _ttsState;
- _ttsState = _ttsState->_next;
-
- delete oldState;
-
- setLanguage(_ttsState->_language);
- setPitch(_ttsState->_pitch);
- setVolume(_ttsState->_volume);
- setRate(_ttsState->_rate);
- setVoice(_ttsState->_activeVoice);
- return false;
-}
void WindowsTextToSpeechManager::freeVoiceData(void *data) {
ISpObjectToken *voiceToken = (ISpObjectToken *) data;
voiceToken->Release();
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index 848ae85..6498c9c 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -64,7 +64,7 @@ public:
virtual void setLanguage(Common::String language);
- virtual bool popState();
+ bool popState();
virtual void freeVoiceData(void *data);
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
index ce9f4a9..9ff8d50 100644
--- a/common/text-to-speech.cpp
+++ b/common/text-to-speech.cpp
@@ -109,5 +109,22 @@ void TextToSpeechManager::pushState() {
updateVoices();
}
+bool TextToSpeechManager::popState() {
+ if (_ttsState->_next == nullptr)
+ return true;
+
+ Common::TTSState *oldState = _ttsState;
+ _ttsState = _ttsState->_next;
+
+ delete oldState;
+
+ setLanguage(_ttsState->_language);
+ setPitch(_ttsState->_pitch);
+ setVolume(_ttsState->_volume);
+ setRate(_ttsState->_rate);
+ setVoice(_ttsState->_activeVoice);
+ return false;
+}
+
}
#endif
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 589b998..d52e1de 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -259,7 +259,7 @@ public:
/**
* Pops the TTS state
*/
- virtual bool popState() { return true; }
+ bool popState();
virtual void freeVoiceData(void *data) {}
Commit: bb3346fba7c77ac4db1a9d4542da01ca3d12bc46
https://github.com/scummvm/scummvm/commit/bb3346fba7c77ac4db1a9d4542da01ca3d12bc46
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Update the TTS documentation
Changed paths:
common/text-to-speech.h
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index d52e1de..63b1839 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -197,7 +197,7 @@ public:
/**
* Sets the speech rate
*
- * @param rate Integer between -100 (slowest) and 100 (fastest)
+ * @param rate Integer between -100 (slowest) and 100 (fastest), 0 is the default
*/
virtual void setRate(int rate) { _ttsState->_rate = rate; }
@@ -209,7 +209,7 @@ public:
/**
* Sets the pitch
*
- * @param pitch Integer between -100 (lowest) and 100 (highest)
+ * @param pitch Integer between -100 (lowest) and 100 (highest), 0 is the default
*/
virtual void setPitch(int pitch) { _ttsState->_pitch = pitch; }
@@ -233,7 +233,7 @@ public:
/**
* Sets the speech language
*
- * @param language The language identifier as defined by ISO (2 characters long string)
+ * @param language The language identifier as defined by ISO 639-1
*
* @note After using this method, it is probably a good idea to use setVoice,
* because voices are usually language specific and so it is set to some platform
@@ -261,6 +261,9 @@ public:
*/
bool popState();
+ /**
+ * Frees the _data field from TTSVoice
+ */
virtual void freeVoiceData(void *data) {}
protected:
Commit: 788b15652db9c5d062520c3202d30c827fd6fe21
https://github.com/scummvm/scummvm/commit/788b15652db9c5d062520c3202d30c827fd6fe21
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Change String == "" to String.empty()
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 85faec1..4b01b44 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -128,7 +128,7 @@ bool LinuxTextToSpeechManager::say(Common::String str, Common::String charset) {
if (_speechState == BROKEN)
return true;
- if (charset == "") {
+ if (charset.empty()) {
#ifdef USE_TRANSLATION
charset = TransMan.getCurrentCharset();
#else
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 506220a..0625bc8 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -98,7 +98,7 @@ bool WindowsTextToSpeechManager::say(Common::String str, Common::String charset)
return true;
}
- if (charset == "") {
+ if (charset.empty()) {
#ifdef USE_TRANSLATION
charset = TransMan.getCurrentCharset();
#else
Commit: b5d5576f90c177c54170a4e68b251c2fd5219260
https://github.com/scummvm/scummvm/commit/b5d5576f90c177c54170a4e68b251c2fd5219260
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add check to getVoice, fix typo.
Check if _availableVoices isn't empty.
Replace availaible with available
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.cpp
common/text-to-speech.cpp
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 4b01b44..9abe805 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -184,8 +184,8 @@ bool LinuxTextToSpeechManager::isReady() {
void LinuxTextToSpeechManager::setVoice(unsigned index) {
if (_speechState == BROKEN)
return;
- assert(index < _ttsState->_availaibleVoices.size());
- Common::TTSVoice voice = _ttsState->_availaibleVoices[index];
+ assert(index < _ttsState->_availableVoices.size());
+ Common::TTSVoice voice = _ttsState->_availableVoices[index];
spd_set_voice_type(_connection, *(SPDVoiceType *)(voice.getData()));
_ttsState->_activeVoice = index;
}
@@ -230,7 +230,7 @@ void LinuxTextToSpeechManager::createVoice(int typeNumber, Common::TTSVoice::Gen
SPDVoiceType *type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
*type = static_cast<SPDVoiceType>(typeNumber);
Common::TTSVoice voice(gender, age, (void *) type, description);
- _ttsState->_availaibleVoices.push_back(voice);
+ _ttsState->_availableVoices.push_back(voice);
}
void LinuxTextToSpeechManager::updateVoices() {
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 0625bc8..575c72b 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -79,7 +79,7 @@ void WindowsTextToSpeechManager::init() {
_audio->SetFormat(format.FormatId(), format.WaveFormatExPtr());
_voice->SetOutput(_audio, FALSE);
- if(_ttsState->_availaibleVoices.size() > 0)
+ if(_ttsState->_availableVoices.size() > 0)
_speechState = READY;
else
_speechState = NO_VOICE;
@@ -180,7 +180,7 @@ bool WindowsTextToSpeechManager::isReady() {
void WindowsTextToSpeechManager::setVoice(unsigned index) {
if(_speechState == BROKEN || _speechState == NO_VOICE)
return;
- _voice->SetVoice((ISpObjectToken *) _ttsState->_availaibleVoices[index].getData());
+ _voice->SetVoice((ISpObjectToken *) _ttsState->_availableVoices[index].getData());
}
void WindowsTextToSpeechManager::setRate(int rate) {
@@ -211,11 +211,11 @@ int WindowsTextToSpeechManager::getVolume() {
}
void WindowsTextToSpeechManager::freeVoices() {
- for(Common::TTSVoice *i = _ttsState->_availaibleVoices.begin(); i < _ttsState->_availaibleVoices.end(); i++) {
+ for(Common::TTSVoice *i = _ttsState->_availableVoices.begin(); i < _ttsState->_availableVoices.end(); i++) {
ISpObjectToken *voiceData = (ISpObjectToken *)i->getData();
voiceData->Release();
}
- _ttsState->_availaibleVoices.clear();
+ _ttsState->_availableVoices.clear();
}
void WindowsTextToSpeechManager::setLanguage(Common::String language) {
@@ -290,7 +290,7 @@ void WindowsTextToSpeechManager::createVoice(void *cpVoiceToken) {
free(buffer);
CoTaskMemFree(data);
- _ttsState->_availaibleVoices.push_back(Common::TTSVoice(gender, Common::TTSVoice::ADULT, (void *) voiceToken, desc));
+ _ttsState->_availableVoices.push_back(Common::TTSVoice(gender, Common::TTSVoice::ADULT, (void *) voiceToken, desc));
}
int strToInt(Common::String str) {
@@ -340,9 +340,9 @@ void WindowsTextToSpeechManager::updateVoices() {
_voice->SetVolume(_ttsState->_volume);
cpEnum->Release();
- if(_ttsState->_availaibleVoices.size() == 0) {
+ if(_ttsState->_availableVoices.size() == 0) {
_speechState = NO_VOICE;
- warning("No voice is availaible");
+ warning("No voice is available");
} else if (_speechState == NO_VOICE)
_speechState = READY;
}
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
index 9ff8d50..5bdf05e 100644
--- a/common/text-to-speech.cpp
+++ b/common/text-to-speech.cpp
@@ -126,5 +126,11 @@ bool TextToSpeechManager::popState() {
return false;
}
+TTSVoice TextToSpeechManager::getVoice() {
+ if (!_ttsState->_availableVoices.empty())
+ return _ttsState->_availableVoices[_ttsState->_activeVoice];
+ return TTSVoice();
+}
+
}
#endif
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 63b1839..012a1e2 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -110,7 +110,7 @@ class TTSVoice {
/**
* Returns the voice description. This description is really tts engine
- * specific and might be not be availaible with some tts engines.
+ * specific and might be not be available with some tts engines.
*/
String getDescription() { return _description; };
@@ -128,7 +128,7 @@ struct TTSState {
int _volume;
String _language;
int _activeVoice;
- Array<TTSVoice> _availaibleVoices;
+ Array<TTSVoice> _availableVoices;
TTSState *_next;
};
@@ -185,14 +185,14 @@ public:
/**
* Sets a voice to be used by the TTS.
*
- * @param index The index of the voice inside the _ttsState->_availaibleVoices array
+ * @param index The index of the voice inside the _ttsState->_availableVoices array
*/
virtual void setVoice(unsigned index) {}
/**
* Returns the voice, that is used right now
*/
- TTSVoice getVoice() { return _ttsState->_availaibleVoices[_ttsState->_activeVoice]; }
+ TTSVoice getVoice();
/**
* Sets the speech rate
@@ -247,9 +247,9 @@ public:
String getLanguage() { return _ttsState->_language; }
/**
- * Returns array of availaible voices for the current language
+ * Returns array of available voices for the current language
*/
- Array<TTSVoice> getVoicesArray() { return _ttsState->_availaibleVoices; }
+ Array<TTSVoice> getVoicesArray() { return _ttsState->_availableVoices; }
/**
* Pushes the current state of the TTS
Commit: 3027acc12e97fe92f617dd4a95a5861625773c38
https://github.com/scummvm/scummvm/commit/3027acc12e97fe92f617dd4a95a5861625773c38
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Minor refactorisations
- Add comment to tts initialization on Windows
- Correctly free the voicesInfo in linux ttsMan
- Remove popState method from linux-text-to-speech.h and
windows-text-to-speech.h
- Add tts to help in configure
- Refactor language setting in gui-manager.cpp
It counted with english being the default language in
ttsMan constructors, which isn't true anymore.
Changed paths:
backends/platform/sdl/win32/win32.cpp
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.h
configure
gui/gui-manager.cpp
diff --git a/backends/platform/sdl/win32/win32.cpp b/backends/platform/sdl/win32/win32.cpp
index 4b0961b..4328773 100644
--- a/backends/platform/sdl/win32/win32.cpp
+++ b/backends/platform/sdl/win32/win32.cpp
@@ -119,6 +119,8 @@ void OSystem_Win32::initBackend() {
// Initialize updates manager
_updateManager = new Win32UpdateManager();
#endif
+
+ // Initialize text to speech
#ifdef USE_WINDOWS_TTS
_textToSpeechManager = new WindowsTextToSpeechManager();
#endif
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 9abe805..d619522 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -256,6 +256,11 @@ void LinuxTextToSpeechManager::updateVoices() {
createVoice(SPD_CHILD_MALE, Common::TTSVoice::MALE, Common::TTSVoice::CHILD, voiceInfo[6]);
createVoice(SPD_CHILD_FEMALE, Common::TTSVoice::FEMALE, Common::TTSVoice::CHILD, voiceInfo[7]);
+ for (int i = 0; i < 8; i++)
+ free(voiceInfo[i]);
+
+ free(voiceInfo);
+
}
void LinuxTextToSpeechManager::freeVoiceData(void *data) {
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index feb5950..a6994f7 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -63,8 +63,6 @@ public:
virtual void setLanguage(Common::String language);
- bool popState();
-
void updateState(SpeechState state);
virtual void freeVoiceData(void *data);
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index 6498c9c..87e4ceb 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -64,8 +64,6 @@ public:
virtual void setLanguage(Common::String language);
- bool popState();
-
virtual void freeVoiceData(void *data);
private:
diff --git a/configure b/configure
index 096e11d..0a13ee0 100755
--- a/configure
+++ b/configure
@@ -1050,6 +1050,8 @@ Optional Features:
--enable-text-console use text console instead of graphical console
--enable-verbose-build enable regular echoing of commands during build
process
+ --enable-tts build support for text to speech
+ --disable-tts don't build support for text to speech
--disable-bink don't build with Bink video support
--opengl-mode=MODE OpenGL (ES) mode to use for OpenGL output [auto]
available modes: auto for autodetection
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 82f7591..9f4af81 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -630,10 +630,11 @@ void GuiManager::initTextToSpeech() {
return;
#ifdef USE_TRANSLATION
Common::String currentLanguage = TransMan.getCurrentLanguage();
- if (currentLanguage != "C") {
+ if (currentLanguage == "C")
+ currentLanguage = "en";
+ else
currentLanguage.setChar('\0', 2);
- ttsMan->setLanguage(currentLanguage);
- }
+ ttsMan->setLanguage(currentLanguage);
#endif
int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm"))
Commit: 134d955006072720031c989a151ee14875bfcf05
https://github.com/scummvm/scummvm/commit/134d955006072720031c989a151ee14875bfcf05
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add iconv implementation of strToUtf8
This might be useful in the future, because SDL cannot convert
from some important encodings (for example CP850)
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index d619522..3914b0f 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -28,6 +28,7 @@
#if defined(USE_LINUX_TTS)
#include <speech-dispatcher/libspeechd.h>
#include "backends/platform/sdl/sdl-sys.h"
+//#include <iconv.h>
#include "common/translation.h"
#include "common/debug.h"
@@ -116,9 +117,40 @@ Common::String LinuxTextToSpeechManager::strToUtf8(Common::String str, Common::S
if (conv_text) {
result = conv_text;
SDL_free(conv_text);
- }
+ } else if (charset != "ASCII"){
+ warning("Could not convert text from %s to UTF-8, trying ASCII", charset.c_str());
+ return strToUtf8(str, "ASCII");
+ } else
+ warning("Could not convert text to UTF-8");
return result;
+
+ // ICONV implementation (supports more charsets)
+ /*size_t inbytes = str.size();
+ char *inStr = new char[inbytes + 1];
+ char *in = inStr;
+ strcpy(inStr, str.c_str());
+
+ size_t outbytes = str.size() * 2 - 1;
+ char *destStr = new char[outbytes + 1];
+ char *out = destStr;
+ iconv_t conv = iconv_open("UTF-8//IGNORE", charset.c_str());
+
+ if (conv == (iconv_t)-1) {
+ warning("Could not convert string from: %s to UTF-8", charset.c_str());
+ return "";
+ }
+
+ if (iconv(conv, &in, &inbytes, &out, &outbytes) == (size_t)-1) {
+ warning("Could not convert string from: %s to UTF-8", charset.c_str());
+ return "";
+ }
+
+ destStr[outbytes + 1] = 0;
+ Common::String result = destStr;
+ delete[] inStr;
+ delete[] destStr;
+ return result; */
#else
return Common::String();
#endif
@@ -135,6 +167,8 @@ bool LinuxTextToSpeechManager::say(Common::String str, Common::String charset) {
charset = "ASCII";
#endif
}
+ debug("charset: %s", charset.c_str());
+
str = strToUtf8(str, charset);
if (isSpeaking())
Commit: 33549d9c0225a1ecfc5924b5584cdcee466498cc
https://github.com/scummvm/scummvm/commit/33549d9c0225a1ecfc5924b5584cdcee466498cc
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add getVoiceIndicesByGender to the base class
This is a way to easily get a list of voices with the given gender
Changed paths:
common/text-to-speech.cpp
common/text-to-speech.h
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
index 5bdf05e..9289cba 100644
--- a/common/text-to-speech.cpp
+++ b/common/text-to-speech.cpp
@@ -132,5 +132,14 @@ TTSVoice TextToSpeechManager::getVoice() {
return TTSVoice();
}
+Array<int> TextToSpeechManager::getVoiceIndicesByGender(TTSVoice::Gender gender) {
+ Array<int> results;
+ for (unsigned i = 0; i < _ttsState->_availableVoices.size(); i++) {
+ if (_ttsState->_availableVoices[i].getGender() == gender)
+ results.push_back(i);
+ }
+ return results;
+}
+
}
#endif
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 012a1e2..9662783 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -251,6 +251,8 @@ public:
*/
Array<TTSVoice> getVoicesArray() { return _ttsState->_availableVoices; }
+ Array<int> getVoiceIndicesByGender (TTSVoice::Gender gender);
+
/**
* Pushes the current state of the TTS
*/
Commit: 324a90f4f6e64ec4b92e2b746df5d4a75893044b
https://github.com/scummvm/scummvm/commit/324a90f4f6e64ec4b92e2b746df5d4a75893044b
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement tts state switching when needed.
The state has to be pushed and poped when there is a transition
between game and gui code.
Changed paths:
base/main.cpp
engines/engine.cpp
gui/gui-manager.h
diff --git a/base/main.cpp b/base/main.cpp
index 656a24b..ccd213e 100644
--- a/base/main.cpp
+++ b/base/main.cpp
@@ -558,9 +558,17 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
break;
}
#endif
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->pushState();
+#endif
// Try to run the game
Common::Error result = runGame(plugin, system, specialDebug);
+#ifdef USE_TTS
+ ttsMan->popState();
+#endif
+
#ifdef ENABLE_EVENTRECORDER
// Flush Event recorder file. The recorder does not get reinitialized for next game
// which is intentional. Only single game per session is allowed.
diff --git a/engines/engine.cpp b/engines/engine.cpp
index b985977..d34f1a6 100644
--- a/engines/engine.cpp
+++ b/engines/engine.cpp
@@ -61,6 +61,10 @@
#include "graphics/pixelformat.h"
#include "image/bmp.h"
+#ifdef USE_TTS
+#include "common/text-to-speech.h"
+#endif
+
#ifdef _WIN32_WCE
extern bool isSmartphone();
#endif
@@ -515,6 +519,11 @@ void Engine::pauseEngineIntern(bool pause) {
void Engine::openMainMenuDialog() {
if (!_mainMenuDialog)
_mainMenuDialog = new MainMenuDialog(this);
+#ifdef USE_TTS
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->pushState();
+ GUI::GuiManager::initTextToSpeech();
+#endif
setGameToLoadSlot(-1);
@@ -536,6 +545,9 @@ void Engine::openMainMenuDialog() {
}
syncSoundSettings();
+#ifdef USE_TTS
+ ttsMan->popState();
+#endif
}
bool Engine::warnUserAboutUnsupportedGame() {
diff --git a/gui/gui-manager.h b/gui/gui-manager.h
index fa5e715..866bc42 100644
--- a/gui/gui-manager.h
+++ b/gui/gui-manager.h
@@ -107,6 +107,7 @@ public:
* only be deleted when that dialog is the top level dialog.
*/
void addToTrash(GuiObject*, Dialog* parent = 0);
+ static void initTextToSpeech();
bool _launched;
@@ -177,7 +178,6 @@ protected:
void giveFocusToDialog(Dialog *dialog);
void setLastMousePos(int16 x, int16 y);
- void initTextToSpeech();
};
} // End of namespace GUI
Commit: e04463b05a615a22d1e1ce72090d663ad29bcad2
https://github.com/scummvm/scummvm/commit/e04463b05a615a22d1e1ce72090d663ad29bcad2
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
MORTEVIELLE: Add text to speech capability
Changed paths:
engines/mortevielle/sound.cpp
engines/mortevielle/sound.h
diff --git a/engines/mortevielle/sound.cpp b/engines/mortevielle/sound.cpp
index 77eba4c..a986db4 100644
--- a/engines/mortevielle/sound.cpp
+++ b/engines/mortevielle/sound.cpp
@@ -27,10 +27,15 @@
#include "mortevielle/mortevielle.h"
#include "mortevielle/sound.h"
+#include "mortevielle/dialogs.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "common/scummsys.h"
+#include "common/config-manager.h"
+#ifdef USE_TTS
+#include "common/text-to-speech.h"
+#endif
namespace Mortevielle {
@@ -62,6 +67,14 @@ SoundManager::SoundManager(MortevielleEngine *vm, Audio::Mixer *mixer) {
_audioStream = nullptr;
_ambiantNoiseBuf = nullptr;
_noiseBuf = nullptr;
+#ifdef USE_TTS
+ _ttsMan = g_system->getTextToSpeechManager();
+ _ttsMan->setLanguage(ConfMan.get("language"));
+ _ttsMan->stop();
+ _ttsMan->setRate(0);
+ _ttsMan->setPitch(0);
+ _ttsMan->setVolume(100);
+#endif //USE_TTS
_soundType = 0;
_phonemeNumb = 0;
@@ -196,6 +209,12 @@ void SoundManager::litph(tablint &t, int typ, int tempo) {
if (!_buildingSentence) {
if (_mixer->isSoundHandleActive(_soundHandle))
_mixer->stopHandle(_soundHandle);
+#ifdef USE_TTS
+ if (_ttsMan) {
+ if (_ttsMan->isSpeaking())
+ _ttsMan->stop();
+ }
+#endif // USE_TTS
_buildingSentence = true;
}
int freq = tempo * 252; // 25.2 * 10
@@ -759,6 +778,50 @@ void SoundManager::handlePhoneme() {
* @remarks Originally called 'parole'
*/
void SoundManager::startSpeech(int rep, int ht, int typ) {
+ if (_vm->_soundOff)
+ return;
+
+ if (typ == 0) {
+ // Speech
+#ifdef USE_TTS
+ if (!_ttsMan)
+ return;
+ Common::Array<int> voices;
+ int pitch;
+ bool male;
+ if (ht > 5) {
+ voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
+ pitch = ht - 6;
+ pitch *= 5;
+ male = false;
+ } else {
+ voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
+ pitch = ht - 5;
+ pitch *= 4;
+ male = true;
+ }
+ // If there is no voice available for the given gender, just set it to the 0th
+ // voice
+ if (voices.empty())
+ _ttsMan->setVoice(0);
+ else {
+ _ttsMan->setVoice(voices[0]);
+ }
+ // If the selected voice is a different gender, than we want, just try to
+ // set the pitch so it may sound a little bit closer to the gender we want
+ if (!((_ttsMan->getVoice().getGender() == Common::TTSVoice::MALE) == male)) {
+ if (male)
+ pitch -= 50;
+ else
+ pitch += 50;
+ }
+
+ _ttsMan->setPitch(pitch);
+ _ttsMan->say(_vm->getString(rep + kDialogStringIndex), "CP850");
+#else
+ return;
+#endif // USE_TTS
+ }
uint16 savph[501];
int tempo;
@@ -766,9 +829,6 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
if ((rep == 141) && (typ == 0))
return;
- if (_vm->_soundOff)
- return;
-
_phonemeNumb = rep;
int haut = ht;
_soundType = typ;
@@ -776,10 +836,11 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
for (int i = 0; i <= 500; ++i)
savph[i] = _cfiphBuffer[i];
tempo = kTempoNoise;
- } else if (haut > 5)
+ } else if (haut > 5) {
tempo = kTempoF;
- else
+ } else {
tempo = kTempoM;
+ }
_vm->_addFix = (float)((tempo - 8)) / 256;
cctable(_tbi);
switch (typ) {
@@ -810,11 +871,15 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
}
void SoundManager::waitSpeech() {
- while (_mixer->isSoundHandleActive(_soundHandle) && !_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
+#ifdef USE_TTS
+ if (!_ttsMan)
+ return;
+ while (_ttsMan->isSpeaking() && !_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
;
// In case the handle is still active, stop it.
- _mixer->stopHandle(_soundHandle);
+ _ttsMan->stop();
+#endif // USE_TTS
if (!_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
g_system->delayMillis(600);
}
diff --git a/engines/mortevielle/sound.h b/engines/mortevielle/sound.h
index 15b4667..0508d1d 100644
--- a/engines/mortevielle/sound.h
+++ b/engines/mortevielle/sound.h
@@ -31,6 +31,9 @@
#include "audio/mixer.h"
#include "common/mutex.h"
#include "common/queue.h"
+#ifdef USE_TTS
+#include "common/text-to-speech.h"
+#endif
namespace Audio {
class QueuingAudioStream;
@@ -91,6 +94,9 @@ public:
~SoundManager();
Audio::Mixer *_mixer;
+#ifdef USE_TTS
+ Common::TextToSpeechManager *_ttsMan;
+#endif //USE_TTS
Audio::SoundHandle _soundHandle;
uint16 *_cfiphBuffer;
Commit: e4363ba2292cb2e1c1a02e7dcdb1914f469efe0b
https://github.com/scummvm/scummvm/commit/e4363ba2292cb2e1c1a02e7dcdb1914f469efe0b
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Minor Windows TTS refactoring
Main changes are:
* Save age information about each voice
* Remove Sample TTS Voice from available voices, because it
basicaly cannot speak.
* Stop speech after silently playing the test speech (the last
voice could be heard speaking after the volume got
restored to its original value)
* Remove voice data freeing from freeVoices, because it gets
freed automaticaly
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 575c72b..58389e5 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -58,25 +58,25 @@ void WindowsTextToSpeechManager::init() {
if (FAILED(::CoInitialize(NULL)))
return;
- // init voice
- HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&_voice);
- if (!SUCCEEDED(hr)) {
- warning("Could not initialize TTS voice");
- return;
- }
- setLanguage("en");
-
// init audio
CSpStreamFormat format;
format.AssignFormat(SPSF_11kHz8BitMono);
ISpObjectToken *pToken;
- hr = SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOOUT, &pToken);
+ HRESULT hr = SpGetDefaultTokenFromCategoryId(SPCAT_AUDIOOUT, &pToken);
if (FAILED(hr)) {
warning("Could not initialize TTS audio");
return;
}
pToken->CreateInstance(NULL, CLSCTX_ALL, IID_ISpAudio, (void **)&_audio);
_audio->SetFormat(format.FormatId(), format.WaveFormatExPtr());
+
+ // init voice
+ hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&_voice);
+ if (!SUCCEEDED(hr)) {
+ warning("Could not initialize TTS voice");
+ return;
+ }
+ setLanguage("en");
_voice->SetOutput(_audio, FALSE);
if(_ttsState->_availableVoices.size() > 0)
@@ -211,11 +211,9 @@ int WindowsTextToSpeechManager::getVolume() {
}
void WindowsTextToSpeechManager::freeVoices() {
- for(Common::TTSVoice *i = _ttsState->_availableVoices.begin(); i < _ttsState->_availableVoices.end(); i++) {
- ISpObjectToken *voiceData = (ISpObjectToken *)i->getData();
- voiceData->Release();
- }
_ttsState->_availableVoices.clear();
+ // The voice data gets freed automaticly, when the reference counting inside TTSVoice
+ // reaches 0, so there is no point in trying to free it here
}
void WindowsTextToSpeechManager::setLanguage(Common::String language) {
@@ -234,6 +232,11 @@ void WindowsTextToSpeechManager::createVoice(void *cpVoiceToken) {
SpGetDescription(voiceToken, &descW);
char *buffer = Win32::unicodeToAnsi(descW);
Common::String desc = buffer;
+ if (desc == "Sample TTS Voice") {
+ // This is really bad voice, it is basicaly unusable
+ free(buffer);
+ return;
+ }
free(buffer);
// voice attributes
@@ -290,7 +293,7 @@ void WindowsTextToSpeechManager::createVoice(void *cpVoiceToken) {
free(buffer);
CoTaskMemFree(data);
- _ttsState->_availableVoices.push_back(Common::TTSVoice(gender, Common::TTSVoice::ADULT, (void *) voiceToken, desc));
+ _ttsState->_availableVoices.push_back(Common::TTSVoice(gender, age, (void *) voiceToken, desc));
}
int strToInt(Common::String str) {
@@ -337,6 +340,11 @@ void WindowsTextToSpeechManager::updateVoices() {
else
cpVoiceToken->Release();
}
+ // stop the test speech, we don't use stop(), because we don't wan't it to set state to READY
+ // and we could easily be in NO_VOICE or BROKEN state here, in which the stop() wouldn't work
+ _audio->SetState(SPAS_STOP, 0);
+ _audio->SetState(SPAS_RUN, 0);
+ _voice->Speak(NULL, SPF_PURGEBEFORESPEAK | SPF_ASYNC | SPF_IS_NOT_XML, 0);
_voice->SetVolume(_ttsState->_volume);
cpEnum->Release();
Commit: 063107083340c9572250c75347ff4b7880a1770b
https://github.com/scummvm/scummvm/commit/063107083340c9572250c75347ff4b7880a1770b
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Fix state switching on windows
Voice is changed when changing language on windows, so when poping
state, the voice, that should get set has to be saved before
changing the language.
The speech shouldn't continue when changing state, so it is stopped
in pushState and popState.
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
common/text-to-speech.cpp
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 58389e5..d102300 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -181,6 +181,7 @@ void WindowsTextToSpeechManager::setVoice(unsigned index) {
if(_speechState == BROKEN || _speechState == NO_VOICE)
return;
_voice->SetVoice((ISpObjectToken *) _ttsState->_availableVoices[index].getData());
+ _ttsState->_activeVoice = index;
}
void WindowsTextToSpeechManager::setRate(int rate) {
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
index 9289cba..f9dc4c6 100644
--- a/common/text-to-speech.cpp
+++ b/common/text-to-speech.cpp
@@ -98,6 +98,7 @@ TextToSpeechManager::~TextToSpeechManager() {
}
void TextToSpeechManager::pushState() {
+ stop();
TTSState *newState = new TTSState;
newState->_pitch = _ttsState->_pitch;
newState->_volume = _ttsState->_volume;
@@ -110,6 +111,7 @@ void TextToSpeechManager::pushState() {
}
bool TextToSpeechManager::popState() {
+ stop();
if (_ttsState->_next == nullptr)
return true;
@@ -118,11 +120,13 @@ bool TextToSpeechManager::popState() {
delete oldState;
+ // The voice has to be saved, because some backends change it when changing language
+ int voice = _ttsState->_activeVoice;
setLanguage(_ttsState->_language);
setPitch(_ttsState->_pitch);
setVolume(_ttsState->_volume);
setRate(_ttsState->_rate);
- setVoice(_ttsState->_activeVoice);
+ setVoice(voice);
return false;
}
Commit: d2b9b9ef21d09fb36d20c289bab3a8d035717c31
https://github.com/scummvm/scummvm/commit/d2b9b9ef21d09fb36d20c289bab3a8d035717c31
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
MORTEVIELLE: Map characters to different voices
Changed paths:
engines/mortevielle/sound.cpp
diff --git a/engines/mortevielle/sound.cpp b/engines/mortevielle/sound.cpp
index a986db4..96b4cdb 100644
--- a/engines/mortevielle/sound.cpp
+++ b/engines/mortevielle/sound.cpp
@@ -788,15 +788,18 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
return;
Common::Array<int> voices;
int pitch;
+ int voiceIndex;
bool male;
if (ht > 5) {
voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
pitch = ht - 6;
+ voiceIndex = pitch;
pitch *= 5;
male = false;
} else {
voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
pitch = ht - 5;
+ voiceIndex = -pitch;
pitch *= 4;
male = true;
}
@@ -805,7 +808,9 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
if (voices.empty())
_ttsMan->setVoice(0);
else {
- _ttsMan->setVoice(voices[0]);
+ voiceIndex %= voices.size();
+ _ttsMan->setVoice(voices[voiceIndex]);
+ debug("voice set: %d", voices[voiceIndex]);
}
// If the selected voice is a different gender, than we want, just try to
// set the pitch so it may sound a little bit closer to the gender we want
Commit: c9ec089e6151a1a852321ccd67808cbb32e869c9
https://github.com/scummvm/scummvm/commit/c9ec089e6151a1a852321ccd67808cbb32e869c9
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
MORTEVIELLE: Remove unneeded TTS code
Changed paths:
engines/mortevielle/sound.cpp
diff --git a/engines/mortevielle/sound.cpp b/engines/mortevielle/sound.cpp
index 96b4cdb..097ea8e 100644
--- a/engines/mortevielle/sound.cpp
+++ b/engines/mortevielle/sound.cpp
@@ -202,10 +202,6 @@ void SoundManager::regenbruit() {
}
void SoundManager::litph(tablint &t, int typ, int tempo) {
- // Skip speech
- if (_soundType == 0)
- return;
-
if (!_buildingSentence) {
if (_mixer->isSoundHandleActive(_soundHandle))
_mixer->stopHandle(_soundHandle);
@@ -226,9 +222,7 @@ void SoundManager::litph(tablint &t, int typ, int tempo) {
case 0: {
int val = _troctBuf[i];
i++;
- if (_soundType == 0)
- warning("TODO: vclas");
- else if (_soundType == 1) {
+ if (_soundType == 1) {
debugC(5, kMortevielleSounds, "litph - duson");
const static int noiseAdr[] = {0, 17224,
17224, 33676,
@@ -277,10 +271,6 @@ void SoundManager::litph(tablint &t, int typ, int tempo) {
case 4:
if (_soundType) {
i += 2;
- } else {
- // Speech
- warning("TODO: Interphoneme: consonne:%d voyelle:%d", _troctBuf[i], _troctBuf[i + 1]);
- i += 2;
}
break;
case 6:
@@ -327,13 +317,6 @@ void SoundManager::playSong(const byte* buf, uint size, uint loops) {
void SoundManager::spfrac(int wor) {
_queue[2]._rep = (uint)wor >> 12;
- if ((_soundType == 0) && (_queue[2]._code != 9)) {
- if (((_queue[2]._code > 4) && (_queue[2]._val != 20) && (_queue[2]._rep != 3) && (_queue[2]._rep != 6) && (_queue[2]._rep != 9)) ||
- ((_queue[2]._code < 5) && ((_queue[2]._val != 19) && (_queue[2]._val != 22) && (_queue[2]._rep != 4) && (_queue[2]._rep != 9)))) {
- ++_queue[2]._rep;
- }
- }
-
_queue[2]._freq = ((uint)wor >> 6) & 7;
_queue[2]._acc = ((uint)wor >> 9) & 7;
}
@@ -830,21 +813,14 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
uint16 savph[501];
int tempo;
- // Hack to avoid a crash in the ending version. To be removed when the speech are implemented
- if ((rep == 141) && (typ == 0))
- return;
-
_phonemeNumb = rep;
- int haut = ht;
_soundType = typ;
if (_soundType != 0) {
for (int i = 0; i <= 500; ++i)
savph[i] = _cfiphBuffer[i];
tempo = kTempoNoise;
- } else if (haut > 5) {
- tempo = kTempoF;
} else {
- tempo = kTempoM;
+ return;
}
_vm->_addFix = (float)((tempo - 8)) / 256;
cctable(_tbi);
@@ -862,16 +838,12 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
litph(_tbi, typ, tempo);
_buildingSentence = false;
- if (typ != 0) {
- _audioStream->finish();
- _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, _audioStream);
- _audioStream = nullptr;
- }
+ _audioStream->finish();
+ _mixer->playStream(Audio::Mixer::kSFXSoundType, &_soundHandle, _audioStream);
+ _audioStream = nullptr;
- if (_soundType != 0) {
- for (int i = 0; i <= 500; ++i)
- _cfiphBuffer[i] = savph[i];
- }
+ for (int i = 0; i <= 500; ++i)
+ _cfiphBuffer[i] = savph[i];
_vm->setPal(_vm->_numpal);
}
Commit: 2ecbf9ac181e21ddbb70e080d97bdda567fe2d19
https://github.com/scummvm/scummvm/commit/2ecbf9ac181e21ddbb70e080d97bdda567fe2d19
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
MORTEVIELLE: Return old code to waitSpeech
The waitSpeech should use the old code, when just sound is playing
(the TTS isn't speaking).
Changed paths:
engines/mortevielle/sound.cpp
diff --git a/engines/mortevielle/sound.cpp b/engines/mortevielle/sound.cpp
index 097ea8e..8f96c5b 100644
--- a/engines/mortevielle/sound.cpp
+++ b/engines/mortevielle/sound.cpp
@@ -848,15 +848,22 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
}
void SoundManager::waitSpeech() {
+ if (_soundType == 0) {
#ifdef USE_TTS
- if (!_ttsMan)
- return;
- while (_ttsMan->isSpeaking() && !_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
- ;
- // In case the handle is still active, stop it.
- _ttsMan->stop();
+ if (!_ttsMan)
+ return;
+ while (_ttsMan->isSpeaking() && !_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
+ ;
+ // In case the TTS is still speaking, stop it.
+ _ttsMan->stop();
#endif // USE_TTS
+ } else {
+ while (_mixer->isSoundHandleActive(_soundHandle) && !_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
+ ;
+ // In case the handle is still active, stop it.
+ _mixer->stopHandle(_soundHandle);
+ }
if (!_vm->keyPressed() && !_vm->_mouseClick && !_vm->shouldQuit())
g_system->delayMillis(600);
}
Commit: 4ec10ffec786be030a358e1b5499a6b856092e81
https://github.com/scummvm/scummvm/commit/4ec10ffec786be030a358e1b5499a6b856092e81
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Remove "static" from initTextToSpeech()
Changed paths:
engines/engine.cpp
gui/gui-manager.h
diff --git a/engines/engine.cpp b/engines/engine.cpp
index d34f1a6..8469c72 100644
--- a/engines/engine.cpp
+++ b/engines/engine.cpp
@@ -522,7 +522,7 @@ void Engine::openMainMenuDialog() {
#ifdef USE_TTS
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->pushState();
- GUI::GuiManager::initTextToSpeech();
+ g_gui.initTextToSpeech();
#endif
setGameToLoadSlot(-1);
diff --git a/gui/gui-manager.h b/gui/gui-manager.h
index 866bc42..880c94d 100644
--- a/gui/gui-manager.h
+++ b/gui/gui-manager.h
@@ -107,7 +107,7 @@ public:
* only be deleted when that dialog is the top level dialog.
*/
void addToTrash(GuiObject*, Dialog* parent = 0);
- static void initTextToSpeech();
+ void initTextToSpeech();
bool _launched;
Commit: 58065ceacda18dbae0e72d19ece6438ab06a332b
https://github.com/scummvm/scummvm/commit/58065ceacda18dbae0e72d19ece6438ab06a332b
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Refactoring
* Delete multiple empty rows
* Make getVolume non-virtual and leave just the implementation
in base class
* Resolve warning about signed / unsigned comparison in
gui-manager
* Clear availableVoices when updating voices on linux
* By default set language to transMan language on windows
(if the transMan is available)
* Remove freeVoices method from Windows ttsMan, it isn't needed
anymore
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
common/text-to-speech.h
gui/gui-manager.cpp
gui/gui-manager.h
gui/options.cpp
gui/options.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 3914b0f..8c797d7 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -182,7 +182,6 @@ bool LinuxTextToSpeechManager::say(Common::String str, Common::String charset) {
return true;
}
return false;
-
}
bool LinuxTextToSpeechManager::stop() {
@@ -248,10 +247,6 @@ void LinuxTextToSpeechManager::setVolume(unsigned volume) {
_ttsState->_volume = volume;
}
-int LinuxTextToSpeechManager::getVolume() {
- return (_ttsState->_volume - 50) * 2;
-}
-
void LinuxTextToSpeechManager::setLanguage(Common::String language) {
if (_speechState == BROKEN)
return;
@@ -278,6 +273,7 @@ void LinuxTextToSpeechManager::updateVoices() {
it depends on the user to map them to the right voices in speech-dispatcher
configuration
*/
+ _ttsState->_availableVoices.clear();
char **voiceInfo = spd_list_voices(_connection);
@@ -294,7 +290,6 @@ void LinuxTextToSpeechManager::updateVoices() {
free(voiceInfo[i]);
free(voiceInfo);
-
}
void LinuxTextToSpeechManager::freeVoiceData(void *data) {
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index a6994f7..fe7eab8 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -59,7 +59,6 @@ public:
virtual void setPitch(int pitch);
virtual void setVolume(unsigned volume);
- virtual int getVolume();
virtual void setLanguage(Common::String language);
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index d102300..f5c88ff 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -76,7 +76,13 @@ void WindowsTextToSpeechManager::init() {
warning("Could not initialize TTS voice");
return;
}
+
+#ifdef USE_TRANSLATION
+ setLanguage(TransMan.getCurrentLanguage());
+#else
setLanguage("en");
+#endif
+
_voice->SetOutput(_audio, FALSE);
if(_ttsState->_availableVoices.size() > 0)
@@ -86,7 +92,6 @@ void WindowsTextToSpeechManager::init() {
}
WindowsTextToSpeechManager::~WindowsTextToSpeechManager() {
- freeVoices();
if (_voice)
_voice->Release();
::CoUninitialize();
@@ -207,16 +212,6 @@ void WindowsTextToSpeechManager::setVolume(unsigned volume) {
_ttsState->_volume = volume;
}
-int WindowsTextToSpeechManager::getVolume() {
- return _ttsState->_volume;
-}
-
-void WindowsTextToSpeechManager::freeVoices() {
- _ttsState->_availableVoices.clear();
- // The voice data gets freed automaticly, when the reference counting inside TTSVoice
- // reaches 0, so there is no point in trying to free it here
-}
-
void WindowsTextToSpeechManager::setLanguage(Common::String language) {
if (language == "C")
language = "en";
@@ -322,7 +317,7 @@ Common::String WindowsTextToSpeechManager::lcidToLocale(Common::String lcid) {
}
void WindowsTextToSpeechManager::updateVoices() {
- freeVoices();
+ _ttsState->_availableVoices.clear();
HRESULT hr = S_OK;
ISpObjectToken *cpVoiceToken = nullptr;
IEnumSpObjectTokens *cpEnum = nullptr;
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index 87e4ceb..f60a59d 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -60,7 +60,6 @@ public:
virtual void setPitch(int pitch);
virtual void setVolume(unsigned volume);
- virtual int getVolume();
virtual void setLanguage(Common::String language);
@@ -70,7 +69,6 @@ private:
void init();
virtual void updateVoices();
void createVoice(void *cpVoiceToken);
- void freeVoices();
Common::String lcidToLocale(Common::String lcid);
SpeechState _speechState;
};
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 9662783..678810a 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -228,7 +228,7 @@ public:
/**
* Returns the current voice volume
*/
- virtual int getVolume() { return _ttsState->_volume; }
+ int getVolume() { return _ttsState->_volume; }
/**
* Sets the speech language
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 9f4af81..2541752 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -641,7 +641,7 @@ void GuiManager::initTextToSpeech() {
volume = 0;
ttsMan->setVolume(volume);
- int voice;
+ unsigned voice;
if(ConfMan.hasKey("tts_voice"))
voice = ConfMan.getInt("tts_voice", "scummvm");
else
diff --git a/gui/gui-manager.h b/gui/gui-manager.h
index 880c94d..f97d527 100644
--- a/gui/gui-manager.h
+++ b/gui/gui-manager.h
@@ -177,7 +177,6 @@ protected:
void giveFocusToDialog(Dialog *dialog);
void setLastMousePos(int16 x, int16 y);
-
};
} // End of namespace GUI
diff --git a/gui/options.cpp b/gui/options.cpp
index 507ae34..68053dd 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -426,7 +426,6 @@ void OptionsDialog::build() {
_subSpeedSlider->setValue(speed);
_subSpeedLabel->setValue(speed);
}
-
}
void OptionsDialog::clean() {
diff --git a/gui/options.h b/gui/options.h
index 76ad4ef..559ecbe 100644
--- a/gui/options.h
+++ b/gui/options.h
@@ -223,7 +223,6 @@ private:
CheckboxWidget *_muteCheckbox;
-
protected:
//
// Game GUI options
Commit: 1795206289c0d3c3dd7ad24c8f19ee3700b2ac83
https://github.com/scummvm/scummvm/commit/1795206289c0d3c3dd7ad24c8f19ee3700b2ac83
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add checks to configure.
Check for presence of required libraries
Disable automatic build of TTS on MinGW32
Changed paths:
configure
diff --git a/configure b/configure
index 0a13ee0..f190742 100755
--- a/configure
+++ b/configure
@@ -4198,6 +4198,35 @@ EOF
fi
define_in_config_if_yes "$_ogg" 'USE_OGG'
echo "$_ogg"
+#
+# Check for TTS
+#
+echocheck "TTS libraries"
+if test "$_tts" = auto ; then
+ _tts=no
+ case $_host_os in
+ mingw*)
+ cat > $TMPC << EOF
+#include <windows.h>
+#include <Servprov.h>
+#include <sapi.h>
+int main(void) { return 0; }
+EOF
+ cc_check -lsapi -lole32 && _tts=yes
+ if test "$_host_os" = "mingw32" ; then
+ _tts=no
+ fi
+ ;;
+ linux*)
+ cat > $TMPC << EOF
+#include <speech-dispatcher/libspeechd.h>
+int main(void) { return 0; }
+EOF
+ cc_check -lspeechd && _tts=yes
+ ;;
+ esac
+fi
+echo "$_tts"
#
# Check for Vorbis
Commit: c7dbf192ef2122cfaa82ffb3ae3bc05ae10e1e12
https://github.com/scummvm/scummvm/commit/c7dbf192ef2122cfaa82ffb3ae3bc05ae10e1e12
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Remove unneeded code
Remove debuging outputs
Remove commented iconv implementation of text conversion to UTF-8
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.cpp
common/text-to-speech.h
engines/mortevielle/sound.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 8c797d7..828eb87 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -31,7 +31,6 @@
//#include <iconv.h>
#include "common/translation.h"
-#include "common/debug.h"
#include "common/system.h"
#include "common/ustr.h"
#include "common/config-manager.h"
@@ -124,33 +123,6 @@ Common::String LinuxTextToSpeechManager::strToUtf8(Common::String str, Common::S
warning("Could not convert text to UTF-8");
return result;
-
- // ICONV implementation (supports more charsets)
- /*size_t inbytes = str.size();
- char *inStr = new char[inbytes + 1];
- char *in = inStr;
- strcpy(inStr, str.c_str());
-
- size_t outbytes = str.size() * 2 - 1;
- char *destStr = new char[outbytes + 1];
- char *out = destStr;
- iconv_t conv = iconv_open("UTF-8//IGNORE", charset.c_str());
-
- if (conv == (iconv_t)-1) {
- warning("Could not convert string from: %s to UTF-8", charset.c_str());
- return "";
- }
-
- if (iconv(conv, &in, &inbytes, &out, &outbytes) == (size_t)-1) {
- warning("Could not convert string from: %s to UTF-8", charset.c_str());
- return "";
- }
-
- destStr[outbytes + 1] = 0;
- Common::String result = destStr;
- delete[] inStr;
- delete[] destStr;
- return result; */
#else
return Common::String();
#endif
@@ -167,13 +139,11 @@ bool LinuxTextToSpeechManager::say(Common::String str, Common::String charset) {
charset = "ASCII";
#endif
}
- debug("charset: %s", charset.c_str());
str = strToUtf8(str, charset);
if (isSpeaking())
stop();
- debug("say: %s", str.c_str());
if(spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1) {
//restart the connection
if (_connection != 0)
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index f5c88ff..21ab024 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -38,7 +38,6 @@
#include "common/translation.h"
-#include "common/debug.h"
#include "common/system.h"
#include "common/ustr.h"
#include "common/config-manager.h"
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 678810a..cefbb71 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -28,7 +28,6 @@
#if defined(USE_TTS)
#include "common/array.h"
-#include "common/debug.h"
namespace Common {
diff --git a/engines/mortevielle/sound.cpp b/engines/mortevielle/sound.cpp
index 8f96c5b..66f421e 100644
--- a/engines/mortevielle/sound.cpp
+++ b/engines/mortevielle/sound.cpp
@@ -793,7 +793,6 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
else {
voiceIndex %= voices.size();
_ttsMan->setVoice(voices[voiceIndex]);
- debug("voice set: %d", voices[voiceIndex]);
}
// If the selected voice is a different gender, than we want, just try to
// set the pitch so it may sound a little bit closer to the gender we want
Commit: 99550a95b20d74afd845f95713ce132c2dcbfa93
https://github.com/scummvm/scummvm/commit/99550a95b20d74afd845f95713ce132c2dcbfa93
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
MORTEVILLE: Stop speech when pressing F8.
Changed paths:
engines/mortevielle/dialogs.cpp
diff --git a/engines/mortevielle/dialogs.cpp b/engines/mortevielle/dialogs.cpp
index ff1e026..7069765 100644
--- a/engines/mortevielle/dialogs.cpp
+++ b/engines/mortevielle/dialogs.cpp
@@ -421,6 +421,10 @@ void DialogManager::checkForF8(int SpeechNum, bool drawFrame2Fl) {
if (_vm->shouldQuit())
return;
} while (_vm->_key != 66); // keycode for F8
+ // just stop the speech when pressing F8
+#ifdef USE_TTS
+ _vm->_soundManager->_ttsMan->stop();
+#endif
}
/**
Commit: a1f69e6b06160d75ff85a0c292c88709abc4f7e9
https://github.com/scummvm/scummvm/commit/a1f69e6b06160d75ff85a0c292c88709abc4f7e9
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
MORTEVIELLE: Refactoring as suggested by Criezy
* Add checks if ttsMan != null before trying to use it
* Simplify startSpeech
* Move haut to startSpeech and pass the character index to
the startSpeech instead.
Changed paths:
engines/mortevielle/dialogs.cpp
engines/mortevielle/sound.cpp
engines/mortevielle/sound.h
engines/mortevielle/utils.cpp
diff --git a/engines/mortevielle/dialogs.cpp b/engines/mortevielle/dialogs.cpp
index 7069765..54c09cc 100644
--- a/engines/mortevielle/dialogs.cpp
+++ b/engines/mortevielle/dialogs.cpp
@@ -423,7 +423,8 @@ void DialogManager::checkForF8(int SpeechNum, bool drawFrame2Fl) {
} while (_vm->_key != 66); // keycode for F8
// just stop the speech when pressing F8
#ifdef USE_TTS
- _vm->_soundManager->_ttsMan->stop();
+ if (_vm->_soundManager->_ttsMan != nullptr)
+ _vm->_soundManager->_ttsMan->stop();
#endif
}
diff --git a/engines/mortevielle/sound.cpp b/engines/mortevielle/sound.cpp
index 66f421e..01a9dbb 100644
--- a/engines/mortevielle/sound.cpp
+++ b/engines/mortevielle/sound.cpp
@@ -69,11 +69,13 @@ SoundManager::SoundManager(MortevielleEngine *vm, Audio::Mixer *mixer) {
_noiseBuf = nullptr;
#ifdef USE_TTS
_ttsMan = g_system->getTextToSpeechManager();
- _ttsMan->setLanguage(ConfMan.get("language"));
- _ttsMan->stop();
- _ttsMan->setRate(0);
- _ttsMan->setPitch(0);
- _ttsMan->setVolume(100);
+ if (_ttsMan) {
+ _ttsMan->setLanguage(ConfMan.get("language"));
+ _ttsMan->stop();
+ _ttsMan->setRate(0);
+ _ttsMan->setPitch(0);
+ _ttsMan->setVolume(100);
+ }
#endif //USE_TTS
_soundType = 0;
@@ -760,30 +762,26 @@ void SoundManager::handlePhoneme() {
* Start speech
* @remarks Originally called 'parole'
*/
-void SoundManager::startSpeech(int rep, int ht, int typ) {
+void SoundManager::startSpeech(int rep, int character, int typ) {
if (_vm->_soundOff)
return;
+ _soundType = typ;
+
if (typ == 0) {
// Speech
#ifdef USE_TTS
+ const int haut[9] = { 0, 0, 1, -3, 6, -2, 2, 7, -1 };
if (!_ttsMan)
return;
Common::Array<int> voices;
- int pitch;
- int voiceIndex;
+ int pitch = haut[character] * 5;
bool male;
- if (ht > 5) {
+ if (haut[character] > 5) {
voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
- pitch = ht - 6;
- voiceIndex = pitch;
- pitch *= 5;
male = false;
} else {
voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
- pitch = ht - 5;
- voiceIndex = -pitch;
- pitch *= 4;
male = true;
}
// If there is no voice available for the given gender, just set it to the 0th
@@ -791,8 +789,8 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
if (voices.empty())
_ttsMan->setVoice(0);
else {
- voiceIndex %= voices.size();
- _ttsMan->setVoice(voices[voiceIndex]);
+ character %= voices.size();
+ _ttsMan->setVoice(voices[character]);
}
// If the selected voice is a different gender, than we want, just try to
// set the pitch so it may sound a little bit closer to the gender we want
@@ -805,22 +803,16 @@ void SoundManager::startSpeech(int rep, int ht, int typ) {
_ttsMan->setPitch(pitch);
_ttsMan->say(_vm->getString(rep + kDialogStringIndex), "CP850");
-#else
- return;
#endif // USE_TTS
+ return;
}
uint16 savph[501];
int tempo;
_phonemeNumb = rep;
- _soundType = typ;
- if (_soundType != 0) {
- for (int i = 0; i <= 500; ++i)
- savph[i] = _cfiphBuffer[i];
- tempo = kTempoNoise;
- } else {
- return;
- }
+ for (int i = 0; i <= 500; ++i)
+ savph[i] = _cfiphBuffer[i];
+ tempo = kTempoNoise;
_vm->_addFix = (float)((tempo - 8)) / 256;
cctable(_tbi);
switch (typ) {
diff --git a/engines/mortevielle/sound.h b/engines/mortevielle/sound.h
index 0508d1d..38081c9 100644
--- a/engines/mortevielle/sound.h
+++ b/engines/mortevielle/sound.h
@@ -104,7 +104,7 @@ public:
void playSong(const byte *buf, uint usize, uint loops);
void loadAmbiantSounds();
void loadNoise();
- void startSpeech(int rep, int ht, int typ);
+ void startSpeech(int rep, int character, int typ);
void waitSpeech();
};
diff --git a/engines/mortevielle/utils.cpp b/engines/mortevielle/utils.cpp
index 4ad8a8e..dd19c26 100644
--- a/engines/mortevielle/utils.cpp
+++ b/engines/mortevielle/utils.cpp
@@ -1330,7 +1330,6 @@ void MortevielleEngine::displayDiningRoom() {
* @remarks Originally called 'sparl'
*/
void MortevielleEngine::startDialog(int16 rep) {
- const int haut[9] = { 0, 0, 1, -3, 6, -2, 2, 7, -1 };
int key;
assert(rep >= 0);
@@ -1342,7 +1341,7 @@ void MortevielleEngine::startDialog(int16 rep) {
key = 0;
do {
- _soundManager->startSpeech(rep, haut[_caff - 69], 0);
+ _soundManager->startSpeech(rep, _caff - 69, 0);
key = _dialogManager->waitForF3F8();
if (shouldQuit())
return;
Commit: 990ab617939c301b49fa6399180d4370deeb0461
https://github.com/scummvm/scummvm/commit/990ab617939c301b49fa6399180d4370deeb0461
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
MORTEVIELLE: Improve voice mapping to characters.
* Return pitch -= 6 to females, this brings the pitch closer
to zero
* Add array of indices which ensures, that we use as many voices
as we can.
Changed paths:
engines/mortevielle/sound.cpp
diff --git a/engines/mortevielle/sound.cpp b/engines/mortevielle/sound.cpp
index 01a9dbb..841a184 100644
--- a/engines/mortevielle/sound.cpp
+++ b/engines/mortevielle/sound.cpp
@@ -772,25 +772,28 @@ void SoundManager::startSpeech(int rep, int character, int typ) {
// Speech
#ifdef USE_TTS
const int haut[9] = { 0, 0, 1, -3, 6, -2, 2, 7, -1 };
+ const int voiceIndices[9] = { 0, 1, 2, 3, 0, 4, 5, 1, 6 };
if (!_ttsMan)
return;
Common::Array<int> voices;
- int pitch = haut[character] * 5;
+ int pitch = haut[character];
bool male;
if (haut[character] > 5) {
voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
male = false;
+ pitch -= 6;
} else {
voices = _ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
male = true;
}
+ pitch *= 5;
// If there is no voice available for the given gender, just set it to the 0th
// voice
if (voices.empty())
_ttsMan->setVoice(0);
else {
- character %= voices.size();
- _ttsMan->setVoice(voices[character]);
+ int voiceIndex = voiceIndices[character] % voices.size();
+ _ttsMan->setVoice(voices[voiceIndex]);
}
// If the selected voice is a different gender, than we want, just try to
// set the pitch so it may sound a little bit closer to the gender we want
Commit: e965df1e8830c994da959ceaeb153d85fae7b859
https://github.com/scummvm/scummvm/commit/e965df1e8830c994da959ceaeb153d85fae7b859
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add TTS support when compiling with msvc.
Changed paths:
backends/platform/sdl/win32/win32_wrapper.cpp
backends/platform/sdl/win32/win32_wrapper.h
backends/text-to-speech/windows/sphelper-scummvm.h
devtools/create_project/create_project.cpp
diff --git a/backends/platform/sdl/win32/win32_wrapper.cpp b/backends/platform/sdl/win32/win32_wrapper.cpp
index 7efcb00..844c6ba 100644
--- a/backends/platform/sdl/win32/win32_wrapper.cpp
+++ b/backends/platform/sdl/win32/win32_wrapper.cpp
@@ -81,7 +81,7 @@ bool confirmWindowsVersion(int majorVersion, int minorVersion) {
return VerifyVersionInfoFunc(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask);
}
-wchar_t *ansiToUnicode(const char *s, uint codePage) {
+wchar_t *ansiToUnicode(const char *s, unsigned int codePage) {
DWORD size = MultiByteToWideChar(codePage, 0, s, -1, NULL, 0);
if (size > 0) {
@@ -93,7 +93,7 @@ wchar_t *ansiToUnicode(const char *s, uint codePage) {
return NULL;
}
-char *unicodeToAnsi(const wchar_t *s, uint codePage) {
+char *unicodeToAnsi(const wchar_t *s, unsigned int codePage) {
DWORD size = WideCharToMultiByte(codePage, 0, s, -1, NULL, 0, 0, 0);
if (size > 0) {
@@ -105,7 +105,7 @@ char *unicodeToAnsi(const wchar_t *s, uint codePage) {
return NULL;
}
-uint getCurrentCharset() {
+unsigned int getCurrentCharset() {
#ifdef USE_TRANSLATION
Common::String charset = TransMan.getCurrentCharset();
if (charset == "iso-8859-2")
diff --git a/backends/platform/sdl/win32/win32_wrapper.h b/backends/platform/sdl/win32/win32_wrapper.h
index 8bd6023..f2e16fa 100644
--- a/backends/platform/sdl/win32/win32_wrapper.h
+++ b/backends/platform/sdl/win32/win32_wrapper.h
@@ -45,7 +45,7 @@ bool confirmWindowsVersion(int majorVersion, int minorVersion);
*
* @note Return value must be freed by the caller.
*/
-wchar_t *ansiToUnicode(const char *s, uint codePage = CP_ACP);
+wchar_t *ansiToUnicode(const char *s, unsigned int codePage = CP_ACP);
/**
* Converts a Windows wide-character string into a C string.
* Used to interact with Win32 Unicode APIs with no ANSI fallback.
@@ -55,9 +55,9 @@ wchar_t *ansiToUnicode(const char *s, uint codePage = CP_ACP);
*
* @note Return value must be freed by the caller.
*/
-char *unicodeToAnsi(const wchar_t *s, uint codePage = CP_ACP);
+char *unicodeToAnsi(const wchar_t *s, unsigned int codePage = CP_ACP);
-uint getCurrentCharset();
+unsigned int getCurrentCharset();
}
diff --git a/backends/text-to-speech/windows/sphelper-scummvm.h b/backends/text-to-speech/windows/sphelper-scummvm.h
index d910a65..d622fda 100644
--- a/backends/text-to-speech/windows/sphelper-scummvm.h
+++ b/backends/text-to-speech/windows/sphelper-scummvm.h
@@ -1371,7 +1371,7 @@ public:
WAVEFORMATEX * m_pCoMemWaveFormatEx;
- static CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
+ static HRESULT CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
{
ULONG cb = sizeof(WAVEFORMATEX) + pSrc->cbSize;
*ppCoMemWFEX = (WAVEFORMATEX *)::CoTaskMemAlloc(cb);
diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp
index 4f178c9..9599aa0 100644
--- a/devtools/create_project/create_project.cpp
+++ b/devtools/create_project/create_project.cpp
@@ -390,7 +390,7 @@ int main(int argc, char *argv[]) {
#endif
}
- bool updatesEnabled = false, curlEnabled = false, sdlnetEnabled = false;
+ bool updatesEnabled = false, curlEnabled = false, sdlnetEnabled = false, ttsEnabled = false;
for (FeatureList::const_iterator i = setup.features.begin(); i != setup.features.end(); ++i) {
if (i->enable) {
if (!strcmp(i->name, "updates"))
@@ -399,6 +399,8 @@ int main(int argc, char *argv[]) {
curlEnabled = true;
else if (!strcmp(i->name, "sdlnet"))
sdlnetEnabled = true;
+ else if (!strcmp(i->name, "tts"))
+ ttsEnabled = true;
}
}
@@ -424,6 +426,11 @@ int main(int argc, char *argv[]) {
setup.libraries.push_back("winmm");
}
+ if (ttsEnabled) {
+ setup.libraries.push_back("sapi");
+ setup.defines.push_back("USE_WINDOWS_TTS");
+ }
+
setup.defines.push_back("SDL_BACKEND");
if (!setup.useSDL2) {
cout << "\nBuilding against SDL 1.2\n\n";
@@ -1088,7 +1095,8 @@ const Feature s_features[] = {
{ "dialogs", "USE_SYSDIALOGS", "", true, "System dialogs support"},
{ "langdetect", "USE_DETECTLANG", "", true, "System language detection support" }, // This feature actually depends on "translation", there
// is just no current way of properly detecting this...
- { "text-console", "USE_TEXT_CONSOLE_FOR_DEBUGGER", "", false, "Text console debugger" } // This feature is always applied in xcode projects
+ { "text-console", "USE_TEXT_CONSOLE_FOR_DEBUGGER", "", false, "Text console debugger" }, // This feature is always applied in xcode projects
+ { "tts", "USE_TTS", "", false, "Text to speech support"}
};
const Tool s_tools[] = {
Commit: 0434419b31cc6c27d2fdebb34abdf0b3131f6b99
https://github.com/scummvm/scummvm/commit/0434419b31cc6c27d2fdebb34abdf0b3131f6b99
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement TextToSpeechManager for macOS
Changed paths:
A backends/text-to-speech/macosx/macosx-text-to-speech.h
A backends/text-to-speech/macosx/macosx-text-to-speech.mm
backends/module.mk
backends/platform/sdl/macosx/macosx.cpp
configure
diff --git a/backends/module.mk b/backends/module.mk
index 04b38bf..11185fc 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -352,6 +352,10 @@ ifdef USE_WINDOWS_TTS
MODULE_OBJS += \
text-to-speech/windows/windows-text-to-speech.o
endif
+ifdef USE_MACOSX_TTS
+MODULE_OBJS += \
+ text-to-speech/macosx/macosx-text-to-speech.o
+endif
# Include common rules
include $(srcdir)/rules.mk
diff --git a/backends/platform/sdl/macosx/macosx.cpp b/backends/platform/sdl/macosx/macosx.cpp
index 3628168..3cca69b 100644
--- a/backends/platform/sdl/macosx/macosx.cpp
+++ b/backends/platform/sdl/macosx/macosx.cpp
@@ -32,6 +32,7 @@
#include "backends/platform/sdl/macosx/macosx.h"
#include "backends/updates/macosx/macosx-updates.h"
#include "backends/taskbar/macosx/macosx-taskbar.h"
+#include "backends/text-to-speech/macosx/macosx-text-to-speech.h"
#include "backends/dialogs/macosx/macosx-dialogs.h"
#include "backends/platform/sdl/macosx/macosx_wrapper.h"
#include "backends/fs/posix/posix-fs.h"
@@ -86,6 +87,11 @@ void OSystem_MacOSX::initBackend() {
_updateManager = new MacOSXUpdateManager();
#endif
+#ifdef USE_MACOSX_TTS
+ // Initialize Text to Speech manager
+ _textToSpeechManager = new MacOSXTextToSpeechManager();
+#endif
+
// Invoke parent implementation of this method
OSystem_POSIX::initBackend();
}
diff --git a/backends/text-to-speech/macosx/macosx-text-to-speech.h b/backends/text-to-speech/macosx/macosx-text-to-speech.h
new file mode 100644
index 0000000..fed0c05
--- /dev/null
+++ b/backends/text-to-speech/macosx/macosx-text-to-speech.h
@@ -0,0 +1,67 @@
+/* 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 BACKENDS_TEXT_TO_SPEECH_MACOSX_H
+#define BACKENDS_TEXT_TO_SPEECH_MACOSX_H
+
+#include "common/scummsys.h"
+
+#if defined(USE_MACOSX_TTS)
+
+#include "common/text-to-speech.h"
+#include "common/str.h"
+
+class MacOSXTextToSpeechManager : public Common::TextToSpeechManager {
+public:
+ MacOSXTextToSpeechManager();
+ virtual ~MacOSXTextToSpeechManager();
+
+ virtual bool say(Common::String str, Common::String charset = "");
+
+ virtual bool stop();
+ virtual bool pause();
+ virtual bool resume();
+
+ virtual bool isSpeaking();
+ virtual bool isPaused();
+ virtual bool isReady();
+
+ virtual void setVoice(unsigned index);
+
+ virtual void setRate(int rate);
+
+ virtual void setPitch(int pitch);
+
+ virtual void setVolume(unsigned volume);
+
+ virtual void setLanguage(Common::String language);
+
+ virtual void freeVoiceData(void *data);
+
+private:
+ virtual void updateVoices();
+};
+
+#endif
+
+#endif // BACKENDS_TEXT_TO_SPEECH_MACOSX_H
+
diff --git a/backends/text-to-speech/macosx/macosx-text-to-speech.mm b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
new file mode 100644
index 0000000..46a05e6
--- /dev/null
+++ b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
@@ -0,0 +1,206 @@
+/* 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.
+ *
+ */
+
+// Disable symbol overrides so that we can use system headers.
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/text-to-speech/macosx/macosx-text-to-speech.h"
+
+#if defined(USE_MACOSX_TTS)
+#include "common/translation.h"
+#include <AppKit/NSSpeechSynthesizer.h>
+#include <Foundation/NSString.h>
+#include <CoreFoundation/CFString.h>
+
+NSSpeechSynthesizer* synthesizer;
+
+MacOSXTextToSpeechManager::MacOSXTextToSpeechManager() : Common::TextToSpeechManager() {
+ synthesizer = [[NSSpeechSynthesizer alloc] init];
+
+#ifdef USE_TRANSLATION
+ setLanguage(TransMan.getCurrentLanguage());
+#else
+ setLanguage("en");
+#endif
+}
+
+MacOSXTextToSpeechManager::~MacOSXTextToSpeechManager() {
+ [synthesizer release];
+}
+
+bool MacOSXTextToSpeechManager::say(Common::String text, Common::String encoding) {
+ if (encoding.empty()) {
+#ifdef USE_TRANSLATION
+ encoding = TransMan.getCurrentCharset();
+#endif
+ }
+
+ // Get current encoding
+ CFStringEncoding stringEncoding = kCFStringEncodingASCII;
+ if (!encoding.empty()) {
+ CFStringRef encStr = CFStringCreateWithCString(NULL, encoding.c_str(), kCFStringEncodingASCII);
+ stringEncoding = CFStringConvertIANACharSetNameToEncoding(encStr);
+ CFRelease(encStr);
+ }
+
+ CFStringRef textNSString = CFStringCreateWithCString(NULL, text.c_str(), stringEncoding);
+ bool status = [synthesizer startSpeakingString:(NSString *)textNSString];
+ CFRelease(textNSString);
+ return status;
+}
+
+bool MacOSXTextToSpeechManager::stop() {
+ [synthesizer stopSpeaking];
+ return true;
+}
+
+bool MacOSXTextToSpeechManager::pause() {
+ // Should we use NSSpeechWordBoundary, or even NSSpeechSentenceBoundary?
+ [synthesizer pauseSpeakingAtBoundary:NSSpeechImmediateBoundary];
+ return true;
+}
+
+bool MacOSXTextToSpeechManager::resume() {
+ [synthesizer continueSpeaking];
+ return true;
+}
+
+bool MacOSXTextToSpeechManager::isSpeaking() {
+ return [synthesizer isSpeaking];
+}
+
+bool MacOSXTextToSpeechManager::isPaused() {
+ NSDictionary *statusDict = (NSDictionary*) [synthesizer objectForProperty:NSSpeechStatusProperty error:nil];
+ return [[statusDict objectForKey:NSSpeechStatusOutputBusy] boolValue] && [[statusDict objectForKey:NSSpeechStatusOutputPaused] boolValue];
+}
+
+bool MacOSXTextToSpeechManager::isReady() {
+ NSDictionary *statusDict = (NSDictionary*) [synthesizer objectForProperty:NSSpeechStatusProperty error:nil];
+ return [[statusDict objectForKey:NSSpeechStatusOutputBusy] boolValue] == NO;
+}
+
+void MacOSXTextToSpeechManager::setVoice(unsigned index) {
+ if (_ttsState->_availableVoices.empty())
+ return;
+ assert(index < _ttsState->_availableVoices.size());
+ Common::TTSVoice voice = _ttsState->_availableVoices[index];
+ _ttsState->_activeVoice = index;
+
+ [synthesizer setVoice:(NSString*)voice.getData()];
+
+ // Setting the voice reset the pitch and rate to the voice defaults.
+ // Apply back the modifiers.
+ int pitch = getPitch(), rate = getRate();
+ Common::TextToSpeechManager::setPitch(0);
+ Common::TextToSpeechManager::setRate(0);
+ setPitch(pitch);
+ setRate(rate);
+}
+
+void MacOSXTextToSpeechManager::setRate(int rate) {
+ int oldRate = getRate();
+ Common::TextToSpeechManager::setRate(rate);
+ // The rate is a value between -100 and +100, with 0 being the default rate.
+ // Convert this to a multiplier between 0.5 and 1.5.
+ float oldRateMultiplier = 1.0f + oldRate / 200.0f;
+ float ratehMultiplier = 1.0f + rate / 200.0f;
+ synthesizer.rate = synthesizer.rate / oldRateMultiplier * ratehMultiplier;
+}
+
+void MacOSXTextToSpeechManager::setPitch(int pitch) {
+ int oldPitch = getPitch();
+ Common::TextToSpeechManager::setPitch(pitch);
+ // The pitch is a value between -100 and +100, with 0 being the default pitch.
+ // Convert this to a multiplier between 0.5 and 1.5 on the default voice pitch.
+ float oldPitchMultiplier = 1.0f + oldPitch / 200.0f;
+ float pitchMultiplier = 1.0f + pitch / 200.0f;
+ NSNumber *basePitchNumber = [synthesizer objectForProperty:NSSpeechPitchBaseProperty error:nil];
+ float basePitch = [basePitchNumber floatValue] / oldPitchMultiplier * pitchMultiplier;
+ [synthesizer setObject:[NSNumber numberWithFloat:basePitch] forProperty:NSSpeechPitchBaseProperty error:nil];
+}
+
+void MacOSXTextToSpeechManager::setVolume(unsigned volume) {
+ Common::TextToSpeechManager::setVolume(volume);
+ synthesizer.volume = volume / 100.0f;
+}
+
+void MacOSXTextToSpeechManager::setLanguage(Common::String language) {
+ Common::TextToSpeechManager::setLanguage(language);
+ updateVoices();
+}
+
+void MacOSXTextToSpeechManager::freeVoiceData(void *data) {
+ NSString* voiceId = (NSString*)data;
+ [voiceId release];
+}
+
+void MacOSXTextToSpeechManager::updateVoices() {
+ Common::String currentVoice;
+ if (!_ttsState->_availableVoices.empty())
+ currentVoice = _ttsState->_availableVoices[_ttsState->_activeVoice].getDescription();
+ _ttsState->_availableVoices.clear();
+ int activeVoiceIndex = -1, defaultVoiceIndex = -1;
+
+ Common::String lang = getLanguage();
+ NSArray *voices = [NSSpeechSynthesizer availableVoices];
+ NSString *defaultVoice = [NSSpeechSynthesizer defaultVoice];
+ int voiceIndex = 0;
+ for (NSString *voiceId in voices) {
+ NSDictionary *voiceAttr = [NSSpeechSynthesizer attributesForVoice:voiceId];
+ Common::String voiceLocale([[voiceAttr objectForKey:NSVoiceLocaleIdentifier] UTF8String]);
+ if (voiceLocale.hasPrefix(lang)) {
+ NSString *data = [[NSString alloc] initWithString:voiceId];
+ Common::String name([[voiceAttr objectForKey:NSVoiceName] UTF8String]);
+ Common::TTSVoice::Gender gender = Common::TTSVoice::UNKNOWN_GENDER;
+ NSString *voiceGender = [voiceAttr objectForKey:NSVoiceGender];
+ if (voiceGender != nil) {
+ // This can be VoiceGenderMale, VoiceGenderFemale, VoiceGenderNeuter
+ if ([voiceGender isEqualToString:@"VoiceGenderMale"])
+ gender = Common::TTSVoice::MALE;
+ else if ([voiceGender isEqualToString:@"VoiceGenderFemale"])
+ gender = Common::TTSVoice::FEMALE;
+ }
+ Common::TTSVoice::Age age = Common::TTSVoice::UNKNOWN_AGE;
+ NSNumber *voiceAge = [voiceAttr objectForKey:NSVoiceAge];
+ if (voiceAge != nil) {
+ if ([voiceAge integerValue] < 18)
+ age = Common::TTSVoice::CHILD;
+ else
+ age = Common::TTSVoice::ADULT;
+ }
+ Common::TTSVoice voice(gender, age, data, name);
+ _ttsState->_availableVoices.push_back(voice);
+ if (name == currentVoice)
+ activeVoiceIndex = voiceIndex;
+ if (defaultVoice != nil && [defaultVoice isEqualToString:voiceId])
+ defaultVoiceIndex = voiceIndex;
+ ++voiceIndex;
+ }
+ }
+
+ if (activeVoiceIndex == -1)
+ activeVoiceIndex = defaultVoiceIndex == -1 ? 0 : defaultVoiceIndex;
+ setVoice(activeVoiceIndex);
+}
+
+
+#endif
diff --git a/configure b/configure
index f190742..c0ef73b 100755
--- a/configure
+++ b/configure
@@ -169,6 +169,7 @@ _iconv=auto
_tts=auto
_linux_tts=no
_windows_tts=no
+_macosx_tts=no
# Default option behavior yes/no
_debug_build=auto
_release_build=auto
@@ -4224,6 +4225,9 @@ int main(void) { return 0; }
EOF
cc_check -lspeechd && _tts=yes
;;
+ darwin*)
+ _tts=yes
+ ;;
esac
fi
echo "$_tts"
@@ -5414,6 +5418,12 @@ else
define_in_config_if_yes $_windows_tts 'USE_WINDOWS_TTS'
append_var LIBS '-lsapi -lole32'
;;
+ darwin*)
+ echo "osx"
+ _tts=yes
+ _macosx_tts=yes
+ define_in_config_if_yes $_macosx_tts 'USE_MACOSX_TTS'
+ ;;
*)
echo "no"
_tts=no
Commit: 5baa023ed0d30024ddc3ef58442e193987becb64
https://github.com/scummvm/scummvm/commit/5baa023ed0d30024ddc3ef58442e193987becb64
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Improve documentation
Changed paths:
common/text-to-speech.h
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index cefbb71..004de31 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -148,6 +148,8 @@ public:
* Says the given string
*
* @param str The string to say
+ * @param charset The encoding of the string. If empty this is assumed to be the
+ * encoding used for the GUI.
*/
virtual bool say(String str, String charset = "") { return false; }
Commit: bac880816b0442e504833bd9eeb3d782bcdd5fac
https://github.com/scummvm/scummvm/commit/bac880816b0442e504833bd9eeb3d782bcdd5fac
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Stop or pause speaking on word boundaries in macOS implementation
Changed paths:
backends/text-to-speech/macosx/macosx-text-to-speech.mm
diff --git a/backends/text-to-speech/macosx/macosx-text-to-speech.mm b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
index 46a05e6..281d92f 100644
--- a/backends/text-to-speech/macosx/macosx-text-to-speech.mm
+++ b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
@@ -69,13 +69,14 @@ bool MacOSXTextToSpeechManager::say(Common::String text, Common::String encoding
}
bool MacOSXTextToSpeechManager::stop() {
- [synthesizer stopSpeaking];
+ // Should we use NSSpeechImmediateBoundary, or even NSSpeechSentenceBoundary?
+ [synthesizer stopSpeakingAtBoundary:NSSpeechWordBoundary];
return true;
}
bool MacOSXTextToSpeechManager::pause() {
- // Should we use NSSpeechWordBoundary, or even NSSpeechSentenceBoundary?
- [synthesizer pauseSpeakingAtBoundary:NSSpeechImmediateBoundary];
+ // Should we use NSSpeechImmediateBoundary, or even NSSpeechSentenceBoundary?
+ [synthesizer pauseSpeakingAtBoundary:NSSpeechWordBoundary];
return true;
}
Commit: 59631627c8e21fceeeb53b6a75853a5be6c273a7
https://github.com/scummvm/scummvm/commit/59631627c8e21fceeeb53b6a75853a5be6c273a7
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Fix reading of list widgets
The TTS read items from list widgets even when the mouse was
outside the widget
Changed paths:
gui/widgets/list.cpp
diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp
index 26edd0a..0d2e9c0 100644
--- a/gui/widgets/list.cpp
+++ b/gui/widgets/list.cpp
@@ -268,6 +268,10 @@ void ListWidget::handleMouseMoved(int x, int y, int button) {
if (!isEnabled())
return;
+ // Determine if we are inside the widget
+ if (x < 0 || x > _w)
+ return;
+
// First check whether the selection changed
int item = findItem(x, y);
Commit: f89ca9ad5c28731b5a95525eb220d43fca55d14b
https://github.com/scummvm/scummvm/commit/f89ca9ad5c28731b5a95525eb220d43fca55d14b
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add tooltip reading when they appear.
Changed paths:
gui/Tooltip.cpp
diff --git a/gui/Tooltip.cpp b/gui/Tooltip.cpp
index dfa1d54..9d34fd4 100644
--- a/gui/Tooltip.cpp
+++ b/gui/Tooltip.cpp
@@ -54,6 +54,15 @@ void Tooltip::setup(Dialog *parent, Widget *widget, int x, int y) {
_x = MIN<int16>(parent->_x + x + _xdelta, g_gui.getWidth() - _w - 3);
_y = MIN<int16>(parent->_y + y + _ydelta, g_gui.getHeight() - _h - 3);
+#ifdef USE_TTS
+ if (ConfMan.hasKey("tts_enabled", "scummvm") &&
+ ConfMan.getBool("tts_enabled", "scummvm")) {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan == nullptr)
+ return;
+ ttsMan->say(widget->getTooltip());
+ }
+#endif
}
void Tooltip::drawDialog(DrawLayer layerToDraw) {
Commit: bbbb608c528699f281fdd5a8a7d814dd44b9aa41
https://github.com/scummvm/scummvm/commit/bbbb608c528699f281fdd5a8a7d814dd44b9aa41
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement OSD message reading
Changed paths:
backends/graphics/opengl/opengl-graphics.cpp
backends/graphics/surfacesdl/surfacesdl-graphics.cpp
diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp
index a6f31e6..06a0109 100644
--- a/backends/graphics/opengl/opengl-graphics.cpp
+++ b/backends/graphics/opengl/opengl-graphics.cpp
@@ -50,6 +50,10 @@
#include "image/bmp.h"
#endif
+#ifdef USE_TTS
+#include "common/text-to-speech.h"
+#endif
+
namespace OpenGL {
OpenGLGraphicsManager::OpenGLGraphicsManager()
@@ -780,7 +784,7 @@ void OpenGLGraphicsManager::displayMessageOnOSD(const char *msg) {
_osdMessageChangeRequest = true;
_osdMessageNextData = msg;
-#endif
+#endif // USE_OSD
}
#ifdef USE_OSD
@@ -842,6 +846,14 @@ void OpenGLGraphicsManager::osdMessageUpdateSurface() {
_osdMessageAlpha = kOSDMessageInitialAlpha;
_osdMessageFadeStartTime = g_system->getMillis() + kOSDMessageFadeOutDelay;
+#ifdef USE_TTS
+ if (ConfMan.hasKey("tts_enabled", "scummvm") &&
+ ConfMan.getBool("tts_enabled", "scummvm")) {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan)
+ ttsMan->say(_osdMessageNextData);
+ }
+#endif // USE_TTS
// Clear the text update request
_osdMessageNextData.clear();
_osdMessageChangeRequest = false;
diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
index 4b4f218..6434e5c 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
@@ -44,6 +44,9 @@
#include "common/file.h"
#include "image/png.h"
#endif
+#ifdef USE_TTS
+#include "common/text-to-speech.h"
+#endif
static const OSystem::GraphicsMode s_supportedShaders[] = {
{"NONE", "Normal (no shader)", 0},
@@ -2303,6 +2306,9 @@ void SurfaceSdlGraphicsManager::drawMouse() {
void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) {
assert(_transactionMode == kTransactionNone);
assert(msg);
+#ifdef USE_TTS
+ Common::String textToSay = msg;
+#endif // USE_TTS
Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends
@@ -2376,6 +2382,14 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) {
// Ensure a full redraw takes place next time the screen is updated
_forceRedraw = true;
+#ifdef USE_TTS
+ if (ConfMan.hasKey("tts_enabled", "scummvm") &&
+ ConfMan.getBool("tts_enabled", "scummvm")) {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ if (ttsMan)
+ ttsMan->say(textToSay);
+ }
+#endif // USE_TTS
}
SDL_Rect SurfaceSdlGraphicsManager::getOSDMessageRect() const {
Commit: 6703f88f7f91bc22ce5ea3593a1699f1dc4fa7c0
https://github.com/scummvm/scummvm/commit/6703f88f7f91bc22ce5ea3593a1699f1dc4fa7c0
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement speech queueing on Linux and Win
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 828eb87..a2a09ae 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -128,9 +128,12 @@ Common::String LinuxTextToSpeechManager::strToUtf8(Common::String str, Common::S
#endif
}
-bool LinuxTextToSpeechManager::say(Common::String str, Common::String charset) {
+bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::String charset) {
if (_speechState == BROKEN)
return true;
+
+ if (action == DROP && isSpeaking())
+ return true;
if (charset.empty()) {
#ifdef USE_TRANSLATION
@@ -142,7 +145,7 @@ bool LinuxTextToSpeechManager::say(Common::String str, Common::String charset) {
str = strToUtf8(str, charset);
- if (isSpeaking())
+ if (isSpeaking() && action == INTERRUPT)
stop();
if(spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1) {
//restart the connection
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index fe7eab8..49701ab 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -42,7 +42,7 @@ public:
LinuxTextToSpeechManager();
virtual ~LinuxTextToSpeechManager();
- virtual bool say(Common::String str, Common::String charset = "");
+ virtual bool say(Common::String str, Action action, Common::String charset = "");
virtual bool stop();
virtual bool pause();
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 21ab024..196a909 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -96,12 +96,15 @@ WindowsTextToSpeechManager::~WindowsTextToSpeechManager() {
::CoUninitialize();
}
-bool WindowsTextToSpeechManager::say(Common::String str, Common::String charset) {
- if(_speechState == BROKEN || _speechState == NO_VOICE) {
+bool WindowsTextToSpeechManager::say(Common::String str, Action action, Common::String charset) {
+ if (_speechState == BROKEN || _speechState == NO_VOICE) {
warning("The tts cannot speak in this state");
return true;
}
+ if (isSpeaking() && action == DROP)
+ return true;
+
if (charset.empty()) {
#ifdef USE_TRANSLATION
charset = TransMan.getCurrentCharset();
@@ -109,19 +112,18 @@ bool WindowsTextToSpeechManager::say(Common::String str, Common::String charset)
charset = "ASCII";
#endif
}
- if (isPaused()) {
- resume();
- }
- _audio->SetState(SPAS_STOP, 0);
- _audio->SetState(SPAS_RUN, 0);
// We have to set the pitch by prepending xml code at the start of the said string;
Common::String pitch= Common::String::format("<pitch absmiddle=\"%d\">", _ttsState->_pitch / 10);
str.replace((uint32)0, 0, pitch);
-
WCHAR *strW = Win32::ansiToUnicode(str.c_str(), Win32::getCodePageId(charset));
- bool result = _voice->Speak(strW, SPF_ASYNC | SPF_PURGEBEFORESPEAK, NULL) != S_OK;
+
+ if ((isPaused() || isSpeaking()) && action == INTERRUPT)
+ stop();
+
+ bool result = _voice->Speak(strW, SPF_ASYNC, NULL) != S_OK;
free(strW);
- _speechState = SPEAKING;
+ if (!isPaused())
+ _speechState = SPEAKING;
return result;
}
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index f60a59d..7a02c19 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -43,7 +43,7 @@ public:
WindowsTextToSpeechManager();
virtual ~WindowsTextToSpeechManager();
- virtual bool say(Common::String str, Common::String charset = "");
+ virtual bool say(Common::String str, Action action, Common::String charset = "");
virtual bool stop();
virtual bool pause();
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 004de31..b776a1c 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -136,6 +136,11 @@ struct TTSState {
*/
class TextToSpeechManager {
public:
+ enum Action {
+ INTERRUPT,
+ QUEUE,
+ DROP
+ };
/**
* The constructor sets the language to the translation manager language if
* USE_TRANSLATION is defined, or english when it isn't defined. It sets the rate,
@@ -145,13 +150,23 @@ public:
virtual ~TextToSpeechManager();
/**
+ * Interrupts what's being said and says the given string
+ *
+ * @param str The string to say
+ * @param charset The encoding of the string. If empty this is assumed to be the
+ * encoding used for the GUI.
+ */
+ virtual bool say(String str, String charset = "") { return say(str, INTERRUPT, charset); }
+
+ /**
* Says the given string
*
* @param str The string to say
+ * @param action What to do if another string is just being said.
* @param charset The encoding of the string. If empty this is assumed to be the
* encoding used for the GUI.
*/
- virtual bool say(String str, String charset = "") { return false; }
+ virtual bool say(String str, Action action, String charset = "") { return false; }
/**
* Stops the speech
Commit: a81b59a3c4df50e246e75b0fbe7f822f71174fd7
https://github.com/scummvm/scummvm/commit/a81b59a3c4df50e246e75b0fbe7f822f71174fd7
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Don't read tooltips when TTS is speaking
Changed paths:
gui/Tooltip.cpp
diff --git a/gui/Tooltip.cpp b/gui/Tooltip.cpp
index 9d34fd4..1f57ba9 100644
--- a/gui/Tooltip.cpp
+++ b/gui/Tooltip.cpp
@@ -60,7 +60,7 @@ void Tooltip::setup(Dialog *parent, Widget *widget, int x, int y) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan == nullptr)
return;
- ttsMan->say(widget->getTooltip());
+ ttsMan->say(widget->getTooltip(), Common::TextToSpeechManager::DROP);
}
#endif
}
Commit: 1694b930e652cd2cc549988f9960604a35e36c25
https://github.com/scummvm/scummvm/commit/1694b930e652cd2cc549988f9960604a35e36c25
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Make one say() method non-virtual
Changed paths:
common/text-to-speech.h
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index b776a1c..df0bee8 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -156,7 +156,7 @@ public:
* @param charset The encoding of the string. If empty this is assumed to be the
* encoding used for the GUI.
*/
- virtual bool say(String str, String charset = "") { return say(str, INTERRUPT, charset); }
+ bool say(String str, String charset = "") { return say(str, INTERRUPT, charset); }
/**
* Says the given string
Commit: 9ca2602e82429cf3b210f363e0a478fd876a13b5
https://github.com/scummvm/scummvm/commit/9ca2602e82429cf3b210f363e0a478fd876a13b5
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement speech queueing on macOS
Changed paths:
backends/text-to-speech/macosx/macosx-text-to-speech.h
backends/text-to-speech/macosx/macosx-text-to-speech.mm
diff --git a/backends/text-to-speech/macosx/macosx-text-to-speech.h b/backends/text-to-speech/macosx/macosx-text-to-speech.h
index fed0c05..7c33a3b 100644
--- a/backends/text-to-speech/macosx/macosx-text-to-speech.h
+++ b/backends/text-to-speech/macosx/macosx-text-to-speech.h
@@ -28,14 +28,14 @@
#if defined(USE_MACOSX_TTS)
#include "common/text-to-speech.h"
-#include "common/str.h"
+#include "common/queue.h"
class MacOSXTextToSpeechManager : public Common::TextToSpeechManager {
public:
MacOSXTextToSpeechManager();
virtual ~MacOSXTextToSpeechManager();
- virtual bool say(Common::String str, Common::String charset = "");
+ virtual bool say(Common::String str, Action action, Common::String charset = "");
virtual bool stop();
virtual bool pause();
@@ -57,8 +57,17 @@ public:
virtual void freeVoiceData(void *data);
+ bool startNextSpeech();
+
private:
virtual void updateVoices();
+
+ struct SpeechText {
+ Common::String text;
+ Common::String encoding;
+ SpeechText(const Common::String& txt, const Common::String& enc) : text(txt), encoding(enc) {}
+ };
+ Common::Queue<SpeechText> _messageQueue;
};
#endif
diff --git a/backends/text-to-speech/macosx/macosx-text-to-speech.mm b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
index 281d92f..9f66841 100644
--- a/backends/text-to-speech/macosx/macosx-text-to-speech.mm
+++ b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
@@ -31,10 +31,32 @@
#include <Foundation/NSString.h>
#include <CoreFoundation/CFString.h>
-NSSpeechSynthesizer* synthesizer;
+ at interface MacOSXTextToSpeechManagerDelegate : NSObject<NSSpeechSynthesizerDelegate> {
+ MacOSXTextToSpeechManager *_ttsManager;
+}
+- (id)initWithManager:(MacOSXTextToSpeechManager*)ttsManager;
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)finishedSpeaking;
+ at end
+
+ at implementation MacOSXTextToSpeechManagerDelegate
+- (id)initWithManager:(MacOSXTextToSpeechManager*)ttsManager {
+ self = [super init];
+ _ttsManager = ttsManager;
+ return self;
+}
+
+- (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)finishedSpeaking {
+ _ttsManager->startNextSpeech();
+}
+ at end
+
+NSSpeechSynthesizer *synthesizer;
+MacOSXTextToSpeechManagerDelegate *synthesizerDelegate;
MacOSXTextToSpeechManager::MacOSXTextToSpeechManager() : Common::TextToSpeechManager() {
synthesizer = [[NSSpeechSynthesizer alloc] init];
+ synthesizerDelegate = [[MacOSXTextToSpeechManagerDelegate alloc] initWithManager:this];
+ [synthesizer setDelegate:synthesizerDelegate];
#ifdef USE_TRANSLATION
setLanguage(TransMan.getCurrentLanguage());
@@ -45,30 +67,52 @@ MacOSXTextToSpeechManager::MacOSXTextToSpeechManager() : Common::TextToSpeechMan
MacOSXTextToSpeechManager::~MacOSXTextToSpeechManager() {
[synthesizer release];
+ [synthesizerDelegate release];
}
-bool MacOSXTextToSpeechManager::say(Common::String text, Common::String encoding) {
+bool MacOSXTextToSpeechManager::say(Common::String text, Action action, Common::String encoding) {
+ if ([synthesizer isSpeaking]) {
+ if (action == DROP)
+ return true;
+ else if (action == INTERRUPT) {
+ _messageQueue.clear();
+ // Should we use NSSpeechImmediateBoundary, or even NSSpeechSentenceBoundary?
+ [synthesizer stopSpeakingAtBoundary:NSSpeechWordBoundary];
+ }
+ }
+
if (encoding.empty()) {
#ifdef USE_TRANSLATION
encoding = TransMan.getCurrentCharset();
#endif
}
+ _messageQueue.push(SpeechText(text, encoding));
+ if (![synthesizer isSpeaking])
+ startNextSpeech();
+ return true;
+}
+
+bool MacOSXTextToSpeechManager::startNextSpeech() {
+ if (_messageQueue.empty())
+ return false;
+ SpeechText text = _messageQueue.pop();
// Get current encoding
CFStringEncoding stringEncoding = kCFStringEncodingASCII;
- if (!encoding.empty()) {
- CFStringRef encStr = CFStringCreateWithCString(NULL, encoding.c_str(), kCFStringEncodingASCII);
+ if (!text.encoding.empty()) {
+ CFStringRef encStr = CFStringCreateWithCString(NULL, text.encoding.c_str(), kCFStringEncodingASCII);
stringEncoding = CFStringConvertIANACharSetNameToEncoding(encStr);
CFRelease(encStr);
}
- CFStringRef textNSString = CFStringCreateWithCString(NULL, text.c_str(), stringEncoding);
+ CFStringRef textNSString = CFStringCreateWithCString(NULL, text.text.c_str(), stringEncoding);
bool status = [synthesizer startSpeakingString:(NSString *)textNSString];
CFRelease(textNSString);
return status;
}
bool MacOSXTextToSpeechManager::stop() {
+ _messageQueue.clear();
// Should we use NSSpeechImmediateBoundary, or even NSSpeechSentenceBoundary?
[synthesizer stopSpeakingAtBoundary:NSSpeechWordBoundary];
return true;
Commit: 7ec4f03a0824d2ee16c24cf995ce4900ecb556af
https://github.com/scummvm/scummvm/commit/7ec4f03a0824d2ee16c24cf995ce4900ecb556af
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Make state switching faster on Linux
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index a2a09ae..c5ff8e0 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -147,6 +147,8 @@ bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::St
if (isSpeaking() && action == INTERRUPT)
stop();
+ if (str.size() != 0)
+ _speechState = SPEAKING;
if(spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1) {
//restart the connection
if (_connection != 0)
@@ -154,24 +156,28 @@ bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::St
init();
return true;
}
+
return false;
}
bool LinuxTextToSpeechManager::stop() {
if (_speechState == READY || _speechState == BROKEN)
return true;
+ _speechState = READY;
return spd_cancel(_connection) == -1;
}
bool LinuxTextToSpeechManager::pause() {
if (_speechState == READY || _speechState == PAUSED || _speechState == BROKEN)
return true;
+ _speechState = PAUSED;
return spd_pause(_connection) == -1;
}
bool LinuxTextToSpeechManager::resume() {
if (_speechState == READY || _speechState == SPEAKING || _speechState == BROKEN)
return true;
+ _speechState = SPEAKING;
return spd_resume(_connection) == -1;
}
Commit: a5c9e8c74b11a96c1c411414a52bedd88193b350
https://github.com/scummvm/scummvm/commit/a5c9e8c74b11a96c1c411414a52bedd88193b350
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBET: Add TTS tests
Changed paths:
A engines/testbed/speech.cpp
A engines/testbed/speech.h
engines/testbed/module.mk
engines/testbed/testbed.cpp
diff --git a/engines/testbed/module.mk b/engines/testbed/module.mk
index d8ff0e7..0dcbfb7 100644
--- a/engines/testbed/module.mk
+++ b/engines/testbed/module.mk
@@ -12,6 +12,7 @@ MODULE_OBJS := \
savegame.o \
sound.o \
encoding.o \
+ speech.o \
testbed.o \
testsuite.o
diff --git a/engines/testbed/speech.cpp b/engines/testbed/speech.cpp
new file mode 100644
index 0000000..65fbbfd
--- /dev/null
+++ b/engines/testbed/speech.cpp
@@ -0,0 +1,430 @@
+/* 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 "common/text-to-speech.h"
+#include "common/system.h"
+#include "common/array.h"
+#include "engines/testbed/speech.h"
+
+namespace Testbed {
+
+TestExitStatus Speechtests::testMale() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ Testsuite::clearScreen();
+ Common::String info = "Male voice test. You should expect a male voice to say \"Testing text to speech with male voice.\"";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing male TTS voice", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testMale\n");
+ return kTestSkipped;
+ }
+
+ Common::Array<int> maleVoices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
+ if (maleVoices.size() == 0) {
+ Testsuite::displayMessage("No male voice available");
+ return kTestFailed;
+ }
+ ttsMan->setVoice(maleVoices[0]);
+ ttsMan->say("Testing text to speech with male voice.");
+ while (ttsMan->isSpeaking()) {
+ g_system->delayMillis(1000);
+ }
+ Common::String prompt = "Did you hear male voice saying: \"Testing text to speech with male voice.\" ?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("Male TTS failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testFemale() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ Testsuite::clearScreen();
+ Common::String info = "Female voice test. You should expect a female voice to say \"Testing text to speech with female voice.\"";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing female TTS voice", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testFemale\n");
+ return kTestSkipped;
+ }
+
+ Common::Array<int> femaleVoices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
+ if (femaleVoices.size() == 0) {
+ Testsuite::displayMessage("No female voice available");
+ }
+ ttsMan->setVoice(femaleVoices[0]);
+ ttsMan->say("Testing text to speech with female voice.");
+ while (ttsMan->isSpeaking()) {
+ g_system->delayMillis(1000);
+ }
+ Common::String prompt = "Did you hear female voice saying: \"Testing text to speech with female voice.\" ?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("Female TTS failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testStop() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech stop test. You should expect a voice to start speaking and after approximately a second it should stop the speech";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS stop", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testStop\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->say("Testing text to speech, the speech should stop after approximately a second after it started, so it shouldn't have the time to read this.");
+ g_system->delayMillis(1000);
+ ttsMan->stop();
+ Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should stop after approximately a second after it started, so it shouldn't have the time to read this.\" but stopping in the middle?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS stop failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testPauseResume() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech pause test. You should expect a voice to start speaking, then after approximately a second of speech, it should pause for about a second and then continue from where it left.";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS pause", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testPauseResume\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->say("Testing text to speech, the speech should pause after a second and then resume again.");
+ g_system->delayMillis(1000);
+ ttsMan->pause();
+ g_system->delayMillis(2000);
+ ttsMan->resume();
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should pause after a second and then resume again.\" but with a second long pause in the middle?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS pauseResume failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testRate() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech rate test. You should expect a voice to say: \"Text to speech slow rate.\" really slowly and then \"Text to speech fast rate\" really fast";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS rate", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testRate\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->setRate(-100);
+ ttsMan->say("Text to speech slow rate.");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ ttsMan->setRate(100);
+ ttsMan->say("Text to speech fast rate.");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ Common::String prompt = "Did you hear a voice saying: \"Text to speech slow rate.\" slowly and then \"Text to speech fast rate.\" fast?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS rate failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testVolume() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech volume test. You should expect a voice to say: \"Text to speech low volume.\" quietly and then \"Text to speech max volume\" at a higher volume";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS volume", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testVolume\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->setVolume(20);
+ ttsMan->say("Text to speech low volume.");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ ttsMan->setVolume(100);
+ ttsMan->say("Text to speech max volume.");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ Common::String prompt = "Did you hear a voice saying: \"Text to speech low volume.\" quietly and then \"Text to speech max volume.\" at a higher volume?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS volume failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testPitch() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech pitch test. You should expect a high pitched voice to say: \"Text to speech high pitch.\" and then a low pitched voice: \"Text to speech low pitch\"";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS pitch", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testPitch\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->setPitch(100);
+ ttsMan->say("Text to speech high pitch.");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ ttsMan->setPitch(-100);
+ ttsMan->say("Text to speech low pitch.");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ Common::String prompt = "Did you hear a high pitched voice saying: \"Text to speech high pitch.\" and then a low pitched voice: \"Text to speech low pitch.\" ?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS pitch failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testStateStacking() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech state stacking test. You should expect a speech from three different voices (different pitch, gender, volume and speech rate), each voice will say: \"Voice number X is speaking.\", the voices will speak in this order: 1, 2, 3, 2, 1. A voice with the same number should sound the same every time";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS state stacking", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testStateStacking\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->say("Voice number 1 is speaking");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ ttsMan->pushState();
+ Common::Array<int> femaleVoices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
+ Common::Array<Common::TTSVoice> allVoices = ttsMan->getVoicesArray();
+ if (femaleVoices.size() == 0)
+ ttsMan->setVoice(1 % allVoices.size());
+ else
+ ttsMan->setVoice(femaleVoices[0]);
+ ttsMan->setVolume(80);
+ ttsMan->setPitch(40);
+ ttsMan->setRate(-30);
+ ttsMan->say("Voice number 2 is speaking");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ ttsMan->pushState();
+ ttsMan->setVoice(2 % allVoices.size());
+ ttsMan->setVolume(90);
+ ttsMan->setPitch(-80);
+ ttsMan->setRate(-50);
+ ttsMan->say("Voice number 3 is speaking");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ ttsMan->popState();
+ ttsMan->say("Voice number 2 is speaking");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ ttsMan->popState();
+ ttsMan->say("Voice number 1 is speaking");
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+
+ Common::String prompt = "Did you hear three different voices speaking in this order: 1, 2, 3, 2, 1 and each time the same voice spoke, it sounded the same?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS state stacking\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testQueueing() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech queue test. You should expect a voice to say: \"This is first speech. This is queued second speech\"";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS queue", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testQueueing\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->say("This is first speech.");
+ ttsMan->say("This is second speech.", Common::TextToSpeechManager::QUEUE);
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ Common::String prompt = "Did you hear a voice saying: \"This is first speech. This is second speech\" ?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS queue failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testInterrupting() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech interrupt test. You should expect a voice to start saying english alphabet and after about a second it should get interrupted and say: \"Speech intprrupted\" instead.";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS interrupt", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testInterrupting\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->say("A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z");
+ g_system->delayMillis(1000);
+ ttsMan->say("Speech interrupted", Common::TextToSpeechManager::INTERRUPT);
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ Common::String prompt = "Did you hear a voice saying the engilsh alphabet, but it got interrupted and said: \"Speech interrupted\" instead?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS interrupt failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testDroping() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech drop test. You should expect a voice to start say:\"Today is a really nice weather, perfect day to use ScummVM, don't you think?\" and nothing else.";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS drop", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testDroping\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->say("Today is a really nice weather, perfect day to use ScummVM, don't you think?");
+ ttsMan->say("Speech interrupted, fail", Common::TextToSpeechManager::DROP);
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ Common::String prompt = "Did you hear a voice say: \"Today is a really nice weather, perfect day to use ScummVM, don't you think?\" and nothing else?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS drop failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+SpeechTestSuite::SpeechTestSuite() {
+ _isTsEnabled = true;
+ if (!g_system->getTextToSpeechManager())
+ _isTsEnabled = false;
+ addTest("testMale", &Speechtests::testMale, true);
+ addTest("testFemale", &Speechtests::testFemale, true);
+ addTest("testStop", &Speechtests::testStop, true);
+ addTest("testPauseResume", &Speechtests::testPauseResume, true);
+ addTest("testRate", &Speechtests::testRate, true);
+ addTest("testVolume", &Speechtests::testVolume, true);
+ addTest("testPitch", &Speechtests::testPitch, true);
+ addTest("testStateStacking", &Speechtests::testStateStacking, true);
+ addTest("testQueueing", &Speechtests::testQueueing, true);
+ addTest("testInterrupting", &Speechtests::testInterrupting, true);
+ addTest("testDroping", &Speechtests::testDroping, true);
+}
+
+} // End of namespace Testbed
diff --git a/engines/testbed/speech.h b/engines/testbed/speech.h
new file mode 100644
index 0000000..a5f576d
--- /dev/null
+++ b/engines/testbed/speech.h
@@ -0,0 +1,78 @@
+/* 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 TESTBED_TEMPLATE_H
+#define TESTBED_TEMPLATE_H
+
+#include "testbed/testsuite.h"
+#include "common/text-to-speech.h"
+
+namespace Testbed {
+
+namespace Speechtests {
+
+// Helper functions for Speech tests
+
+// will contain function declarations for Speech tests
+// add more here
+
+TestExitStatus testMale();
+TestExitStatus testFemale();
+TestExitStatus testStop();
+TestExitStatus testPauseResume();
+TestExitStatus testRate();
+TestExitStatus testVolume();
+TestExitStatus testPitch();
+TestExitStatus testStateStacking();
+TestExitStatus testQueueing();
+TestExitStatus testInterrupting();
+TestExitStatus testDroping();
+
+
+} // End of namespace Speechtests
+
+class SpeechTestSuite : public Testsuite {
+public:
+ /**
+ * The constructor for the XXXTestSuite
+ * For every test to be executed one must:
+ * 1) Create a function that would invoke the test
+ * 2) Add that test to list by executing addTest()
+ *
+ * @see addTest()
+ */
+ SpeechTestSuite();
+ ~SpeechTestSuite() {}
+ const char *getName() const {
+ return "Speech";
+ }
+
+ const char *getDescription() const {
+ return "Speech Subsystem";
+ }
+
+};
+
+
+} // End of namespace Testbed
+
+#endif // TESTBED_TEMPLATE_H
diff --git a/engines/testbed/testbed.cpp b/engines/testbed/testbed.cpp
index 1b0c2dd..44d491f 100644
--- a/engines/testbed/testbed.cpp
+++ b/engines/testbed/testbed.cpp
@@ -46,6 +46,9 @@
#ifdef USE_SDL_NET
#include "testbed/webserver.h"
#endif
+#ifdef USE_TTS
+#include "testbed/speech.h"
+#endif
namespace Testbed {
@@ -120,8 +123,14 @@ TestbedEngine::TestbedEngine(OSystem *syst)
DebugMan.enableDebugChannel("LOG");
// Initialize testsuites here
+ Testsuite *ts;
+#ifdef USE_TTS
+ // TextToSpeech
+ ts = new SpeechTestSuite();
+ _testsuiteList.push_back(ts);
+#endif
// GFX
- Testsuite *ts = new GFXTestSuite();
+ ts = new GFXTestSuite();
_testsuiteList.push_back(ts);
// FS
ts = new FSTestSuite();
Commit: 1234f8e42fbcb5da16744deaa62c186778f14184
https://github.com/scummvm/scummvm/commit/1234f8e42fbcb5da16744deaa62c186778f14184
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Fix pause() and resume() on linux
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index c5ff8e0..c63c4c7 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -51,7 +51,10 @@ void speech_end_callback(size_t msg_id, size_t client_id, SPDNotificationType st
void speech_cancel_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
LinuxTextToSpeechManager *manager =
static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::READY);
+ if (manager->isSpeaking())
+ manager->updateState(LinuxTextToSpeechManager::READY);
+ if (manager->isPaused())
+ manager->updateState(LinuxTextToSpeechManager::PAUSED);
}
void speech_resume_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
@@ -170,8 +173,14 @@ bool LinuxTextToSpeechManager::stop() {
bool LinuxTextToSpeechManager::pause() {
if (_speechState == READY || _speechState == PAUSED || _speechState == BROKEN)
return true;
+ bool result = spd_pause_all(_connection) == -1;
+ if (result)
+ return true;
+ result = spd_stop(_connection) == -1;
+ if (result)
+ return true;
_speechState = PAUSED;
- return spd_pause(_connection) == -1;
+ return false;
}
bool LinuxTextToSpeechManager::resume() {
Commit: feaba6fff62d92c057579d9a95ed085d8bd3d159
https://github.com/scummvm/scummvm/commit/feaba6fff62d92c057579d9a95ed085d8bd3d159
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Reimplement isSpeaking on Windows.
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 196a909..0731239 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -165,9 +165,11 @@ bool WindowsTextToSpeechManager::resume() {
bool WindowsTextToSpeechManager::isSpeaking() {
if(_speechState == BROKEN || _speechState == NO_VOICE)
return false;
- SPVOICESTATUS eventStatus;
- _voice->GetStatus(&eventStatus, NULL);
- return eventStatus.dwRunningState == SPRS_IS_SPEAKING;
+ SPAUDIOSTATUS audioStatus;
+ SPVOICESTATUS voiceStatus;
+ _audio->GetStatus(&audioStatus);
+ _voice->GetStatus(&voiceStatus, NULL);
+ return audioStatus.State != SPAS_CLOSED || voiceStatus.dwRunningState != SPRS_DONE;
}
bool WindowsTextToSpeechManager::isPaused() {
Commit: 98cea3e2cef3bf4c723967af60dfb961ab4bafaf
https://github.com/scummvm/scummvm/commit/98cea3e2cef3bf4c723967af60dfb961ab4bafaf
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBED: Add state queries to TTS tests.
Changed paths:
engines/testbed/speech.cpp
diff --git a/engines/testbed/speech.cpp b/engines/testbed/speech.cpp
index 65fbbfd..266b8f7 100644
--- a/engines/testbed/speech.cpp
+++ b/engines/testbed/speech.cpp
@@ -51,6 +51,10 @@ TestExitStatus Speechtests::testMale() {
}
ttsMan->setVoice(maleVoices[0]);
ttsMan->say("Testing text to speech with male voice.");
+ if (!ttsMan->isSpeaking()) {
+ Testsuite::logDetailedPrintf("Male TTS failed\n");
+ return kTestFailed;
+ }
while (ttsMan->isSpeaking()) {
g_system->delayMillis(1000);
}
@@ -85,6 +89,10 @@ TestExitStatus Speechtests::testFemale() {
}
ttsMan->setVoice(femaleVoices[0]);
ttsMan->say("Testing text to speech with female voice.");
+ if (!ttsMan->isSpeaking()) {
+ Testsuite::logDetailedPrintf("Female TTS failed\n");
+ return kTestFailed;
+ }
while (ttsMan->isSpeaking()) {
g_system->delayMillis(1000);
}
@@ -117,6 +125,13 @@ TestExitStatus Speechtests::testStop() {
ttsMan->say("Testing text to speech, the speech should stop after approximately a second after it started, so it shouldn't have the time to read this.");
g_system->delayMillis(1000);
ttsMan->stop();
+ // It is allright if the voice isn't available right away, but a second should be
+ // enough for the TTS to recover and get ready.
+ g_system->delayMillis(1000);
+ if (!ttsMan->isReady()) {
+ Testsuite::logDetailedPrintf("TTS stop failed\n");
+ return kTestFailed;
+ }
Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should stop after approximately a second after it started, so it shouldn't have the time to read this.\" but stopping in the middle?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS stop failed\n");
@@ -146,8 +161,20 @@ TestExitStatus Speechtests::testPauseResume() {
ttsMan->say("Testing text to speech, the speech should pause after a second and then resume again.");
g_system->delayMillis(1000);
ttsMan->pause();
+ if (!ttsMan->isPaused()) {
+ Testsuite::logDetailedPrintf("TTS pause failed\n");
+ return kTestFailed;
+ }
g_system->delayMillis(2000);
+ if (!ttsMan->isPaused()) {
+ Testsuite::logDetailedPrintf("TTS pause failed\n");
+ return kTestFailed;
+ }
ttsMan->resume();
+ if (!ttsMan->isSpeaking()) {
+ Testsuite::logDetailedPrintf("TTS pause failed\n");
+ return kTestFailed;
+ }
while (ttsMan->isSpeaking())
g_system->delayMillis(1000);
Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should pause after a second and then resume again.\" but with a second long pause in the middle?";
Commit: 4bae32ffe7952ca967b16c8043c1957e9a3379b2
https://github.com/scummvm/scummvm/commit/4bae32ffe7952ca967b16c8043c1957e9a3379b2
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add *_NO_REPEAT actions
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index c63c4c7..97d05a6 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -81,6 +81,7 @@ void LinuxTextToSpeechManager::init() {
warning("Couldn't initialize text to speech through speech-dispatcher");
return;
}
+ _lastSaid = "";
_connection->callback_begin = speech_begin_callback;
spd_set_notification_on(_connection, SPD_BEGIN);
@@ -137,6 +138,12 @@ bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::St
if (action == DROP && isSpeaking())
return true;
+
+ if (action == INTERRUPT_NO_REPEAT && _lastSaid == str && isSpeaking())
+ return true;
+
+ if (action == QUEUE_NO_REPEAT && _lastSaid == str && isSpeaking())
+ return true;
if (charset.empty()) {
#ifdef USE_TRANSLATION
@@ -148,10 +155,11 @@ bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::St
str = strToUtf8(str, charset);
- if (isSpeaking() && action == INTERRUPT)
+ if (isSpeaking() && action == INTERRUPT || action == INTERRUPT_NO_REPEAT)
stop();
if (str.size() != 0)
_speechState = SPEAKING;
+ _lastSaid = str;
if(spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1) {
//restart the connection
if (_connection != 0)
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 49701ab..30fa9b8 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -72,6 +72,7 @@ private:
void createVoice(int typeNumber, Common::TTSVoice::Gender, Common::TTSVoice::Age, char *description);
SpeechState _speechState;
Common::String strToUtf8(Common::String str, Common::String charset);
+ Common::String _lastSaid;
};
#endif
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 0731239..d0254fc 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -88,6 +88,7 @@ void WindowsTextToSpeechManager::init() {
_speechState = READY;
else
_speechState = NO_VOICE;
+ _lastSaid = "";
}
WindowsTextToSpeechManager::~WindowsTextToSpeechManager() {
@@ -105,6 +106,12 @@ bool WindowsTextToSpeechManager::say(Common::String str, Action action, Common::
if (isSpeaking() && action == DROP)
return true;
+ if (isSpeaking() && action == INTERRUPT_NO_REPEAT && _lastSaid == str)
+ return true;
+
+ if (isSpeaking() && action == QUEUE_NO_REPEAT && _lastSaid == str)
+ return true;
+
if (charset.empty()) {
#ifdef USE_TRANSLATION
charset = TransMan.getCurrentCharset();
@@ -112,6 +119,7 @@ bool WindowsTextToSpeechManager::say(Common::String str, Action action, Common::
charset = "ASCII";
#endif
}
+ _lastSaid = str;
// We have to set the pitch by prepending xml code at the start of the said string;
Common::String pitch= Common::String::format("<pitch absmiddle=\"%d\">", _ttsState->_pitch / 10);
str.replace((uint32)0, 0, pitch);
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index 7a02c19..d896824 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -71,6 +71,7 @@ private:
void createVoice(void *cpVoiceToken);
Common::String lcidToLocale(Common::String lcid);
SpeechState _speechState;
+ Common::String _lastSaid;
};
#endif
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index df0bee8..2ec9e1e 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -138,7 +138,9 @@ class TextToSpeechManager {
public:
enum Action {
INTERRUPT,
+ INTERRUPT_NO_REPEAT,
QUEUE,
+ QUEUE_NO_REPEAT,
DROP
};
/**
@@ -156,7 +158,7 @@ public:
* @param charset The encoding of the string. If empty this is assumed to be the
* encoding used for the GUI.
*/
- bool say(String str, String charset = "") { return say(str, INTERRUPT, charset); }
+ bool say(String str, String charset = "") { return say(str, INTERRUPT_NO_REPEAT, charset); }
/**
* Says the given string
Commit: fb12e3b36b4db7118fccfdff79f450c69bcf9b2f
https://github.com/scummvm/scummvm/commit/fb12e3b36b4db7118fccfdff79f450c69bcf9b2f
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBED: Rewrite parts of TTS tests.
As suggested by Criezy on github
Changed paths:
engines/testbed/speech.cpp
diff --git a/engines/testbed/speech.cpp b/engines/testbed/speech.cpp
index 266b8f7..bca6ad7 100644
--- a/engines/testbed/speech.cpp
+++ b/engines/testbed/speech.cpp
@@ -85,7 +85,8 @@ TestExitStatus Speechtests::testFemale() {
Common::Array<int> femaleVoices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
if (femaleVoices.size() == 0) {
- Testsuite::displayMessage("No female voice available");
+ Testsuite::logDetailedPrintf("Female TTS failed\n");
+ return kTestFailed;
}
ttsMan->setVoice(femaleVoices[0]);
ttsMan->say("Testing text to speech with female voice.");
@@ -165,7 +166,7 @@ TestExitStatus Speechtests::testPauseResume() {
Testsuite::logDetailedPrintf("TTS pause failed\n");
return kTestFailed;
}
- g_system->delayMillis(2000);
+ g_system->delayMillis(1000);
if (!ttsMan->isPaused()) {
Testsuite::logDetailedPrintf("TTS pause failed\n");
return kTestFailed;
@@ -384,7 +385,7 @@ TestExitStatus Speechtests::testInterrupting() {
ttsMan->setPitch(0);
ttsMan->setVoice(0);
Testsuite::clearScreen();
- Common::String info = "Text to speech interrupt test. You should expect a voice to start saying english alphabet and after about a second it should get interrupted and say: \"Speech intprrupted\" instead.";
+ Common::String info = "Text to speech interrupt test. You should expect a voice to start saying english alphabet and after about a second it should get interrupted and say: \"Speech interrupted\" instead.";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS interrupt", pt);
Commit: 84df34df108f2c2166dcaa967ee10d84e06f0002
https://github.com/scummvm/scummvm/commit/84df34df108f2c2166dcaa967ee10d84e06f0002
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBED: Add tests for TTS *_NO_REPEAT actions
Changed paths:
engines/testbed/speech.cpp
engines/testbed/speech.h
diff --git a/engines/testbed/speech.cpp b/engines/testbed/speech.cpp
index bca6ad7..6715ee3 100644
--- a/engines/testbed/speech.cpp
+++ b/engines/testbed/speech.cpp
@@ -438,6 +438,77 @@ TestExitStatus Speechtests::testDroping() {
return kTestPassed;
}
+TestExitStatus Speechtests::testInterruptNoRepeat() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech inturrept no repeat test. You should expect a voice to start saying:\"This is the first sentence, this should get interrupted\", but the speech gets interrupted and \"This is the second sentence, it should play only once\" is said instead.";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS Interrupt No Repeat", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testInterruptNoRepeat\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->say("This is the first sentence, this should get interrupted");
+ g_system->delayMillis(1000);
+ ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
+ g_system->delayMillis(1000);
+ ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
+ g_system->delayMillis(1000);
+ ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ Common::String prompt = "Did you hear a voice say: \"This is the first sentence, this should get interrupted\", but it got interrupted and \"This is the second sentence, it should play only once.\" got said instead?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS interruptNoRepeat failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
+TestExitStatus Speechtests::testQueueNoRepeat() {
+ Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
+ ttsMan->setLanguage("en");
+ ttsMan->setVolume(100);
+ ttsMan->setRate(0);
+ ttsMan->setPitch(0);
+ ttsMan->setVoice(0);
+ Testsuite::clearScreen();
+ Common::String info = "Text to speech queue no repeat test. You should expect a voice to start say:\"This is the first sentence. This is the second sentence\" and nothing else";
+
+ Common::Point pt(0, 100);
+ Testsuite::writeOnScreen("Testing TTS Queue No Repeat", pt);
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : testQueueNoRepeat\n");
+ return kTestSkipped;
+ }
+
+ ttsMan->say("This is the first sentence.");
+ ttsMan->say("This is the first sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
+ g_system->delayMillis(1000);
+ ttsMan->say("This is the first sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
+ ttsMan->say("This is the second sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
+ ttsMan->say("This is the second sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
+ g_system->delayMillis(1000);
+ ttsMan->say("This is the second sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
+ while (ttsMan->isSpeaking())
+ g_system->delayMillis(1000);
+ Common::String prompt = "Did you hear a voice say: \"This is the first sentence. This the second sentence\" and nothing else?";
+ if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
+ Testsuite::logDetailedPrintf("TTS QueueNoRepeat failed\n");
+ return kTestFailed;
+ }
+ return kTestPassed;
+}
+
SpeechTestSuite::SpeechTestSuite() {
_isTsEnabled = true;
if (!g_system->getTextToSpeechManager())
@@ -453,6 +524,8 @@ SpeechTestSuite::SpeechTestSuite() {
addTest("testQueueing", &Speechtests::testQueueing, true);
addTest("testInterrupting", &Speechtests::testInterrupting, true);
addTest("testDroping", &Speechtests::testDroping, true);
+ addTest("testInterruptNoRepeat", &Speechtests::testInterruptNoRepeat, true);
+ addTest("testQueueNoRepeat", &Speechtests::testQueueNoRepeat, true);
}
} // End of namespace Testbed
diff --git a/engines/testbed/speech.h b/engines/testbed/speech.h
index a5f576d..4600ff7 100644
--- a/engines/testbed/speech.h
+++ b/engines/testbed/speech.h
@@ -46,6 +46,8 @@ TestExitStatus testStateStacking();
TestExitStatus testQueueing();
TestExitStatus testInterrupting();
TestExitStatus testDroping();
+TestExitStatus testInterruptNoRepeat();
+TestExitStatus testQueueNoRepeat();
} // End of namespace Speechtests
Commit: 7613bcaa5f10f7537210bd7abd74d1d3ec7e7ac3
https://github.com/scummvm/scummvm/commit/7613bcaa5f10f7537210bd7abd74d1d3ec7e7ac3
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Use QUEUE_NO_REPEAT action for tooltips
Changed paths:
gui/Tooltip.cpp
diff --git a/gui/Tooltip.cpp b/gui/Tooltip.cpp
index 1f57ba9..cdfc43f 100644
--- a/gui/Tooltip.cpp
+++ b/gui/Tooltip.cpp
@@ -60,7 +60,7 @@ void Tooltip::setup(Dialog *parent, Widget *widget, int x, int y) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan == nullptr)
return;
- ttsMan->say(widget->getTooltip(), Common::TextToSpeechManager::DROP);
+ ttsMan->say(widget->getTooltip(), Common::TextToSpeechManager::QUEUE_NO_REPEAT);
}
#endif
}
Commit: 21fb4cef06ab84f90d200d448e9742cb75d3f53a
https://github.com/scummvm/scummvm/commit/21fb4cef06ab84f90d200d448e9742cb75d3f53a
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement our own queuing for linux
It seems like, that at least some versions of speech-dispatcher
aren't able to successfuly pause and resume. For me, when trying
to pause, it still finishes the speech just being said instead
of pausing it and then it puts it at the end of the speech queue
with some speech-dispatcher internal commands added to it, which
are also hearable.
There is no way to find out where the speech ended when calling
pause, so it is just stopped and when resume is called it is
read from it's start again.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 97d05a6..20ba07d 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -28,45 +28,42 @@
#if defined(USE_LINUX_TTS)
#include <speech-dispatcher/libspeechd.h>
#include "backends/platform/sdl/sdl-sys.h"
-//#include <iconv.h>
#include "common/translation.h"
#include "common/system.h"
#include "common/ustr.h"
#include "common/config-manager.h"
+
SPDConnection *_connection;
void speech_begin_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
LinuxTextToSpeechManager *manager =
static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::SPEAKING);
+ manager->updateState(LinuxTextToSpeechManager::SPEECH_BEGUN);
}
void speech_end_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
LinuxTextToSpeechManager *manager =
static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::READY);
+ manager->updateState(LinuxTextToSpeechManager::SPEECH_ENDED);
}
void speech_cancel_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
LinuxTextToSpeechManager *manager =
static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- if (manager->isSpeaking())
- manager->updateState(LinuxTextToSpeechManager::READY);
- if (manager->isPaused())
- manager->updateState(LinuxTextToSpeechManager::PAUSED);
+ manager->updateState(LinuxTextToSpeechManager::SPEECH_CANCELED);
}
void speech_resume_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
LinuxTextToSpeechManager *manager =
static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::SPEAKING);
+ manager->updateState(LinuxTextToSpeechManager::SPEECH_RESUMED);
}
void speech_pause_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
LinuxTextToSpeechManager *manager =
static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::PAUSED);
+ manager->updateState(LinuxTextToSpeechManager::SPEECH_PAUSED);
}
LinuxTextToSpeechManager::LinuxTextToSpeechManager()
@@ -101,6 +98,7 @@ void LinuxTextToSpeechManager::init() {
#else
setLanguage("en");
#endif
+ _speechQueue.clear();
}
LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
@@ -108,8 +106,29 @@ LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
spd_close(_connection);
}
-void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechState state) {
- _speechState = state;
+void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechEvent event) {
+ if (_speechState == BROKEN)
+ return;
+ switch(event) {
+ case SPEECH_ENDED:
+ _speechQueue.pop_front();
+ if (_speechQueue.size() == 0)
+ _speechState = READY;
+ break;
+ case SPEECH_PAUSED:
+ _speechState = PAUSED;
+ break;
+ case SPEECH_CANCELED:
+ if (_speechState != PAUSED) {
+ _speechState = READY;
+ }
+ break;
+ case SPEECH_RESUMED:
+ break;
+ case SPEECH_BEGUN:
+ _speechState = SPEAKING;
+ break;
+ }
}
Common::String LinuxTextToSpeechManager::strToUtf8(Common::String str, Common::String charset) {
@@ -155,17 +174,19 @@ bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::St
str = strToUtf8(str, charset);
- if (isSpeaking() && action == INTERRUPT || action == INTERRUPT_NO_REPEAT)
+ if (isSpeaking() && (action == INTERRUPT || action == INTERRUPT_NO_REPEAT))
stop();
- if (str.size() != 0)
+ if (!str.empty()) {
_speechState = SPEAKING;
- _lastSaid = str;
- if(spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1) {
- //restart the connection
- if (_connection != 0)
- spd_close(_connection);
- init();
- return true;
+ _speechQueue.push_back(str);
+ _lastSaid = str;
+ if(spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1) {
+ //restart the connection
+ if (_connection != 0)
+ spd_close(_connection);
+ init();
+ return true;
+ }
}
return false;
@@ -175,27 +196,35 @@ bool LinuxTextToSpeechManager::stop() {
if (_speechState == READY || _speechState == BROKEN)
return true;
_speechState = READY;
+ _speechQueue.clear();
return spd_cancel(_connection) == -1;
}
bool LinuxTextToSpeechManager::pause() {
if (_speechState == READY || _speechState == PAUSED || _speechState == BROKEN)
return true;
- bool result = spd_pause_all(_connection) == -1;
+ _speechState = PAUSED;
+ bool result = spd_cancel_all(_connection) == -1;
if (result)
return true;
- result = spd_stop(_connection) == -1;
if (result)
return true;
- _speechState = PAUSED;
return false;
}
bool LinuxTextToSpeechManager::resume() {
if (_speechState == READY || _speechState == SPEAKING || _speechState == BROKEN)
return true;
- _speechState = SPEAKING;
- return spd_resume(_connection) == -1;
+ if (_speechQueue.size()) {
+ _speechState = SPEAKING;
+ for (Common::List<Common::String>::iterator i = _speechQueue.begin(); i != _speechQueue.end(); i++) {
+ if (spd_say(_connection, SPD_MESSAGE, i->c_str()) == -1)
+ return true;
+ }
+ }
+ else
+ _speechState = READY;
+ return false;
}
bool LinuxTextToSpeechManager::isSpeaking() {
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 30fa9b8..23c35c3 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -29,6 +29,7 @@
#include "common/text-to-speech.h"
#include "common/str.h"
+#include "common/list.h"
class LinuxTextToSpeechManager : public Common::TextToSpeechManager {
public:
@@ -39,6 +40,14 @@ public:
BROKEN
};
+ enum SpeechEvent {
+ SPEECH_ENDED,
+ SPEECH_PAUSED,
+ SPEECH_CANCELED,
+ SPEECH_RESUMED,
+ SPEECH_BEGUN
+ };
+
LinuxTextToSpeechManager();
virtual ~LinuxTextToSpeechManager();
@@ -62,7 +71,7 @@ public:
virtual void setLanguage(Common::String language);
- void updateState(SpeechState state);
+ void updateState(SpeechEvent event);
virtual void freeVoiceData(void *data);
@@ -70,9 +79,12 @@ private:
void init();
virtual void updateVoices();
void createVoice(int typeNumber, Common::TTSVoice::Gender, Common::TTSVoice::Age, char *description);
- SpeechState _speechState;
Common::String strToUtf8(Common::String str, Common::String charset);
+ bool spdSay(const char *str);
+
+ SpeechState _speechState;
Common::String _lastSaid;
+ Common::List<Common::String> _speechQueue;
};
#endif
Commit: 5198459bba357c2160218abc7e9424f77db1f2ba
https://github.com/scummvm/scummvm/commit/5198459bba357c2160218abc7e9424f77db1f2ba
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBED: Move the TTS tests after the critical components
Changed paths:
engines/testbed/testbed.cpp
diff --git a/engines/testbed/testbed.cpp b/engines/testbed/testbed.cpp
index 44d491f..7d3d400 100644
--- a/engines/testbed/testbed.cpp
+++ b/engines/testbed/testbed.cpp
@@ -124,11 +124,6 @@ TestbedEngine::TestbedEngine(OSystem *syst)
// Initialize testsuites here
Testsuite *ts;
-#ifdef USE_TTS
- // TextToSpeech
- ts = new SpeechTestSuite();
- _testsuiteList.push_back(ts);
-#endif
// GFX
ts = new GFXTestSuite();
_testsuiteList.push_back(ts);
@@ -150,6 +145,11 @@ TestbedEngine::TestbedEngine(OSystem *syst)
// Midi
ts = new MidiTestSuite();
_testsuiteList.push_back(ts);
+#ifdef USE_TTS
+ // TextToSpeech
+ ts = new SpeechTestSuite();
+ _testsuiteList.push_back(ts);
+#endif
#if defined(USE_CLOUD) && defined(USE_LIBCURL)
// Cloud
ts = new CloudTestSuite();
Commit: bd2757138b88796754e1080f90e146c624bc58d7
https://github.com/scummvm/scummvm/commit/bd2757138b88796754e1080f90e146c624bc58d7
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBED: Make sure to process events while waiting for speech to finish
Same implementations of TextToSpeechManager may require system events
to be processed for the state synchronisation to work properly.
This commit also fixes a few typos or inconsistencies in some texts.
Changed paths:
engines/testbed/speech.cpp
engines/testbed/speech.h
diff --git a/engines/testbed/speech.cpp b/engines/testbed/speech.cpp
index 6715ee3..216c9e4 100644
--- a/engines/testbed/speech.cpp
+++ b/engines/testbed/speech.cpp
@@ -22,11 +22,20 @@
#include "common/text-to-speech.h"
#include "common/system.h"
+#include "common/events.h"
#include "common/array.h"
#include "engines/testbed/speech.h"
namespace Testbed {
+void Speechtests::waitForSpeechEnd(Common::TextToSpeechManager *ttsMan) {
+ Common::Event event;
+ while (ttsMan->isSpeaking()) {
+ g_system->delayMillis(100);
+ g_system->getEventManager()->pollEvent(event);
+ }
+}
+
TestExitStatus Speechtests::testMale() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
ttsMan->setLanguage("en");
@@ -55,9 +64,7 @@ TestExitStatus Speechtests::testMale() {
Testsuite::logDetailedPrintf("Male TTS failed\n");
return kTestFailed;
}
- while (ttsMan->isSpeaking()) {
- g_system->delayMillis(1000);
- }
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear male voice saying: \"Testing text to speech with male voice.\" ?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("Male TTS failed\n");
@@ -94,9 +101,7 @@ TestExitStatus Speechtests::testFemale() {
Testsuite::logDetailedPrintf("Female TTS failed\n");
return kTestFailed;
}
- while (ttsMan->isSpeaking()) {
- g_system->delayMillis(1000);
- }
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear female voice saying: \"Testing text to speech with female voice.\" ?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("Female TTS failed\n");
@@ -176,8 +181,7 @@ TestExitStatus Speechtests::testPauseResume() {
Testsuite::logDetailedPrintf("TTS pause failed\n");
return kTestFailed;
}
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should pause after a second and then resume again.\" but with a second long pause in the middle?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS pauseResume failed\n");
@@ -206,12 +210,11 @@ TestExitStatus Speechtests::testRate() {
ttsMan->setRate(-100);
ttsMan->say("Text to speech slow rate.");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
ttsMan->setRate(100);
ttsMan->say("Text to speech fast rate.");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
+
Common::String prompt = "Did you hear a voice saying: \"Text to speech slow rate.\" slowly and then \"Text to speech fast rate.\" fast?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS rate failed\n");
@@ -240,12 +243,10 @@ TestExitStatus Speechtests::testVolume() {
ttsMan->setVolume(20);
ttsMan->say("Text to speech low volume.");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
ttsMan->setVolume(100);
ttsMan->say("Text to speech max volume.");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying: \"Text to speech low volume.\" quietly and then \"Text to speech max volume.\" at a higher volume?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS volume failed\n");
@@ -274,12 +275,10 @@ TestExitStatus Speechtests::testPitch() {
ttsMan->setPitch(100);
ttsMan->say("Text to speech high pitch.");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
ttsMan->setPitch(-100);
ttsMan->say("Text to speech low pitch.");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a high pitched voice saying: \"Text to speech high pitch.\" and then a low pitched voice: \"Text to speech low pitch.\" ?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS pitch failed\n");
@@ -307,8 +306,7 @@ TestExitStatus Speechtests::testStateStacking() {
}
ttsMan->say("Voice number 1 is speaking");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
ttsMan->pushState();
Common::Array<int> femaleVoices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
Common::Array<Common::TTSVoice> allVoices = ttsMan->getVoicesArray();
@@ -320,24 +318,20 @@ TestExitStatus Speechtests::testStateStacking() {
ttsMan->setPitch(40);
ttsMan->setRate(-30);
ttsMan->say("Voice number 2 is speaking");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
ttsMan->pushState();
ttsMan->setVoice(2 % allVoices.size());
ttsMan->setVolume(90);
ttsMan->setPitch(-80);
ttsMan->setRate(-50);
ttsMan->say("Voice number 3 is speaking");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
ttsMan->popState();
ttsMan->say("Voice number 2 is speaking");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
ttsMan->popState();
ttsMan->say("Voice number 1 is speaking");
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear three different voices speaking in this order: 1, 2, 3, 2, 1 and each time the same voice spoke, it sounded the same?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
@@ -355,7 +349,7 @@ TestExitStatus Speechtests::testQueueing() {
ttsMan->setPitch(0);
ttsMan->setVoice(0);
Testsuite::clearScreen();
- Common::String info = "Text to speech queue test. You should expect a voice to say: \"This is first speech. This is queued second speech\"";
+ Common::String info = "Text to speech queue test. You should expect a voice to say: \"This is first speech. This is second speech\"";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS queue", pt);
@@ -367,8 +361,7 @@ TestExitStatus Speechtests::testQueueing() {
ttsMan->say("This is first speech.");
ttsMan->say("This is second speech.", Common::TextToSpeechManager::QUEUE);
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying: \"This is first speech. This is second speech\" ?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS queue failed\n");
@@ -398,8 +391,7 @@ TestExitStatus Speechtests::testInterrupting() {
ttsMan->say("A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z");
g_system->delayMillis(1000);
ttsMan->say("Speech interrupted", Common::TextToSpeechManager::INTERRUPT);
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice saying the engilsh alphabet, but it got interrupted and said: \"Speech interrupted\" instead?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS interrupt failed\n");
@@ -428,8 +420,7 @@ TestExitStatus Speechtests::testDroping() {
ttsMan->say("Today is a really nice weather, perfect day to use ScummVM, don't you think?");
ttsMan->say("Speech interrupted, fail", Common::TextToSpeechManager::DROP);
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice say: \"Today is a really nice weather, perfect day to use ScummVM, don't you think?\" and nothing else?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS drop failed\n");
@@ -446,7 +437,7 @@ TestExitStatus Speechtests::testInterruptNoRepeat() {
ttsMan->setPitch(0);
ttsMan->setVoice(0);
Testsuite::clearScreen();
- Common::String info = "Text to speech inturrept no repeat test. You should expect a voice to start saying:\"This is the first sentence, this should get interrupted\", but the speech gets interrupted and \"This is the second sentence, it should play only once\" is said instead.";
+ Common::String info = "Text to speech interrupt no repeat test. You should expect a voice to start saying:\"This is the first sentence, this should get interrupted\", but the speech gets interrupted and \"This is the second sentence, it should play only once\" is said instead.";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS Interrupt No Repeat", pt);
@@ -463,8 +454,7 @@ TestExitStatus Speechtests::testInterruptNoRepeat() {
ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
g_system->delayMillis(1000);
ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice say: \"This is the first sentence, this should get interrupted\", but it got interrupted and \"This is the second sentence, it should play only once.\" got said instead?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS interruptNoRepeat failed\n");
@@ -499,8 +489,7 @@ TestExitStatus Speechtests::testQueueNoRepeat() {
ttsMan->say("This is the second sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
g_system->delayMillis(1000);
ttsMan->say("This is the second sentence.", Common::TextToSpeechManager::QUEUE_NO_REPEAT);
- while (ttsMan->isSpeaking())
- g_system->delayMillis(1000);
+ waitForSpeechEnd(ttsMan);
Common::String prompt = "Did you hear a voice say: \"This is the first sentence. This the second sentence\" and nothing else?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS QueueNoRepeat failed\n");
diff --git a/engines/testbed/speech.h b/engines/testbed/speech.h
index 4600ff7..715f252 100644
--- a/engines/testbed/speech.h
+++ b/engines/testbed/speech.h
@@ -49,6 +49,8 @@ TestExitStatus testDroping();
TestExitStatus testInterruptNoRepeat();
TestExitStatus testQueueNoRepeat();
+// Utility function to avoid dupplicated code
+void waitForSpeechEnd(Common::TextToSpeechManager *);
} // End of namespace Speechtests
Commit: 13a32fc5e91d691214c975f5e545f432bd9c37ac
https://github.com/scummvm/scummvm/commit/13a32fc5e91d691214c975f5e545f432bd9c37ac
Author: Thierry Crozat (criezy at scummvm.org)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement *_NO_REPEAT actions and Fix state synchronization issues on macOS
The NSSpeechSynthesizer is asynchronous and does not immediately start, pause,
or stop the speech. As a result querrying the state of the NSSpeechSynthesizer
does not alwats return the expected result (for example isSpeaking may not
yet been true just after we requested starting to speak). So instead the
TextToSpeechManager on macOS keeps track of the state itself.
Changed paths:
backends/text-to-speech/macosx/macosx-text-to-speech.h
backends/text-to-speech/macosx/macosx-text-to-speech.mm
diff --git a/backends/text-to-speech/macosx/macosx-text-to-speech.h b/backends/text-to-speech/macosx/macosx-text-to-speech.h
index 7c33a3b..9ed418d 100644
--- a/backends/text-to-speech/macosx/macosx-text-to-speech.h
+++ b/backends/text-to-speech/macosx/macosx-text-to-speech.h
@@ -68,6 +68,8 @@ private:
SpeechText(const Common::String& txt, const Common::String& enc) : text(txt), encoding(enc) {}
};
Common::Queue<SpeechText> _messageQueue;
+ Common::String _currentSpeech;
+ bool _paused;
};
#endif
diff --git a/backends/text-to-speech/macosx/macosx-text-to-speech.mm b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
index 9f66841..1e8a10e 100644
--- a/backends/text-to-speech/macosx/macosx-text-to-speech.mm
+++ b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
@@ -53,7 +53,7 @@
NSSpeechSynthesizer *synthesizer;
MacOSXTextToSpeechManagerDelegate *synthesizerDelegate;
-MacOSXTextToSpeechManager::MacOSXTextToSpeechManager() : Common::TextToSpeechManager() {
+MacOSXTextToSpeechManager::MacOSXTextToSpeechManager() : Common::TextToSpeechManager(), _paused(false) {
synthesizer = [[NSSpeechSynthesizer alloc] init];
synthesizerDelegate = [[MacOSXTextToSpeechManagerDelegate alloc] initWithManager:this];
[synthesizer setDelegate:synthesizerDelegate];
@@ -71,13 +71,27 @@ MacOSXTextToSpeechManager::~MacOSXTextToSpeechManager() {
}
bool MacOSXTextToSpeechManager::say(Common::String text, Action action, Common::String encoding) {
- if ([synthesizer isSpeaking]) {
+ if (isSpeaking()) {
+ // Interruptions are done on word boundaries for nice transitions.
+ // Should we interrupt immediately?
if (action == DROP)
return true;
else if (action == INTERRUPT) {
_messageQueue.clear();
- // Should we use NSSpeechImmediateBoundary, or even NSSpeechSentenceBoundary?
[synthesizer stopSpeakingAtBoundary:NSSpeechWordBoundary];
+ } else if (action == INTERRUPT_NO_REPEAT) {
+ // If the new speech is the one being currently said, continue that speech but clear the queue.
+ // And otherwise both clear the queue and interrupt the current speech.
+ _messageQueue.clear();
+ if (_currentSpeech == text)
+ return true;
+ [synthesizer stopSpeakingAtBoundary:NSSpeechWordBoundary];
+ } else if (action == QUEUE_NO_REPEAT) {
+ if (!_messageQueue.empty()) {
+ if (_messageQueue.back().text == text)
+ return true;
+ } else if (_currentSpeech == text)
+ return true;
}
}
@@ -88,12 +102,13 @@ bool MacOSXTextToSpeechManager::say(Common::String text, Action action, Common::
}
_messageQueue.push(SpeechText(text, encoding));
- if (![synthesizer isSpeaking])
+ if (!isSpeaking())
startNextSpeech();
return true;
}
bool MacOSXTextToSpeechManager::startNextSpeech() {
+ _currentSpeech.clear();
if (_messageQueue.empty())
return false;
SpeechText text = _messageQueue.pop();
@@ -108,39 +123,56 @@ bool MacOSXTextToSpeechManager::startNextSpeech() {
CFStringRef textNSString = CFStringCreateWithCString(NULL, text.text.c_str(), stringEncoding);
bool status = [synthesizer startSpeakingString:(NSString *)textNSString];
CFRelease(textNSString);
+ if (status)
+ _currentSpeech = text.text;
+
return status;
}
bool MacOSXTextToSpeechManager::stop() {
_messageQueue.clear();
- // Should we use NSSpeechImmediateBoundary, or even NSSpeechSentenceBoundary?
- [synthesizer stopSpeakingAtBoundary:NSSpeechWordBoundary];
+ _currentSpeech.clear(); // so that it immediately reports that it is no longer speeking
+ // Stop as soon as possible
+ [synthesizer stopSpeakingAtBoundary:NSSpeechImmediateBoundary];
return true;
}
bool MacOSXTextToSpeechManager::pause() {
- // Should we use NSSpeechImmediateBoundary, or even NSSpeechSentenceBoundary?
+ // Pause on a word boundary as pausing/resuming in a middle of words is strange.
[synthesizer pauseSpeakingAtBoundary:NSSpeechWordBoundary];
+ _paused = true;
return true;
}
bool MacOSXTextToSpeechManager::resume() {
+ _paused = false;
[synthesizer continueSpeaking];
return true;
}
bool MacOSXTextToSpeechManager::isSpeaking() {
- return [synthesizer isSpeaking];
+ // Because the NSSpeechSynthesizer is asynchronous, it doesn't start speeking immediately
+ // and thus using [synthesizer isSpeaking] just after [synthesizer startSpeakingString:]] is
+ // likely to return NO. So instead we check the _currentSpeech string (set when calling
+ // startSpeakingString, and cleared when we receive the didFinishSpeaking message).
+ //return [synthesizer isSpeaking];
+ return !_currentSpeech.empty();
}
bool MacOSXTextToSpeechManager::isPaused() {
- NSDictionary *statusDict = (NSDictionary*) [synthesizer objectForProperty:NSSpeechStatusProperty error:nil];
- return [[statusDict objectForKey:NSSpeechStatusOutputBusy] boolValue] && [[statusDict objectForKey:NSSpeechStatusOutputPaused] boolValue];
+ // Because the NSSpeechSynthesizer is asynchronous, and because we pause at the end of a word
+ // and not immediately, we cannot check the speech status as it is likely to not be paused yet
+ // immediately after we requested the pause. So we keep our own flag.
+ //NSDictionary *statusDict = (NSDictionary*) [synthesizer objectForProperty:NSSpeechStatusProperty error:nil];
+ //return [[statusDict objectForKey:NSSpeechStatusOutputBusy] boolValue] && [[statusDict objectForKey:NSSpeechStatusOutputPaused] boolValue];
+ return _paused;
}
bool MacOSXTextToSpeechManager::isReady() {
- NSDictionary *statusDict = (NSDictionary*) [synthesizer objectForProperty:NSSpeechStatusProperty error:nil];
- return [[statusDict objectForKey:NSSpeechStatusOutputBusy] boolValue] == NO;
+ // See comments in isSpeaking() and isPaused()
+ //NSDictionary *statusDict = (NSDictionary*) [synthesizer objectForProperty:NSSpeechStatusProperty error:nil];
+ //return [[statusDict objectForKey:NSSpeechStatusOutputBusy] boolValue] == NO;
+ return _currentSpeech.empty() && !_paused;
}
void MacOSXTextToSpeechManager::setVoice(unsigned index) {
Commit: 4036b73b6c5fb776c7bb62209bf6b4fe36771667
https://github.com/scummvm/scummvm/commit/4036b73b6c5fb776c7bb62209bf6b4fe36771667
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Fix INTERRUPT_NO_REPEAT on Windows
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index d0254fc..7c8f879 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -125,7 +125,7 @@ bool WindowsTextToSpeechManager::say(Common::String str, Action action, Common::
str.replace((uint32)0, 0, pitch);
WCHAR *strW = Win32::ansiToUnicode(str.c_str(), Win32::getCodePageId(charset));
- if ((isPaused() || isSpeaking()) && action == INTERRUPT)
+ if ((isPaused() || isSpeaking()) && (action == INTERRUPT || action == INTERRUPT_NO_REPEAT))
stop();
bool result = _voice->Speak(strW, SPF_ASYNC, NULL) != S_OK;
Commit: bc101179804bddbb312a0e7ec79df3a29605c2ad
https://github.com/scummvm/scummvm/commit/bc101179804bddbb312a0e7ec79df3a29605c2ad
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Restart SPD if resume() fails.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 20ba07d..cc506db 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -218,8 +218,12 @@ bool LinuxTextToSpeechManager::resume() {
if (_speechQueue.size()) {
_speechState = SPEAKING;
for (Common::List<Common::String>::iterator i = _speechQueue.begin(); i != _speechQueue.end(); i++) {
- if (spd_say(_connection, SPD_MESSAGE, i->c_str()) == -1)
+ if (spd_say(_connection, SPD_MESSAGE, i->c_str()) == -1) {
+ if (_connection != 0)
+ spd_close(_connection);
+ init();
return true;
+ }
}
}
else
Commit: 38e769430aedab13ad5c5b92d246d5393867a003
https://github.com/scummvm/scummvm/commit/38e769430aedab13ad5c5b92d246d5393867a003
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Improve documentation
Changed paths:
common/text-to-speech.h
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 2ec9e1e..d227c95 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -165,6 +165,16 @@ public:
*
* @param str The string to say
* @param action What to do if another string is just being said.
+ * Possible actions are:
+ * INTERRUPT - interrupts the current speech
+ * INTERRUPT_NO_REPEAT - interrupts the current speech only if the str
+ * is different than the last string in the queue (or the string, that
+ * is currently being said if the queue is empty)
+ * QUEUE - queues the speech
+ * QUEUE_NO_REPEAT - queues the speech only if the str is different than
+ * the last string in the queue (or the string, that is currently
+ * being said if the queue is empty)
+ * DROP - does nothing if there is anything being said at the moment
* @param charset The encoding of the string. If empty this is assumed to be the
* encoding used for the GUI.
*/
@@ -269,6 +279,14 @@ public:
*/
Array<TTSVoice> getVoicesArray() { return _ttsState->_availableVoices; }
+ /**
+ * Returns array of indices of voices from the _availableVoices array, which
+ * have the needed gender.
+ *
+ * @param gender Gender, which indices should be returned
+ *
+ * @return Array of indices into _availableVoices
+ */
Array<int> getVoiceIndicesByGender (TTSVoice::Gender gender);
/**
Commit: f131cad3e5aa5df1465bc027489c8c8947151d3f
https://github.com/scummvm/scummvm/commit/f131cad3e5aa5df1465bc027489c8c8947151d3f
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBED: Improve some TTS tests
Changed paths:
engines/testbed/speech.cpp
diff --git a/engines/testbed/speech.cpp b/engines/testbed/speech.cpp
index 216c9e4..282b4d1 100644
--- a/engines/testbed/speech.cpp
+++ b/engines/testbed/speech.cpp
@@ -164,13 +164,14 @@ TestExitStatus Speechtests::testPauseResume() {
return kTestSkipped;
}
- ttsMan->say("Testing text to speech, the speech should pause after a second and then resume again.");
+ ttsMan->say("Testing text to speech, the speech should pause after a second");
g_system->delayMillis(1000);
ttsMan->pause();
if (!ttsMan->isPaused()) {
Testsuite::logDetailedPrintf("TTS pause failed\n");
return kTestFailed;
}
+ ttsMan->say("and then resume again", Common::TextToSpeechManager::QUEUE);
g_system->delayMillis(1000);
if (!ttsMan->isPaused()) {
Testsuite::logDetailedPrintf("TTS pause failed\n");
@@ -448,10 +449,13 @@ TestExitStatus Speechtests::testInterruptNoRepeat() {
}
ttsMan->say("This is the first sentence, this should get interrupted");
+ ttsMan->say("Failure", Common::TextToSpeechManager::QUEUE);
g_system->delayMillis(1000);
ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
+ ttsMan->say("Failure", Common::TextToSpeechManager::QUEUE);
g_system->delayMillis(1000);
ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
+ ttsMan->say("Failure", Common::TextToSpeechManager::QUEUE);
g_system->delayMillis(1000);
ttsMan->say("This is the second sentence, it should play only once", Common::TextToSpeechManager::INTERRUPT_NO_REPEAT);
waitForSpeechEnd(ttsMan);
Commit: 01d843c527d7808027a546dc5d9a23a981839f8f
https://github.com/scummvm/scummvm/commit/01d843c527d7808027a546dc5d9a23a981839f8f
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Update documentation of INTERRUPT_NO_REPEAT
Changed paths:
common/text-to-speech.h
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index d227c95..6867656 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -167,9 +167,9 @@ public:
* @param action What to do if another string is just being said.
* Possible actions are:
* INTERRUPT - interrupts the current speech
- * INTERRUPT_NO_REPEAT - interrupts the current speech only if the str
- * is different than the last string in the queue (or the string, that
- * is currently being said if the queue is empty)
+ * INTERRUPT_NO_REPEAT - interrupts the speech (deletes the whole queue),
+ * if the str is the same as the string currently being said,
+ * it lets the current string finish.
* QUEUE - queues the speech
* QUEUE_NO_REPEAT - queues the speech only if the str is different than
* the last string in the queue (or the string, that is currently
Commit: be5c865c508c93f6910468a07d3947237e7e0a8e
https://github.com/scummvm/scummvm/commit/be5c865c508c93f6910468a07d3947237e7e0a8e
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add proper speech queuing, update INT_NO_REP.
Before I used SPD to queue messages and I had a copy of the queue,
so I could requeue everything when resume is called(). But more
control of the queue is needed, so I don't use the SPD's queue
and instead start speeches from my queue one by one from another
thread.
INTERRUPT_NO_REPEAT now behaves as described in the documentation
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index cc506db..bad45e8 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -33,6 +33,7 @@
#include "common/system.h"
#include "common/ustr.h"
#include "common/config-manager.h"
+#include <pthread.h>
SPDConnection *_connection;
@@ -66,8 +67,32 @@ void speech_pause_callback(size_t msg_id, size_t client_id, SPDNotificationType
manager->updateState(LinuxTextToSpeechManager::SPEECH_PAUSED);
}
+
+void *LinuxTextToSpeechManager::startSpeech(void *p) {
+ StartSpeechParams *params = (StartSpeechParams *) p;
+ pthread_mutex_lock(params->mutex);
+ if (!_connection || g_system->getTextToSpeechManager()->isPaused() ||
+ params->speechQueue->front().empty()) {
+ pthread_mutex_unlock(params->mutex);
+ return NULL;
+ }
+ if(spd_say(_connection, SPD_MESSAGE, params->speechQueue->front().c_str()) == -1) {
+ // close the connection
+ if (_connection != 0) {
+ spd_close(_connection);
+ _connection = 0;
+ }
+ }
+ pthread_mutex_unlock(params->mutex);
+ return NULL;
+}
+
LinuxTextToSpeechManager::LinuxTextToSpeechManager()
: _speechState(READY) {
+ pthread_mutex_init(&_speechMutex, NULL);
+ _params.mutex = &_speechMutex;
+ _params.speechQueue = &_speechQueue;
+ _threadCreated = false;
init();
}
@@ -78,7 +103,6 @@ void LinuxTextToSpeechManager::init() {
warning("Couldn't initialize text to speech through speech-dispatcher");
return;
}
- _lastSaid = "";
_connection->callback_begin = speech_begin_callback;
spd_set_notification_on(_connection, SPD_BEGIN);
@@ -102,8 +126,12 @@ void LinuxTextToSpeechManager::init() {
}
LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
+ stop();
if (_connection != 0)
spd_close(_connection);
+ if (_threadCreated)
+ pthread_join(_thread, NULL);
+ pthread_mutex_destroy(&_speechMutex);
}
void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechEvent event) {
@@ -111,9 +139,25 @@ void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechEvent
return;
switch(event) {
case SPEECH_ENDED:
+ pthread_mutex_lock(&_speechMutex);
_speechQueue.pop_front();
if (_speechQueue.size() == 0)
_speechState = READY;
+ else {
+ // reinitialize if needed
+ if (!_connection)
+ init();
+ if (_speechState != BROKEN) {
+ if (_threadCreated)
+ pthread_join(_thread, NULL);
+ _threadCreated = true;
+ if (pthread_create(&_thread, NULL, startSpeech, &_params)) {
+ _threadCreated = false;
+ warning("TTS: Cannot start new speech");
+ }
+ }
+ }
+ pthread_mutex_unlock(&_speechMutex);
break;
case SPEECH_PAUSED:
_speechState = PAUSED;
@@ -152,18 +196,22 @@ Common::String LinuxTextToSpeechManager::strToUtf8(Common::String str, Common::S
}
bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::String charset) {
- if (_speechState == BROKEN)
- return true;
- if (action == DROP && isSpeaking())
- return true;
+ pthread_mutex_lock(&_speechMutex);
+ // reinitialize if needed
+ if (!_connection)
+ init();
- if (action == INTERRUPT_NO_REPEAT && _lastSaid == str && isSpeaking())
+ if (_speechState == BROKEN) {
+ pthread_mutex_unlock(&_speechMutex);
return true;
+ }
- if (action == QUEUE_NO_REPEAT && _lastSaid == str && isSpeaking())
+ if (action == DROP && isSpeaking()) {
+ pthread_mutex_unlock(&_speechMutex);
return true;
-
+ }
+
if (charset.empty()) {
#ifdef USE_TRANSLATION
charset = TransMan.getCurrentCharset();
@@ -174,18 +222,30 @@ bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::St
str = strToUtf8(str, charset);
+ if (!_speechQueue.empty() && action == INTERRUPT_NO_REPEAT &&
+ _speechQueue.front() == str && isSpeaking()) {
+ _speechQueue.clear();
+ _speechQueue.push_back(str);
+ pthread_mutex_unlock(&_speechMutex);
+ return true;
+ }
+
+ if (!_speechQueue.empty() && action == QUEUE_NO_REPEAT &&
+ _speechQueue.back() == str && isSpeaking()) {
+ pthread_mutex_unlock(&_speechMutex);
+ return true;
+ }
+
+ pthread_mutex_unlock(&_speechMutex);
if (isSpeaking() && (action == INTERRUPT || action == INTERRUPT_NO_REPEAT))
stop();
if (!str.empty()) {
- _speechState = SPEAKING;
+ pthread_mutex_lock(&_speechMutex);
_speechQueue.push_back(str);
- _lastSaid = str;
- if(spd_say(_connection, SPD_MESSAGE, str.c_str()) == -1) {
- //restart the connection
- if (_connection != 0)
- spd_close(_connection);
- init();
- return true;
+ pthread_mutex_unlock(&_speechMutex);
+ if (isReady()) {
+ _speechState = SPEAKING;
+ startSpeech((void *)(&_params));
}
}
@@ -196,17 +256,20 @@ bool LinuxTextToSpeechManager::stop() {
if (_speechState == READY || _speechState == BROKEN)
return true;
_speechState = READY;
+ pthread_mutex_lock(&_speechMutex);
_speechQueue.clear();
- return spd_cancel(_connection) == -1;
+ bool result = spd_cancel(_connection) == -1;
+ pthread_mutex_unlock(&_speechMutex);
+ return result;
}
bool LinuxTextToSpeechManager::pause() {
if (_speechState == READY || _speechState == PAUSED || _speechState == BROKEN)
return true;
+ pthread_mutex_lock(&_speechMutex);
_speechState = PAUSED;
bool result = spd_cancel_all(_connection) == -1;
- if (result)
- return true;
+ pthread_mutex_unlock(&_speechMutex);
if (result)
return true;
return false;
@@ -215,16 +278,17 @@ bool LinuxTextToSpeechManager::pause() {
bool LinuxTextToSpeechManager::resume() {
if (_speechState == READY || _speechState == SPEAKING || _speechState == BROKEN)
return true;
+ // If there is a thread from before pause() waiting, let it finish (it shouln't
+ // do anything). There shouldn't be any other threads getting created,
+ // because the speech is paused, so we don't need to synchronize
+ if (_threadCreated) {
+ pthread_join(_thread, NULL);
+ _threadCreated = false;
+ }
+ _speechState = PAUSED;
if (_speechQueue.size()) {
_speechState = SPEAKING;
- for (Common::List<Common::String>::iterator i = _speechQueue.begin(); i != _speechQueue.end(); i++) {
- if (spd_say(_connection, SPD_MESSAGE, i->c_str()) == -1) {
- if (_connection != 0)
- spd_close(_connection);
- init();
- return true;
- }
- }
+ startSpeech((void *) &_params);
}
else
_speechState = READY;
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 23c35c3..33f15da 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -30,6 +30,12 @@
#include "common/text-to-speech.h"
#include "common/str.h"
#include "common/list.h"
+#include "common/mutex.h"
+
+struct StartSpeechParams {
+ pthread_mutex_t *mutex;
+ Common::List<Common::String> *speechQueue;
+};
class LinuxTextToSpeechManager : public Common::TextToSpeechManager {
public:
@@ -62,13 +68,9 @@ public:
virtual bool isReady();
virtual void setVoice(unsigned index);
-
virtual void setRate(int rate);
-
virtual void setPitch(int pitch);
-
virtual void setVolume(unsigned volume);
-
virtual void setLanguage(Common::String language);
void updateState(SpeechEvent event);
@@ -80,11 +82,14 @@ private:
virtual void updateVoices();
void createVoice(int typeNumber, Common::TTSVoice::Gender, Common::TTSVoice::Age, char *description);
Common::String strToUtf8(Common::String str, Common::String charset);
- bool spdSay(const char *str);
+ static void *startSpeech(void *p);
+ StartSpeechParams _params;
SpeechState _speechState;
- Common::String _lastSaid;
Common::List<Common::String> _speechQueue;
+ pthread_mutex_t _speechMutex;
+ pthread_t _thread;
+ bool _threadCreated;
};
#endif
Commit: 1a6ad384cb52f3ec5bf98faed77b53a795c71ca0
https://github.com/scummvm/scummvm/commit/1a6ad384cb52f3ec5bf98faed77b53a795c71ca0
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBED: Increase pause in TTS pause test
Changed paths:
engines/testbed/speech.cpp
diff --git a/engines/testbed/speech.cpp b/engines/testbed/speech.cpp
index 282b4d1..cf51f1a 100644
--- a/engines/testbed/speech.cpp
+++ b/engines/testbed/speech.cpp
@@ -154,7 +154,7 @@ TestExitStatus Speechtests::testPauseResume() {
ttsMan->setPitch(0);
ttsMan->setVoice(0);
Testsuite::clearScreen();
- Common::String info = "Text to speech pause test. You should expect a voice to start speaking, then after approximately a second of speech, it should pause for about a second and then continue from where it left.";
+ Common::String info = "Text to speech pause test. You should expect a voice to start speaking, then after approximately a second of speech, it should pause and then continue from where it left.";
Common::Point pt(0, 100);
Testsuite::writeOnScreen("Testing TTS pause", pt);
@@ -172,7 +172,7 @@ TestExitStatus Speechtests::testPauseResume() {
return kTestFailed;
}
ttsMan->say("and then resume again", Common::TextToSpeechManager::QUEUE);
- g_system->delayMillis(1000);
+ g_system->delayMillis(3000);
if (!ttsMan->isPaused()) {
Testsuite::logDetailedPrintf("TTS pause failed\n");
return kTestFailed;
@@ -183,7 +183,7 @@ TestExitStatus Speechtests::testPauseResume() {
return kTestFailed;
}
waitForSpeechEnd(ttsMan);
- Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should pause after a second and then resume again.\" but with a second long pause in the middle?";
+ Common::String prompt = "Did you hear a voice saying: \"Testing text to speech, the speech should pause after a second and then resume again.\" but with a pause in the middle?";
if (!Testsuite::handleInteractiveInput(prompt, "Yes", "No", kOptionLeft)) {
Testsuite::logDetailedPrintf("TTS pauseResume failed\n");
return kTestFailed;
Commit: b1bffaba8682af2870d4ff019bedbceb72440707
https://github.com/scummvm/scummvm/commit/b1bffaba8682af2870d4ff019bedbceb72440707
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Implement our own queuing on windows.
Similarly as on linux, there isn't enough control of the speech
queue to properly implement INTERRUPT_NO_REPEAT. So since this
commit we use our own queuing and use SAPI to speak each speech.
This is done outside the main thread.
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 7c8f879..ec3961d 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -50,6 +50,15 @@ ISpAudio *_audio;
WindowsTextToSpeechManager::WindowsTextToSpeechManager()
: _speechState(BROKEN){
init();
+ _threadParams.queue = &_speechQueue;
+ _threadParams.state = &_speechState;
+ _threadParams.mutex = &_speechMutex;
+ _thread = NULL;
+ _speechMutex = CreateMutex(NULL, FALSE, NULL);
+ if (_speechMutex == NULL) {
+ _speechState = BROKEN;
+ warning("Could not create TTS mutex");
+ }
}
void WindowsTextToSpeechManager::init() {
@@ -84,19 +93,67 @@ void WindowsTextToSpeechManager::init() {
_voice->SetOutput(_audio, FALSE);
- if(_ttsState->_availableVoices.size() > 0)
+ if (_ttsState->_availableVoices.size() > 0)
_speechState = READY;
else
_speechState = NO_VOICE;
_lastSaid = "";
+ while (!_speechQueue.empty()) {
+ free(_speechQueue.front());
+ _speechQueue.pop_front();
+ }
}
WindowsTextToSpeechManager::~WindowsTextToSpeechManager() {
+ stop();
+ if (_thread != NULL) {
+ WaitForSingleObject(_thread, INFINITE);
+ CloseHandle(_thread);
+ }
+ if (_speechMutex != NULL) {
+ CloseHandle(_speechMutex);
+ }
if (_voice)
_voice->Release();
::CoUninitialize();
}
+DWORD WINAPI startSpeech(LPVOID parameters) {
+ WindowsTextToSpeechManager::SpeechParameters *params =
+ (WindowsTextToSpeechManager::SpeechParameters *) parameters;
+ // wait for the previous speech, if the previous thread exited too early
+ _voice->WaitUntilDone(INFINITE);
+
+ while (!params->queue->empty()) {
+ WaitForSingleObject(*params->mutex, INFINITE);
+ // check again, when we have exclusive access to the queue
+ if (params->queue->empty() || *(params->state) == WindowsTextToSpeechManager::PAUSED) {
+ break;
+ }
+ WCHAR *currentSpeech = params->queue->front();
+ _voice->Speak(currentSpeech, SPF_PURGEBEFORESPEAK | SPF_ASYNC, 0);
+ ReleaseMutex(*params->mutex);
+
+ while (*(params->state) != WindowsTextToSpeechManager::PAUSED)
+ if (_voice->WaitUntilDone(10) == S_OK)
+ break;
+
+ WaitForSingleObject(*params->mutex, INFINITE);
+ if (!params->queue->empty() && params->queue->front() == currentSpeech) {
+ if (currentSpeech != NULL)
+ free(currentSpeech);
+ params->queue->pop_front();
+ }
+ ReleaseMutex(*params->mutex);
+ }
+
+ WaitForSingleObject(*params->mutex, INFINITE);
+ if (*(params->state) != WindowsTextToSpeechManager::PAUSED)
+ *(params->state) = WindowsTextToSpeechManager::READY;
+ ReleaseMutex(*params->mutex);
+ return 0;
+}
+
bool WindowsTextToSpeechManager::say(Common::String str, Action action, Common::String charset) {
if (_speechState == BROKEN || _speechState == NO_VOICE) {
warning("The tts cannot speak in this state");
@@ -106,12 +163,6 @@ bool WindowsTextToSpeechManager::say(Common::String str, Action action, Common::
if (isSpeaking() && action == DROP)
return true;
- if (isSpeaking() && action == INTERRUPT_NO_REPEAT && _lastSaid == str)
- return true;
-
- if (isSpeaking() && action == QUEUE_NO_REPEAT && _lastSaid == str)
- return true;
-
if (charset.empty()) {
#ifdef USE_TRANSLATION
charset = TransMan.getCurrentCharset();
@@ -119,65 +170,114 @@ bool WindowsTextToSpeechManager::say(Common::String str, Action action, Common::
charset = "ASCII";
#endif
}
- _lastSaid = str;
+
// We have to set the pitch by prepending xml code at the start of the said string;
Common::String pitch= Common::String::format("<pitch absmiddle=\"%d\">", _ttsState->_pitch / 10);
str.replace((uint32)0, 0, pitch);
WCHAR *strW = Win32::ansiToUnicode(str.c_str(), Win32::getCodePageId(charset));
- if ((isPaused() || isSpeaking()) && (action == INTERRUPT || action == INTERRUPT_NO_REPEAT))
+ WaitForSingleObject(_speechMutex, INFINITE);
+ if (isSpeaking() && !_speechQueue.empty() && action == INTERRUPT_NO_REPEAT &&
+ _speechQueue.front() != NULL && !wcscmp(_speechQueue.front(), strW)) {
+ while (_speechQueue.size() != 1) {
+ free(_speechQueue.back());
+ _speechQueue.pop_back();
+ }
+ free(strW);
+ ReleaseMutex(_speechMutex);
+ return true;
+ }
+
+ if (isSpeaking() && !_speechQueue.empty() && action == QUEUE_NO_REPEAT &&
+ _speechQueue.front() != NULL &&!wcscmp(_speechQueue.back(), strW)) {
+ ReleaseMutex(_speechMutex);
+ return true;
+ }
+
+ ReleaseMutex(_speechMutex);
+ if ((isPaused() || isSpeaking()) && (action == INTERRUPT || action == INTERRUPT_NO_REPEAT)) {
stop();
+ }
- bool result = _voice->Speak(strW, SPF_ASYNC, NULL) != S_OK;
- free(strW);
- if (!isPaused())
+ WaitForSingleObject(_speechMutex, INFINITE);
+ _speechQueue.push_back(strW);
+ ReleaseMutex(_speechMutex);
+
+ if (!isSpeaking() && !isPaused()) {
+ DWORD threadId;
+ if (_thread != NULL) {
+ WaitForSingleObject(_thread, INFINITE);
+ CloseHandle(_thread);
+ }
_speechState = SPEAKING;
- return result;
+ _thread = CreateThread(NULL, 0, startSpeech, &_threadParams, 0, &threadId);
+ if (_thread == NULL) {
+ warning("Could not create speech thread");
+ _speechState = READY;
+ return true;
+ }
+ }
+ return false;
}
bool WindowsTextToSpeechManager::stop() {
- if(_speechState == BROKEN || _speechState == NO_VOICE)
+ if (_speechState == BROKEN || _speechState == NO_VOICE)
return true;
if (isPaused())
resume();
_audio->SetState(SPAS_STOP, 0);
+ WaitForSingleObject(_speechMutex, INFINITE);
+ while (!_speechQueue.empty()) {
+ if (_speechQueue.front() != NULL)
+ free(_speechQueue.front());
+ _speechQueue.pop_front();
+ }
+ _speechQueue.push_back(NULL);
+ ReleaseMutex(_speechMutex);
+ if (_thread != NULL) {
+ WaitForSingleObject(_thread, INFINITE);
+ CloseHandle(_thread);
+ _thread = NULL;
+ }
_audio->SetState(SPAS_RUN, 0);
- _voice->Speak(NULL, SPF_PURGEBEFORESPEAK | SPF_ASYNC | SPF_IS_NOT_XML, 0);
- _speechState = READY;
return false;
}
bool WindowsTextToSpeechManager::pause() {
- if(_speechState == BROKEN || _speechState == NO_VOICE)
+ if (_speechState == BROKEN || _speechState == NO_VOICE)
return true;
if (isPaused())
return false;
+ WaitForSingleObject(_speechMutex, INFINITE);
_voice->Pause();
_speechState = PAUSED;
+ ReleaseMutex(_speechMutex);
return false;
}
bool WindowsTextToSpeechManager::resume() {
- if(_speechState == BROKEN || _speechState == NO_VOICE)
+ if (_speechState == BROKEN || _speechState == NO_VOICE)
return true;
if (!isPaused())
return false;
_voice->Resume();
- if (isSpeaking())
- _speechState = SPEAKING;
- else
+ DWORD threadId;
+ if (_thread != NULL) {
+ WaitForSingleObject(_thread, INFINITE);
+ CloseHandle(_thread);
+ }
+ _speechState = SPEAKING;
+ _thread = CreateThread(NULL, 0, startSpeech, &_threadParams, 0, &threadId);
+ if (_thread == NULL) {
+ warning("Could not create speech thread");
_speechState = READY;
+ return true;
+ }
return false;
}
bool WindowsTextToSpeechManager::isSpeaking() {
- if(_speechState == BROKEN || _speechState == NO_VOICE)
- return false;
- SPAUDIOSTATUS audioStatus;
- SPVOICESTATUS voiceStatus;
- _audio->GetStatus(&audioStatus);
- _voice->GetStatus(&voiceStatus, NULL);
- return audioStatus.State != SPAS_CLOSED || voiceStatus.dwRunningState != SPRS_DONE;
+ return _speechState == SPEAKING;
}
bool WindowsTextToSpeechManager::isPaused() {
@@ -185,7 +285,7 @@ bool WindowsTextToSpeechManager::isPaused() {
}
bool WindowsTextToSpeechManager::isReady() {
- if(_speechState == BROKEN || _speechState == NO_VOICE)
+ if (_speechState == BROKEN || _speechState == NO_VOICE)
return false;
if (_speechState != PAUSED && !isSpeaking())
return true;
@@ -194,14 +294,14 @@ bool WindowsTextToSpeechManager::isReady() {
}
void WindowsTextToSpeechManager::setVoice(unsigned index) {
- if(_speechState == BROKEN || _speechState == NO_VOICE)
+ if (_speechState == BROKEN || _speechState == NO_VOICE)
return;
_voice->SetVoice((ISpObjectToken *) _ttsState->_availableVoices[index].getData());
_ttsState->_activeVoice = index;
}
void WindowsTextToSpeechManager::setRate(int rate) {
- if(_speechState == BROKEN || _speechState == NO_VOICE)
+ if (_speechState == BROKEN || _speechState == NO_VOICE)
return;
assert(rate >= -100 && rate <= 100);
_voice->SetRate(rate / 10);
@@ -209,14 +309,14 @@ void WindowsTextToSpeechManager::setRate(int rate) {
}
void WindowsTextToSpeechManager::setPitch(int pitch) {
- if(_speechState == BROKEN || _speechState == NO_VOICE)
+ if (_speechState == BROKEN || _speechState == NO_VOICE)
return;
assert(pitch >= -100 && pitch <= 100);
_ttsState->_pitch = pitch;
}
void WindowsTextToSpeechManager::setVolume(unsigned volume) {
- if(_speechState == BROKEN || _speechState == NO_VOICE)
+ if (_speechState == BROKEN || _speechState == NO_VOICE)
return;
assert(volume <= 100);
_voice->SetVolume(volume);
@@ -306,7 +406,7 @@ void WindowsTextToSpeechManager::createVoice(void *cpVoiceToken) {
int strToInt(Common::String str) {
str.toUppercase();
int result = 0;
- for(unsigned i = 0; i < str.size(); i++) {
+ for (unsigned i = 0; i < str.size(); i++) {
if (str[i] < '0' || (str[i] > '9' && str[i] < 'A') || str[i] > 'F')
break;
int num = (str[i] <= '9') ? str[i] - '0' : str[i] - 55;
@@ -342,7 +442,7 @@ void WindowsTextToSpeechManager::updateVoices() {
while (SUCCEEDED(hr) && ulCount--) {
hr = cpEnum->Next(1, &cpVoiceToken, NULL);
_voice->SetVoice(cpVoiceToken);
- if(SUCCEEDED(_voice->Speak(L"hi, this is test", SPF_PURGEBEFORESPEAK | SPF_ASYNC | SPF_IS_NOT_XML, 0)))
+ if (SUCCEEDED(_voice->Speak(L"hi, this is test", SPF_PURGEBEFORESPEAK | SPF_ASYNC | SPF_IS_NOT_XML, 0)))
createVoice(cpVoiceToken);
else
cpVoiceToken->Release();
@@ -355,7 +455,7 @@ void WindowsTextToSpeechManager::updateVoices() {
_voice->SetVolume(_ttsState->_volume);
cpEnum->Release();
- if(_ttsState->_availableVoices.size() == 0) {
+ if (_ttsState->_availableVoices.size() == 0) {
_speechState = NO_VOICE;
warning("No voice is available");
} else if (_speechState == NO_VOICE)
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index d896824..cbf3eb0 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -29,6 +29,8 @@
#include "common/text-to-speech.h"
#include "common/str.h"
+#include "common/list.h"
+
class WindowsTextToSpeechManager : public Common::TextToSpeechManager {
public:
@@ -40,6 +42,12 @@ public:
NO_VOICE
};
+ struct SpeechParameters {
+ Common::List<WCHAR *> *queue;
+ SpeechState *state;
+ HANDLE *mutex;
+ };
+
WindowsTextToSpeechManager();
virtual ~WindowsTextToSpeechManager();
@@ -72,8 +80,13 @@ private:
Common::String lcidToLocale(Common::String lcid);
SpeechState _speechState;
Common::String _lastSaid;
+ HANDLE _thread;
+ Common::List<WCHAR *> _speechQueue;
+ SpeechParameters _threadParams;
+ HANDLE _speechMutex;
};
+
#endif
#endif // BACKENDS_UPDATES_WINDOWS_H
Commit: c2c44582537264c7fe0fe55ba456e54b405d0678
https://github.com/scummvm/scummvm/commit/c2c44582537264c7fe0fe55ba456e54b405d0678
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
JANITORIAL: Remove some trailing whitespaces
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/windows/windows-text-to-speech.h
common/text-to-speech.cpp
common/text-to-speech.h
engines/testbed/speech.cpp
gui/themes/scummclassic/classic_layout.stx
gui/themes/scummclassic/classic_layout_lowres.stx
gui/themes/scummmodern/scummmodern_layout.stx
gui/themes/scummmodern/scummmodern_layout_lowres.stx
gui/themes/scummremastered/remastered_layout.stx
gui/widgets/popup.cpp
gui/widgets/tab.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 33f15da..33135d2 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -66,7 +66,7 @@ public:
virtual bool isSpeaking();
virtual bool isPaused();
virtual bool isReady();
-
+
virtual void setVoice(unsigned index);
virtual void setRate(int rate);
virtual void setPitch(int pitch);
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index cbf3eb0..cc94107 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -60,7 +60,7 @@ public:
virtual bool isSpeaking();
virtual bool isPaused();
virtual bool isReady();
-
+
virtual void setVoice(unsigned index);
virtual void setRate(int rate);
diff --git a/common/text-to-speech.cpp b/common/text-to-speech.cpp
index f9dc4c6..cdfc8ab 100644
--- a/common/text-to-speech.cpp
+++ b/common/text-to-speech.cpp
@@ -36,7 +36,7 @@ TTSVoice::TTSVoice()
*_refCount = 1;
}
-TTSVoice::TTSVoice(Gender gender, Age age, void *data, String description)
+TTSVoice::TTSVoice(Gender gender, Age age, void *data, String description)
: _gender(gender)
, _age(age)
, _data(data)
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 6867656..d4f6c68 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -144,7 +144,7 @@ public:
DROP
};
/**
- * The constructor sets the language to the translation manager language if
+ * The constructor sets the language to the translation manager language if
* USE_TRANSLATION is defined, or english when it isn't defined. It sets the rate,
* pitch and volume to their middle values.
*/
@@ -209,7 +209,7 @@ public:
* Returns true, if the TTS engine is ready to speak (isn't speaking and isn't paused)
*/
virtual bool isReady() { return false; }
-
+
/**
* Sets a voice to be used by the TTS.
*
@@ -284,7 +284,7 @@ public:
* have the needed gender.
*
* @param gender Gender, which indices should be returned
- *
+ *
* @return Array of indices into _availableVoices
*/
Array<int> getVoiceIndicesByGender (TTSVoice::Gender gender);
diff --git a/engines/testbed/speech.cpp b/engines/testbed/speech.cpp
index cf51f1a..895a926 100644
--- a/engines/testbed/speech.cpp
+++ b/engines/testbed/speech.cpp
@@ -311,7 +311,7 @@ TestExitStatus Speechtests::testStateStacking() {
ttsMan->pushState();
Common::Array<int> femaleVoices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
Common::Array<Common::TTSVoice> allVoices = ttsMan->getVoicesArray();
- if (femaleVoices.size() == 0)
+ if (femaleVoices.size() == 0)
ttsMan->setVoice(1 % allVoices.size());
else
ttsMan->setVoice(femaleVoices[0]);
diff --git a/gui/themes/scummclassic/classic_layout.stx b/gui/themes/scummclassic/classic_layout.stx
index 14f1bb7..84bd056 100644
--- a/gui/themes/scummclassic/classic_layout.stx
+++ b/gui/themes/scummclassic/classic_layout.stx
@@ -824,7 +824,7 @@
</dialog>
<dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
- <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
<widget name = 'Container'/>
</layout>
</dialog>
@@ -918,11 +918,11 @@
<dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
<layout type='vertical' padding='16,16,16,16' spacing='16'>
- <widget name='TTSCheckbox'
- type='Checkbox'
+ <widget name='TTSCheckbox'
+ type='Checkbox'
/>
- <widget name='TTSVoiceSelection'
- type='PopUp'
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
/>
</layout>
</dialog>
diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx
index f2bbb00..e1d90ff 100644
--- a/gui/themes/scummclassic/classic_layout_lowres.stx
+++ b/gui/themes/scummclassic/classic_layout_lowres.stx
@@ -919,11 +919,11 @@
</dialog>
<dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
<layout type='vertical' padding='16,16,16,16' spacing='16'>
- <widget name='TTSCheckbox'
- type='Checkbox'
+ <widget name='TTSCheckbox'
+ type='Checkbox'
/>
- <widget name='TTSVoiceSelection'
- type='PopUp'
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
/>
</layout>
</dialog>
diff --git a/gui/themes/scummmodern/scummmodern_layout.stx b/gui/themes/scummmodern/scummmodern_layout.stx
index 28a7265..a45dd3e 100644
--- a/gui/themes/scummmodern/scummmodern_layout.stx
+++ b/gui/themes/scummmodern/scummmodern_layout.stx
@@ -838,7 +838,7 @@
</dialog>
<dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
- <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
<widget name = 'Container'/>
</layout>
</dialog>
@@ -932,11 +932,11 @@
<dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
<layout type='vertical' padding='16,16,16,16' spacing='16'>
- <widget name='TTSCheckbox'
- type='Checkbox'
+ <widget name='TTSCheckbox'
+ type='Checkbox'
/>
- <widget name='TTSVoiceSelection'
- type='PopUp'
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
/>
</layout>
</dialog>
diff --git a/gui/themes/scummmodern/scummmodern_layout_lowres.stx b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
index 8574534..62c0ff2 100644
--- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx
+++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
@@ -918,11 +918,11 @@
<dialog name='GlobalOptions_Accessibility' overlays='Dialog.GlobalOptions.TabWidget'>
<layout type='vertical' padding='16,16,16,16' spacing='16'>
- <widget name='TTSCheckbox'
- type='Checkbox'
+ <widget name='TTSCheckbox'
+ type='Checkbox'
/>
- <widget name='TTSVoiceSelection'
- type='PopUp'
+ <widget name='TTSVoiceSelection'
+ type='PopUp'
/>
</layout>
</dialog>
diff --git a/gui/themes/scummremastered/remastered_layout.stx b/gui/themes/scummremastered/remastered_layout.stx
index 7543304..092bbc0 100644
--- a/gui/themes/scummremastered/remastered_layout.stx
+++ b/gui/themes/scummremastered/remastered_layout.stx
@@ -838,7 +838,7 @@
</dialog>
<dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
- <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
<widget name = 'Container'/>
</layout>
</dialog>
diff --git a/gui/widgets/popup.cpp b/gui/widgets/popup.cpp
index 2fce6e1..970e35a 100644
--- a/gui/widgets/popup.cpp
+++ b/gui/widgets/popup.cpp
@@ -55,7 +55,7 @@ public:
void handleMouseUp(int x, int y, int button, int clickCount) override;
void handleMouseWheel(int x, int y, int direction) override; // Scroll through entries with scroll wheel
void handleMouseMoved(int x, int y, int button) override; // Redraw selections depending on mouse position
- void handleMouseLeft(int button) override;
+ void handleMouseLeft(int button) override;
void handleKeyDown(Common::KeyState state) override; // Scroll through entries with arrow keys etc.
protected:
diff --git a/gui/widgets/tab.cpp b/gui/widgets/tab.cpp
index 103f572..8c136ba 100644
--- a/gui/widgets/tab.cpp
+++ b/gui/widgets/tab.cpp
@@ -237,7 +237,7 @@ void TabWidget::handleMouseMoved(int x, int y, int button) {
_lastRead = tabID;
}
}
- else
+ else
_lastRead = -1;
}
Commit: c861581adc361c6f8a11e7634c2faa722fc6ef73
https://github.com/scummvm/scummvm/commit/c861581adc361c6f8a11e7634c2faa722fc6ef73
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Fix missing indentation
Changed paths:
backends/text-to-speech/windows/windows-text-to-speech.cpp
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index ec3961d..c4de02d 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -229,7 +229,7 @@ bool WindowsTextToSpeechManager::stop() {
WaitForSingleObject(_speechMutex, INFINITE);
while (!_speechQueue.empty()) {
if (_speechQueue.front() != NULL)
- free(_speechQueue.front());
+ free(_speechQueue.front());
_speechQueue.pop_front();
}
_speechQueue.push_back(NULL);
Commit: 54c37f6f46531d4b7995bcda9677c7646f6622a3
https://github.com/scummvm/scummvm/commit/54c37f6f46531d4b7995bcda9677c7646f6622a3
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Document diferences in resume()
On linux the resume() behaves slightly differently than on
other platforms.
Changed paths:
common/text-to-speech.h
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index d4f6c68..019db23 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -192,6 +192,10 @@ public:
/**
* Resumes the speech
+ *
+ * @note On linux, the speech resumes from the begining of the last speech being
+ * said, when pause() was called. On other platforms the speech resumes from
+ * exactly where it was paused();
*/
virtual bool resume() { return false; }
Commit: 07acdb84336380b6de1cf72a27116b3b60ee3599
https://github.com/scummvm/scummvm/commit/07acdb84336380b6de1cf72a27116b3b60ee3599
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBED: Don't build TTS tests if TTS is disabled
Changed paths:
engines/testbed/module.mk
diff --git a/engines/testbed/module.mk b/engines/testbed/module.mk
index 0dcbfb7..afbba5c 100644
--- a/engines/testbed/module.mk
+++ b/engines/testbed/module.mk
@@ -12,7 +12,6 @@ MODULE_OBJS := \
savegame.o \
sound.o \
encoding.o \
- speech.o \
testbed.o \
testsuite.o
@@ -28,6 +27,11 @@ MODULE_OBJS += \
webserver.o
endif
+ifdef USE_TTS
+MODULE_OBJS += \
+ speech.o
+endif
+
MODULE_DIRS += \
engines/testbed
Commit: c402666635b17e6a4b4603f7cfa48d6755e2bfef
https://github.com/scummvm/scummvm/commit/c402666635b17e6a4b4603f7cfa48d6755e2bfef
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Refactoring
Refactoring as suggested by bluegr on github.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.cpp
gui/options.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index bad45e8..eb864eb 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -76,7 +76,7 @@ void *LinuxTextToSpeechManager::startSpeech(void *p) {
pthread_mutex_unlock(params->mutex);
return NULL;
}
- if(spd_say(_connection, SPD_MESSAGE, params->speechQueue->front().c_str()) == -1) {
+ if (spd_say(_connection, SPD_MESSAGE, params->speechQueue->front().c_str()) == -1) {
// close the connection
if (_connection != 0) {
spd_close(_connection);
@@ -141,7 +141,7 @@ void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechEvent
case SPEECH_ENDED:
pthread_mutex_lock(&_speechMutex);
_speechQueue.pop_front();
- if (_speechQueue.size() == 0)
+ if (_speechQueue.empty())
_speechState = READY;
else {
// reinitialize if needed
@@ -286,7 +286,7 @@ bool LinuxTextToSpeechManager::resume() {
_threadCreated = false;
}
_speechState = PAUSED;
- if (_speechQueue.size()) {
+ if (!_speechQueue.empty()) {
_speechState = SPEAKING;
startSpeech((void *) &_params);
}
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index c4de02d..3ef4b55 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -80,7 +80,7 @@ void WindowsTextToSpeechManager::init() {
// init voice
hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&_voice);
- if (!SUCCEEDED(hr)) {
+ if (FAILED(hr)) {
warning("Could not initialize TTS voice");
return;
}
@@ -93,7 +93,7 @@ void WindowsTextToSpeechManager::init() {
_voice->SetOutput(_audio, FALSE);
- if (_ttsState->_availableVoices.size() > 0)
+ if (!_ttsState->_availableVoices.empty())
_speechState = READY;
else
_speechState = NO_VOICE;
@@ -156,7 +156,7 @@ DWORD WINAPI startSpeech(LPVOID parameters) {
bool WindowsTextToSpeechManager::say(Common::String str, Action action, Common::String charset) {
if (_speechState == BROKEN || _speechState == NO_VOICE) {
- warning("The tts cannot speak in this state");
+ warning("The text to speech cannot speak in this state");
return true;
}
@@ -455,7 +455,7 @@ void WindowsTextToSpeechManager::updateVoices() {
_voice->SetVolume(_ttsState->_volume);
cpEnum->Release();
- if (_ttsState->_availableVoices.size() == 0) {
+ if (_ttsState->_availableVoices.empty()) {
_speechState = NO_VOICE;
warning("No voice is available");
} else if (_speechState == NO_VOICE)
diff --git a/gui/options.cpp b/gui/options.cpp
index 68053dd..9e77aaa 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -1807,7 +1807,7 @@ void GlobalOptionsDialog::build() {
for(unsigned i = 0; i < voices.size(); i++) {
_ttsVoiceSelectionPopUp->appendEntry(voices[i].getDescription(), i);
}
- if (voices.size() == 0)
+ if (voices.empty())
_ttsVoiceSelectionPopUp->appendEntry("None", 0);
if (ConfMan.hasKey("tts_voice") && (unsigned) ConfMan.getInt("tts_voice", _domain) < voices.size())
Commit: d49331132abf01b6a22c9dd051a042914206fd6e
https://github.com/scummvm/scummvm/commit/d49331132abf01b6a22c9dd051a042914206fd6e
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Add summary of changes in sphelper-scummvm.h
Changed paths:
backends/text-to-speech/windows/sphelper-scummvm.h
diff --git a/backends/text-to-speech/windows/sphelper-scummvm.h b/backends/text-to-speech/windows/sphelper-scummvm.h
index d622fda..9ced239 100644
--- a/backends/text-to-speech/windows/sphelper-scummvm.h
+++ b/backends/text-to-speech/windows/sphelper-scummvm.h
@@ -1,3 +1,30 @@
+// To get sphelper.h working with MinGW, several changes had to be made
+//
+// SUMMARY OF CHANGES:
+// 1. Unneeded functions got deleted
+// SpCreateObjectFromToken
+// SpCreateObjectFromTokenId
+// SpCreateDefaultObjectFromCategoryId
+// SpCreateBestObject
+// SpCreatePhoneConverter
+// SpBindToFile
+// CreatePhraseFromWordArray
+// CreatePhraseFromText
+//
+// 2. Unneeded includes got deleted
+// crtdbg.h
+// SPDebug.h
+// atlbase.h
+//
+// 3. Include got added
+// cwtype
+//
+// 4. Calls to SPDBG_ASSERT() got deleted
+// 5. CComPtr<> were replaced by plain C style pointers and code that works with
+// these was adjusted accordingly
+//
+// More small changes were made throughout the whole file. The best way to
+// see all changes is probably to diff this and the original file.
/*******************************************************************************
* SPHelper.h *
*------------*
@@ -754,140 +781,6 @@ inline HRESULT SpFindBestToken(
return hr;
}
-/*template<class T>
-HRESULT SpCreateObjectFromToken(ISpObjectToken * pToken, T ** ppObject,
- IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL)
-{
- HRESULT hr;
-
- hr = pToken->CreateInstance(pUnkOuter, dwClsCtxt, __uuidof(T), (void **)ppObject);
-
- return hr;
-}
-
-template<class T>
-HRESULT SpCreateObjectFromTokenId(const WCHAR * pszTokenId, T ** ppObject,
- IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL)
-{
-
- ISpObjectToken * pToken;
- HRESULT hr = SpGetTokenFromId(pszTokenId, &pToken);
- if (SUCCEEDED(hr))
- {
- hr = SpCreateObjectFromToken(pToken, ppObject, pUnkOuter, dwClsCtxt);
- pToken->Release();
- }
-
- return hr;
-}*/
-/*
-template<class T>
-HRESULT SpCreateDefaultObjectFromCategoryId(const WCHAR * pszCategoryId, T ** ppObject,
- IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL)
-{
-
- ISpObjectToken * pToken;
- HRESULT hr = SpGetDefaultTokenFromCategoryId(pszCategoryId, &pToken);
- if (SUCCEEDED(hr))
- {
- hr = SpCreateObjectFromToken(pToken, ppObject, pUnkOuter, dwClsCtxt);
- pToken->Release();
- }
-
- return hr;
-}
-
-template<class T>
-HRESULT SpCreateBestObject(
- const WCHAR * pszCategoryId,
- const WCHAR * pszReqAttribs,
- const WCHAR * pszOptAttribs,
- T ** ppObject,
- IUnknown * pUnkOuter = NULL,
- DWORD dwClsCtxt = CLSCTX_ALL)
-{
- HRESULT hr;
-
- ISpObjectToken *cpToken;
- hr = SpFindBestToken(pszCategoryId, pszReqAttribs, pszOptAttribs, &cpToken);
-
- if (SUCCEEDED(hr))
- {
- hr = SpCreateObjectFromToken(cpToken, ppObject, pUnkOuter, dwClsCtxt);
- }
-
- if (hr != SPERR_NOT_FOUND)
- {
- }
-
- return hr;
-}*/
-
-/*HRESULT SpCreateBestObject(
- const WCHAR * pszCategoryId,
- const WCHAR * pszReqAttribs,
- const WCHAR * pszOptAttribs,
- ISpPhoneConverter ** ** ppObject,
- IUnknown * pUnkOuter = NULL,
- DWORD dwClsCtxt = CLSCTX_ALL)
-{
- HRESULT hr;
-
- ISpObjectToken *cpToken;
- hr = SpFindBestToken(pszCategoryId, pszReqAttribs, pszOptAttribs, &cpToken);
-
- if (SUCCEEDED(hr))
- {
- hr = SpCreateObjectFromToken(cpToken, ppObject, pUnkOuter, dwClsCtxt);
- }
-
- if (hr != SPERR_NOT_FOUND)
- {
- }
-
- return hr;
-}*/
-
-/*inline HRESULT SpCreatePhoneConverter(
- LANGID LangID,
- const WCHAR * pszReqAttribs,
- const WCHAR * pszOptAttribs,
- ISpPhoneConverter ** ppPhoneConverter)
-{
- HRESULT hr;
-
- if (LangID == 0)
- {
- hr = E_INVALIDARG;
- }
- else
- {
- CSpDynamicString dstrReqAttribs;
- if (pszReqAttribs)
- {
- dstrReqAttribs = pszReqAttribs;
- dstrReqAttribs.Append(L";");
- }
-
- WCHAR szLang[MAX_PATH];
-
- SpHexFromUlong(szLang, LangID);
-
- WCHAR szLangCondition[MAX_PATH];
- wcscpy(szLangCondition, L"Language=");
- wcscat(szLangCondition, szLang);
-
- dstrReqAttribs.Append(szLangCondition);
-
- hr = SpCreateBestObject(SPCAT_PHONECONVERTERS, dstrReqAttribs, pszOptAttribs, ppPhoneConverter);
- }
-
- if (hr != SPERR_NOT_FOUND)
- {
- }
-
- return hr;
-}*/
/****************************************************************************
* SpHrFromWin32 *
@@ -2402,247 +2295,5 @@ public:
}
};
-/**** Helper function used to create a new phrase object from an array of
- test words. Each word in the string is converted to a phrase element.
- This is useful to create a phrase to pass to the EmulateRecognition method.
- The method can convert standard words as well as words with the
- "/display_text/lexical_form/pronounciation;" word format.
- You can also specify the DisplayAttributes for each element if desired.
- If prgDispAttribs is NULL then the DisplayAttribs for each element default to
- SPAF_ONE_TRAILING_SPACE. ****/
-/*inline HRESULT CreatePhraseFromWordArray(const WCHAR ** ppWords, ULONG cWords,
- SPDISPLYATTRIBUTES * prgDispAttribs,
- ISpPhraseBuilder **ppResultPhrase,
- LANGID LangId = 0,
- ISpPhoneConverter *cpPhoneConv = NULL)
-{
- HRESULT hr = S_OK;
-
- if ( cWords == 0 || ppWords == NULL || ::IsBadReadPtr(ppWords, sizeof(*ppWords) * cWords ) )
- {
- return E_INVALIDARG;
- }
-
- if ( prgDispAttribs != NULL && ::IsBadReadPtr(prgDispAttribs, sizeof(*prgDispAttribs) * cWords ) )
- {
- return E_INVALIDARG;
- }
-
- ULONG cTotalChars = 0;
- ULONG i;
- WCHAR** pStringPtrArray = (WCHAR**)::CoTaskMemAlloc( cWords * sizeof(WCHAR *));
- if ( !pStringPtrArray )
- {
- return E_OUTOFMEMORY;
- }
- for (i = 0; i < cWords; i++)
- {
- cTotalChars += wcslen(ppWords[i])+1;
- }
-
- CSpDynamicString dsText(cTotalChars);
- if(dsText.m_psz == NULL)
- {
- ::CoTaskMemFree(pStringPtrArray);
- return E_OUTOFMEMORY;
- }
- CSpDynamicString dsPhoneId(cTotalChars);
- if(dsPhoneId.m_psz == NULL)
- {
- ::CoTaskMemFree(pStringPtrArray);
- return E_OUTOFMEMORY;
- }
- SPPHONEID* pphoneId = dsPhoneId;
-
- SPPHRASE Phrase;
- memset(&Phrase, 0, sizeof(Phrase));
- Phrase.cbSize = sizeof(Phrase);
-
- if(LangId == 0)
- {
- LangId = SpGetUserDefaultUILanguage();
- }
-
- if(cpPhoneConv == NULL)
- {
- hr = SpCreatePhoneConverter(LangId, NULL, NULL, &cpPhoneConv);
- if(FAILED(hr))
- {
- ::CoTaskMemFree(pStringPtrArray);
- return hr;
- }
- }
-
- SPPHRASEELEMENT *pPhraseElement = new SPPHRASEELEMENT[cWords];
- if(pPhraseElement == NULL)
- {
- ::CoTaskMemFree(pStringPtrArray);
- return E_OUTOFMEMORY;
- }
- memset(pPhraseElement, 0, sizeof(SPPHRASEELEMENT) * cWords); // !!!
-
- WCHAR * pText = dsText;
- for (i = 0; SUCCEEDED(hr) && i < cWords; i++)
- {
- WCHAR *p = pText;
- pStringPtrArray[i] = pText;
- wcscpy( pText, ppWords[i] );
- pText += wcslen( p ) + 1;
-
- if (*p == L'/')
- {
- //This is a compound word
- WCHAR* pszFirstPart = ++p;
- WCHAR* pszSecondPart = NULL;
- WCHAR* pszThirdPart = NULL;
-
- while (*p && *p != L'/')
- {
- p++;
- }
- if (*p == L'/')
- {
- //It means we stop at the second '/'
- *p = L'\0';
- pszSecondPart = ++p;
- while (*p && *p != L'/')
- {
- p++;
- }
- if (*p == L'/')
- {
- //It means we stop at the third '/'
- *p = L'\0';
- pszThirdPart = ++p;
- }
- }
-
- pPhraseElement[i].pszDisplayText = pszFirstPart;
- pPhraseElement[i].pszLexicalForm = pszSecondPart ? pszSecondPart : pszFirstPart;
-
- if ( pszThirdPart)
- {
- hr = cpPhoneConv->PhoneToId(pszThirdPart, pphoneId);
- if (SUCCEEDED(hr))
- {
- pPhraseElement[i].pszPronunciation = pphoneId;
- pphoneId += wcslen(pphoneId) + 1;
- }
- }
- }
- else
- {
- //It is the simple format, only have one form, use it for everything.
- pPhraseElement[i].pszDisplayText = NULL;
- pPhraseElement[i].pszLexicalForm = p;
- pPhraseElement[i].pszPronunciation = NULL;
- }
-
- pPhraseElement[i].bDisplayAttributes = (BYTE)(prgDispAttribs ? prgDispAttribs[i] : SPAF_ONE_TRAILING_SPACE);
- pPhraseElement[i].RequiredConfidence = SP_NORMAL_CONFIDENCE;
- pPhraseElement[i].ActualConfidence = SP_NORMAL_CONFIDENCE;
- }
-
- Phrase.Rule.ulCountOfElements = cWords;
- Phrase.pElements = pPhraseElement;
- Phrase.LangID = LangId;
-
- ISpPhraseBuilder *cpPhrase;
- if (SUCCEEDED(hr))
- {
- hr = cpPhrase.CoCreateInstance(CLSID_SpPhraseBuilder);
- }
-
- if (SUCCEEDED(hr))
- {
- hr = cpPhrase->InitFromPhrase(&Phrase);
- }
- if (SUCCEEDED(hr))
- {
- *ppResultPhrase = cpPhrase.Detach();
- }
-
- delete pPhraseElement;
- ::CoTaskMemFree(pStringPtrArray);
-
- return hr;
-}*/
-
-/**** Helper function used to create a new phrase object from a
- test string. Each word in the string is converted to a phrase element.
- This is useful to create a phrase to pass to the EmulateRecognition method.
- The method can convert standard words as well as words with the
- "/display_text/lexical_form/pronounciation;" word format ****/
-/*inline HRESULT CreatePhraseFromText(const WCHAR *pszOriginalText,
- ISpPhraseBuilder **ppResultPhrase,
- LANGID LangId = 0,
- ISpPhoneConverter *cpPhoneConv = NULL)
-{
- HRESULT hr = S_OK;
-
- //We first trim the input text
- CSpDynamicString dsText(pszOriginalText);
- if(dsText.m_psz == NULL)
- {
- return E_OUTOFMEMORY;
- }
- dsText.TrimBoth();
-
- ULONG cWords = 0;
- BOOL fInCompoundword = FALSE;
-
- // Set first array pointer (if *p).
- WCHAR *p = dsText;
- while (*p)
- {
- if( iswspace(*p) && !fInCompoundword)
- {
- cWords++;
- *p++ = L'\0';
- while (*p && iswspace(*p))
- {
- *p++ = L'\0';
- }
- // Add new array pointer. Use vector.
- }
- else if (*p == L'/' && !fInCompoundword)
- {
- fInCompoundword = TRUE;
- }
- else if (*p == L';' && fInCompoundword)
- {
- fInCompoundword = FALSE;
- *p++ = L'\0';
- // Add new array element.
- }
- else
- {
- p++;
- }
- }
-
- cWords++;
-
- WCHAR** pStringPtrArray = (WCHAR**)::CoTaskMemAlloc( cWords * sizeof(WCHAR *));
- if ( !pStringPtrArray )
- {
- hr = E_OUTOFMEMORY;
- }
-
- if ( SUCCEEDED( hr ) )
- {
- p = dsText;
- for (ULONG i=0; i<cWords; i++)
- {
- pStringPtrArray[i] = p;
- p += wcslen(p)+1;
- }
-
- hr = CreatePhraseFromWordArray((const WCHAR **)pStringPtrArray, cWords, NULL, ppResultPhrase, LangId, cpPhoneConv);
-
- ::CoTaskMemFree(pStringPtrArray);
- }
- return hr;
-}*/
#endif /* This must be the last line in the file */
Commit: b97333d4b715013f37700c200fb1619da9225de8
https://github.com/scummvm/scummvm/commit/b97333d4b715013f37700c200fb1619da9225de8
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Remove USE_PLATFORM_TTS defines
Use defined(USE_TTS) && defined(PLATFORM) instead
Changed paths:
backends/module.mk
backends/platform/sdl/macosx/macosx.cpp
backends/platform/sdl/posix/posix.cpp
backends/platform/sdl/win32/win32.cpp
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
backends/text-to-speech/macosx/macosx-text-to-speech.h
backends/text-to-speech/macosx/macosx-text-to-speech.mm
backends/text-to-speech/windows/windows-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.h
configure
devtools/create_project/create_project.cpp
diff --git a/backends/module.mk b/backends/module.mk
index 11185fc..3fe829b 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -166,6 +166,12 @@ MODULE_OBJS += \
plugins/posix/posix-provider.o \
saves/posix/posix-saves.o \
taskbar/unity/unity-taskbar.o
+
+ifdef USE_TTS
+MODULE_OBJS += \
+ text-to-speech/linux/linux-text-to-speech.o
+endif
+
endif
ifdef MACOSX
@@ -176,6 +182,12 @@ MODULE_OBJS += \
midi/coremidi.o \
updates/macosx/macosx-updates.o \
taskbar/macosx/macosx-taskbar.o
+
+ifdef USE_TTS
+MODULE_OBJS += \
+ text-to-speech/macosx/macosx-text-to-speech.o
+endif
+
endif
ifdef WIN32
@@ -189,6 +201,12 @@ MODULE_OBJS += \
saves/windows/windows-saves.o \
updates/win32/win32-updates.o \
taskbar/win32/win32-taskbar.o
+
+ifdef USE_TTS
+MODULE_OBJS += \
+ text-to-speech/windows/windows-text-to-speech.o
+endif
+
endif
ifeq ($(BACKEND),android)
@@ -344,18 +362,5 @@ MODULE_OBJS += \
saves/recorder/recorder-saves.o
endif
-ifdef USE_LINUX_TTS
-MODULE_OBJS += \
- text-to-speech/linux/linux-text-to-speech.o
-endif
-ifdef USE_WINDOWS_TTS
-MODULE_OBJS += \
- text-to-speech/windows/windows-text-to-speech.o
-endif
-ifdef USE_MACOSX_TTS
-MODULE_OBJS += \
- text-to-speech/macosx/macosx-text-to-speech.o
-endif
-
# Include common rules
include $(srcdir)/rules.mk
diff --git a/backends/platform/sdl/macosx/macosx.cpp b/backends/platform/sdl/macosx/macosx.cpp
index 3cca69b..1c47401 100644
--- a/backends/platform/sdl/macosx/macosx.cpp
+++ b/backends/platform/sdl/macosx/macosx.cpp
@@ -87,7 +87,7 @@ void OSystem_MacOSX::initBackend() {
_updateManager = new MacOSXUpdateManager();
#endif
-#ifdef USE_MACOSX_TTS
+#ifdef USE_TTS
// Initialize Text to Speech manager
_textToSpeechManager = new MacOSXTextToSpeechManager();
#endif
diff --git a/backends/platform/sdl/posix/posix.cpp b/backends/platform/sdl/posix/posix.cpp
index 50a1ccd..0b6c4d6 100644
--- a/backends/platform/sdl/posix/posix.cpp
+++ b/backends/platform/sdl/posix/posix.cpp
@@ -55,7 +55,7 @@
#include <spawn.h>
#endif
-#ifdef USE_LINUX_TTS
+#ifdef USE_TTS
#include "backends/text-to-speech/linux/linux-text-to-speech.h"
#endif
extern char **environ;
@@ -83,7 +83,7 @@ void OSystem_POSIX::initBackend() {
if (_savefileManager == 0)
_savefileManager = new POSIXSaveFileManager();
-#ifdef USE_LINUX_TTS
+#ifdef USE_TTS
// Initialize Text to Speech manager
_textToSpeechManager = new LinuxTextToSpeechManager();
#endif
diff --git a/backends/platform/sdl/win32/win32.cpp b/backends/platform/sdl/win32/win32.cpp
index 4328773..5f35d46 100644
--- a/backends/platform/sdl/win32/win32.cpp
+++ b/backends/platform/sdl/win32/win32.cpp
@@ -54,7 +54,7 @@
#include "common/ustr.h"
#include "common/encoding.h"
-#ifdef USE_WINDOWS_TTS
+#if defined(USE_TTS)
#include "backends/text-to-speech/windows/windows-text-to-speech.h"
#endif
@@ -121,7 +121,7 @@ void OSystem_Win32::initBackend() {
#endif
// Initialize text to speech
-#ifdef USE_WINDOWS_TTS
+#ifdef USE_TTS
_textToSpeechManager = new WindowsTextToSpeechManager();
#endif
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index eb864eb..31efb39 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -25,7 +25,7 @@
#include "backends/text-to-speech/linux/linux-text-to-speech.h"
-#if defined(USE_LINUX_TTS)
+#if defined(USE_TTS) && defined(POSIX)
#include <speech-dispatcher/libspeechd.h>
#include "backends/platform/sdl/sdl-sys.h"
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index 33135d2..db3e64d 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -25,7 +25,7 @@
#include "common/scummsys.h"
-#if defined(USE_LINUX_TTS)
+#if defined(USE_TTS) && defined(POSIX)
#include "common/text-to-speech.h"
#include "common/str.h"
diff --git a/backends/text-to-speech/macosx/macosx-text-to-speech.h b/backends/text-to-speech/macosx/macosx-text-to-speech.h
index 9ed418d..40ea808 100644
--- a/backends/text-to-speech/macosx/macosx-text-to-speech.h
+++ b/backends/text-to-speech/macosx/macosx-text-to-speech.h
@@ -25,7 +25,7 @@
#include "common/scummsys.h"
-#if defined(USE_MACOSX_TTS)
+#if defined(USE_TTS) && defined(MACOSX)
#include "common/text-to-speech.h"
#include "common/queue.h"
diff --git a/backends/text-to-speech/macosx/macosx-text-to-speech.mm b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
index 1e8a10e..72f56f9 100644
--- a/backends/text-to-speech/macosx/macosx-text-to-speech.mm
+++ b/backends/text-to-speech/macosx/macosx-text-to-speech.mm
@@ -25,7 +25,7 @@
#include "backends/text-to-speech/macosx/macosx-text-to-speech.h"
-#if defined(USE_MACOSX_TTS)
+#if defined(USE_TTS) && defined(MACOSX)
#include "common/translation.h"
#include <AppKit/NSSpeechSynthesizer.h>
#include <Foundation/NSString.h>
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index 3ef4b55..b7d39f0 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -25,7 +25,7 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-#if defined(USE_WINDOWS_TTS)
+#if defined(USE_TTS) && defined(WIN32)
#include <basetyps.h>
#include <windows.h>
#include <Servprov.h>
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.h b/backends/text-to-speech/windows/windows-text-to-speech.h
index cc94107..3e7c29b 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.h
+++ b/backends/text-to-speech/windows/windows-text-to-speech.h
@@ -25,7 +25,7 @@
#include "common/scummsys.h"
-#if defined(USE_WINDOWS_TTS)
+#if defined(USE_TTS) && defined(WIN32)
#include "common/text-to-speech.h"
#include "common/str.h"
diff --git a/configure b/configure
index c0ef73b..8328626 100755
--- a/configure
+++ b/configure
@@ -167,9 +167,6 @@ _libunity=auto
_dialogs=auto
_iconv=auto
_tts=auto
-_linux_tts=no
-_windows_tts=no
-_macosx_tts=no
# Default option behavior yes/no
_debug_build=auto
_release_build=auto
@@ -5407,22 +5404,16 @@ else
linux*)
echo "linux"
_tts=yes
- _linux_tts=yes
- define_in_config_if_yes $_linux_tts 'USE_LINUX_TTS'
append_var LIBS '-lspeechd'
;;
mingw*)
echo "win32"
_tts=yes
- _windows_tts=yes
- define_in_config_if_yes $_windows_tts 'USE_WINDOWS_TTS'
append_var LIBS '-lsapi -lole32'
;;
darwin*)
echo "osx"
_tts=yes
- _macosx_tts=yes
- define_in_config_if_yes $_macosx_tts 'USE_MACOSX_TTS'
;;
*)
echo "no"
diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp
index 9599aa0..30f4164 100644
--- a/devtools/create_project/create_project.cpp
+++ b/devtools/create_project/create_project.cpp
@@ -428,7 +428,6 @@ int main(int argc, char *argv[]) {
if (ttsEnabled) {
setup.libraries.push_back("sapi");
- setup.defines.push_back("USE_WINDOWS_TTS");
}
setup.defines.push_back("SDL_BACKEND");
Commit: 0d332e065ec4a56a5964d8933f1d6118a85f6965
https://github.com/scummvm/scummvm/commit/0d332e065ec4a56a5964d8933f1d6118a85f6965
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Rename LinuxTextToSpeechManager to SpeechDispatcherManager
Add a new define for the SpeechDispatcherManager
Changed paths:
backends/module.mk
backends/platform/sdl/posix/posix.cpp
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/linux/linux-text-to-speech.h
configure
diff --git a/backends/module.mk b/backends/module.mk
index 3fe829b..491be80 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -167,10 +167,12 @@ MODULE_OBJS += \
saves/posix/posix-saves.o \
taskbar/unity/unity-taskbar.o
+ifdef USE_SPEECH_DISPATCHER
ifdef USE_TTS
MODULE_OBJS += \
text-to-speech/linux/linux-text-to-speech.o
endif
+endif
endif
diff --git a/backends/platform/sdl/posix/posix.cpp b/backends/platform/sdl/posix/posix.cpp
index 0b6c4d6..b983849 100644
--- a/backends/platform/sdl/posix/posix.cpp
+++ b/backends/platform/sdl/posix/posix.cpp
@@ -55,7 +55,7 @@
#include <spawn.h>
#endif
-#ifdef USE_TTS
+#if defined(USE_SPEECH_DISPATCHER) && defined(USE_TTS)
#include "backends/text-to-speech/linux/linux-text-to-speech.h"
#endif
extern char **environ;
@@ -83,9 +83,9 @@ void OSystem_POSIX::initBackend() {
if (_savefileManager == 0)
_savefileManager = new POSIXSaveFileManager();
-#ifdef USE_TTS
+#if defined(USE_SPEECH_DISPATCHER) && defined(USE_TTS)
// Initialize Text to Speech manager
- _textToSpeechManager = new LinuxTextToSpeechManager();
+ _textToSpeechManager = new SpeechDispatcherManager();
#endif
// Invoke parent implementation of this method
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index 31efb39..e484373 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -25,7 +25,7 @@
#include "backends/text-to-speech/linux/linux-text-to-speech.h"
-#if defined(USE_TTS) && defined(POSIX)
+#if defined(USE_TTS) && defined(USE_SPEECH_DISPATCHER) && defined(POSIX)
#include <speech-dispatcher/libspeechd.h>
#include "backends/platform/sdl/sdl-sys.h"
@@ -38,37 +38,37 @@
SPDConnection *_connection;
void speech_begin_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
- LinuxTextToSpeechManager *manager =
- static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::SPEECH_BEGUN);
+ SpeechDispatcherManager *manager =
+ static_cast<SpeechDispatcherManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(SpeechDispatcherManager::SPEECH_BEGUN);
}
void speech_end_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
- LinuxTextToSpeechManager *manager =
- static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::SPEECH_ENDED);
+ SpeechDispatcherManager *manager =
+ static_cast<SpeechDispatcherManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(SpeechDispatcherManager::SPEECH_ENDED);
}
void speech_cancel_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
- LinuxTextToSpeechManager *manager =
- static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::SPEECH_CANCELED);
+ SpeechDispatcherManager *manager =
+ static_cast<SpeechDispatcherManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(SpeechDispatcherManager::SPEECH_CANCELED);
}
void speech_resume_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
- LinuxTextToSpeechManager *manager =
- static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::SPEECH_RESUMED);
+ SpeechDispatcherManager *manager =
+ static_cast<SpeechDispatcherManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(SpeechDispatcherManager::SPEECH_RESUMED);
}
void speech_pause_callback(size_t msg_id, size_t client_id, SPDNotificationType state){
- LinuxTextToSpeechManager *manager =
- static_cast<LinuxTextToSpeechManager *> (g_system->getTextToSpeechManager());
- manager->updateState(LinuxTextToSpeechManager::SPEECH_PAUSED);
+ SpeechDispatcherManager *manager =
+ static_cast<SpeechDispatcherManager *> (g_system->getTextToSpeechManager());
+ manager->updateState(SpeechDispatcherManager::SPEECH_PAUSED);
}
-void *LinuxTextToSpeechManager::startSpeech(void *p) {
+void *SpeechDispatcherManager::startSpeech(void *p) {
StartSpeechParams *params = (StartSpeechParams *) p;
pthread_mutex_lock(params->mutex);
if (!_connection || g_system->getTextToSpeechManager()->isPaused() ||
@@ -87,7 +87,7 @@ void *LinuxTextToSpeechManager::startSpeech(void *p) {
return NULL;
}
-LinuxTextToSpeechManager::LinuxTextToSpeechManager()
+SpeechDispatcherManager::SpeechDispatcherManager()
: _speechState(READY) {
pthread_mutex_init(&_speechMutex, NULL);
_params.mutex = &_speechMutex;
@@ -96,7 +96,7 @@ LinuxTextToSpeechManager::LinuxTextToSpeechManager()
init();
}
-void LinuxTextToSpeechManager::init() {
+void SpeechDispatcherManager::init() {
_connection = spd_open("ScummVM", "main", NULL, SPD_MODE_THREADED);
if (_connection == 0) {
_speechState = BROKEN;
@@ -125,7 +125,7 @@ void LinuxTextToSpeechManager::init() {
_speechQueue.clear();
}
-LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
+SpeechDispatcherManager::~SpeechDispatcherManager() {
stop();
if (_connection != 0)
spd_close(_connection);
@@ -134,7 +134,7 @@ LinuxTextToSpeechManager::~LinuxTextToSpeechManager() {
pthread_mutex_destroy(&_speechMutex);
}
-void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechEvent event) {
+void SpeechDispatcherManager::updateState(SpeechDispatcherManager::SpeechEvent event) {
if (_speechState == BROKEN)
return;
switch(event) {
@@ -175,7 +175,7 @@ void LinuxTextToSpeechManager::updateState(LinuxTextToSpeechManager::SpeechEvent
}
}
-Common::String LinuxTextToSpeechManager::strToUtf8(Common::String str, Common::String charset) {
+Common::String SpeechDispatcherManager::strToUtf8(Common::String str, Common::String charset) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
char *conv_text = SDL_iconv_string("UTF-8", charset.c_str(), str.c_str(), str.size() + 1);
@@ -195,7 +195,7 @@ Common::String LinuxTextToSpeechManager::strToUtf8(Common::String str, Common::S
#endif
}
-bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::String charset) {
+bool SpeechDispatcherManager::say(Common::String str, Action action, Common::String charset) {
pthread_mutex_lock(&_speechMutex);
// reinitialize if needed
@@ -252,7 +252,7 @@ bool LinuxTextToSpeechManager::say(Common::String str, Action action, Common::St
return false;
}
-bool LinuxTextToSpeechManager::stop() {
+bool SpeechDispatcherManager::stop() {
if (_speechState == READY || _speechState == BROKEN)
return true;
_speechState = READY;
@@ -263,7 +263,7 @@ bool LinuxTextToSpeechManager::stop() {
return result;
}
-bool LinuxTextToSpeechManager::pause() {
+bool SpeechDispatcherManager::pause() {
if (_speechState == READY || _speechState == PAUSED || _speechState == BROKEN)
return true;
pthread_mutex_lock(&_speechMutex);
@@ -275,7 +275,7 @@ bool LinuxTextToSpeechManager::pause() {
return false;
}
-bool LinuxTextToSpeechManager::resume() {
+bool SpeechDispatcherManager::resume() {
if (_speechState == READY || _speechState == SPEAKING || _speechState == BROKEN)
return true;
// If there is a thread from before pause() waiting, let it finish (it shouln't
@@ -295,19 +295,19 @@ bool LinuxTextToSpeechManager::resume() {
return false;
}
-bool LinuxTextToSpeechManager::isSpeaking() {
+bool SpeechDispatcherManager::isSpeaking() {
return _speechState == SPEAKING;
}
-bool LinuxTextToSpeechManager::isPaused() {
+bool SpeechDispatcherManager::isPaused() {
return _speechState == PAUSED;
}
-bool LinuxTextToSpeechManager::isReady() {
+bool SpeechDispatcherManager::isReady() {
return _speechState == READY;
}
-void LinuxTextToSpeechManager::setVoice(unsigned index) {
+void SpeechDispatcherManager::setVoice(unsigned index) {
if (_speechState == BROKEN)
return;
assert(index < _ttsState->_availableVoices.size());
@@ -316,7 +316,7 @@ void LinuxTextToSpeechManager::setVoice(unsigned index) {
_ttsState->_activeVoice = index;
}
-void LinuxTextToSpeechManager::setRate(int rate) {
+void SpeechDispatcherManager::setRate(int rate) {
if (_speechState == BROKEN)
return;
assert(rate >= -100 && rate <= 100);
@@ -324,7 +324,7 @@ void LinuxTextToSpeechManager::setRate(int rate) {
_ttsState->_rate = rate;
}
-void LinuxTextToSpeechManager::setPitch(int pitch) {
+void SpeechDispatcherManager::setPitch(int pitch) {
if (_speechState == BROKEN)
return;
assert(pitch >= -100 && pitch <= 100);
@@ -332,7 +332,7 @@ void LinuxTextToSpeechManager::setPitch(int pitch) {
_ttsState->_pitch = pitch;
}
-void LinuxTextToSpeechManager::setVolume(unsigned volume) {
+void SpeechDispatcherManager::setVolume(unsigned volume) {
if (_speechState == BROKEN)
return;
assert(volume <= 100);
@@ -340,7 +340,7 @@ void LinuxTextToSpeechManager::setVolume(unsigned volume) {
_ttsState->_volume = volume;
}
-void LinuxTextToSpeechManager::setLanguage(Common::String language) {
+void SpeechDispatcherManager::setLanguage(Common::String language) {
if (_speechState == BROKEN)
return;
spd_set_language(_connection, language.c_str());
@@ -348,14 +348,14 @@ void LinuxTextToSpeechManager::setLanguage(Common::String language) {
setVoice(_ttsState->_activeVoice);
}
-void LinuxTextToSpeechManager::createVoice(int typeNumber, Common::TTSVoice::Gender gender, Common::TTSVoice::Age age, char *description) {
+void SpeechDispatcherManager::createVoice(int typeNumber, Common::TTSVoice::Gender gender, Common::TTSVoice::Age age, char *description) {
SPDVoiceType *type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
*type = static_cast<SPDVoiceType>(typeNumber);
Common::TTSVoice voice(gender, age, (void *) type, description);
_ttsState->_availableVoices.push_back(voice);
}
-void LinuxTextToSpeechManager::updateVoices() {
+void SpeechDispatcherManager::updateVoices() {
if (_speechState == BROKEN)
return;
/* just use these voices:
@@ -385,7 +385,7 @@ void LinuxTextToSpeechManager::updateVoices() {
free(voiceInfo);
}
-void LinuxTextToSpeechManager::freeVoiceData(void *data) {
+void SpeechDispatcherManager::freeVoiceData(void *data) {
free(data);
}
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.h b/backends/text-to-speech/linux/linux-text-to-speech.h
index db3e64d..4c02b99 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.h
+++ b/backends/text-to-speech/linux/linux-text-to-speech.h
@@ -25,7 +25,7 @@
#include "common/scummsys.h"
-#if defined(USE_TTS) && defined(POSIX)
+#if defined(USE_TTS) && defined(USE_SPEECH_DISPATCHER) && defined(POSIX)
#include "common/text-to-speech.h"
#include "common/str.h"
@@ -37,7 +37,7 @@ struct StartSpeechParams {
Common::List<Common::String> *speechQueue;
};
-class LinuxTextToSpeechManager : public Common::TextToSpeechManager {
+class SpeechDispatcherManager : public Common::TextToSpeechManager {
public:
enum SpeechState {
READY,
@@ -54,8 +54,8 @@ public:
SPEECH_BEGUN
};
- LinuxTextToSpeechManager();
- virtual ~LinuxTextToSpeechManager();
+ SpeechDispatcherManager();
+ virtual ~SpeechDispatcherManager();
virtual bool say(Common::String str, Action action, Common::String charset = "");
diff --git a/configure b/configure
index 8328626..fe56b92 100755
--- a/configure
+++ b/configure
@@ -5404,6 +5404,7 @@ else
linux*)
echo "linux"
_tts=yes
+ define_in_config_if_yes $_tts 'USE_SPEECH_DISPATCHER'
append_var LIBS '-lspeechd'
;;
mingw*)
Commit: 4b5b812712373a094e65506d30aa2ae611425e75
https://github.com/scummvm/scummvm/commit/4b5b812712373a094e65506d30aa2ae611425e75
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Better documentation of TTSVoice.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
common/text-to-speech.h
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index e484373..d201083 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -349,6 +349,9 @@ void SpeechDispatcherManager::setLanguage(Common::String language) {
}
void SpeechDispatcherManager::createVoice(int typeNumber, Common::TTSVoice::Gender gender, Common::TTSVoice::Age age, char *description) {
+ // This pointer will point to data needed for voice switching. It is stored
+ // in the Common::TTSVoice and it is freed by freeVoiceData() once it
+ // is not needed.
SPDVoiceType *type = (SPDVoiceType *) malloc(sizeof(SPDVoiceType));
*type = static_cast<SPDVoiceType>(typeNumber);
Common::TTSVoice voice(gender, age, (void *) type, description);
diff --git a/common/text-to-speech.h b/common/text-to-speech.h
index 019db23..8e86721 100644
--- a/common/text-to-speech.h
+++ b/common/text-to-speech.h
@@ -95,17 +95,17 @@ class TTSVoice {
/**
* Returns the data about the voice, this is engine specific variable,
- * it has close to no value for anything else then communicating with
+ * it has close to no value for anything else then communicating
* directly with the TTS engine, which should probably be done only by
* the backends.
*/
- void setData(void *data) { _data = data; };
+ void *getData() { return _data; };
/**
- * Sets the voice age, should probably be used only by the backends
+ * Sets the voice data, should probably be used only by the backends
* that are directly communicating with the TTS engine.
*/
- void *getData() { return _data; };
+ void setData(void *data) { _data = data; };
/**
* Returns the voice description. This description is really tts engine
@@ -114,11 +114,11 @@ class TTSVoice {
String getDescription() { return _description; };
protected:
- Gender _gender;
- Age _age;
- void *_data;
- String _description;
- int *_refCount;
+ Gender _gender; ///< Gender of the voice
+ Age _age; ///< Age of the voice
+ void *_data; ///< Pointer to tts engine specific data about the voice
+ String _description; ///< Description of the voice (gets displayed in GUI)
+ int *_refCount; ///< Reference count (serves for proper feeing of _data)
};
struct TTSState {
Commit: d399c37e6e7581e0bcb888a242c4da248e87c712
https://github.com/scummvm/scummvm/commit/d399c37e6e7581e0bcb888a242c4da248e87c712
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
WIN32: Use uint instead of unsigned int.
Changed paths:
backends/platform/sdl/win32/win32_wrapper.cpp
backends/platform/sdl/win32/win32_wrapper.h
diff --git a/backends/platform/sdl/win32/win32_wrapper.cpp b/backends/platform/sdl/win32/win32_wrapper.cpp
index 844c6ba..7efcb00 100644
--- a/backends/platform/sdl/win32/win32_wrapper.cpp
+++ b/backends/platform/sdl/win32/win32_wrapper.cpp
@@ -81,7 +81,7 @@ bool confirmWindowsVersion(int majorVersion, int minorVersion) {
return VerifyVersionInfoFunc(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask);
}
-wchar_t *ansiToUnicode(const char *s, unsigned int codePage) {
+wchar_t *ansiToUnicode(const char *s, uint codePage) {
DWORD size = MultiByteToWideChar(codePage, 0, s, -1, NULL, 0);
if (size > 0) {
@@ -93,7 +93,7 @@ wchar_t *ansiToUnicode(const char *s, unsigned int codePage) {
return NULL;
}
-char *unicodeToAnsi(const wchar_t *s, unsigned int codePage) {
+char *unicodeToAnsi(const wchar_t *s, uint codePage) {
DWORD size = WideCharToMultiByte(codePage, 0, s, -1, NULL, 0, 0, 0);
if (size > 0) {
@@ -105,7 +105,7 @@ char *unicodeToAnsi(const wchar_t *s, unsigned int codePage) {
return NULL;
}
-unsigned int getCurrentCharset() {
+uint getCurrentCharset() {
#ifdef USE_TRANSLATION
Common::String charset = TransMan.getCurrentCharset();
if (charset == "iso-8859-2")
diff --git a/backends/platform/sdl/win32/win32_wrapper.h b/backends/platform/sdl/win32/win32_wrapper.h
index f2e16fa..9fa1a16 100644
--- a/backends/platform/sdl/win32/win32_wrapper.h
+++ b/backends/platform/sdl/win32/win32_wrapper.h
@@ -23,6 +23,8 @@
#ifndef PLATFORM_SDL_WIN32_WRAPPER_H
#define PLATFORM_SDL_WIN32_WRAPPER_H
+#include "common/scummsys.h"
+
HRESULT SHGetFolderPathFunc(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPSTR pszPath);
// Helper functions
@@ -45,7 +47,7 @@ bool confirmWindowsVersion(int majorVersion, int minorVersion);
*
* @note Return value must be freed by the caller.
*/
-wchar_t *ansiToUnicode(const char *s, unsigned int codePage = CP_ACP);
+wchar_t *ansiToUnicode(const char *s, uint codePage = CP_ACP);
/**
* Converts a Windows wide-character string into a C string.
* Used to interact with Win32 Unicode APIs with no ANSI fallback.
@@ -55,9 +57,9 @@ wchar_t *ansiToUnicode(const char *s, unsigned int codePage = CP_ACP);
*
* @note Return value must be freed by the caller.
*/
-char *unicodeToAnsi(const wchar_t *s, unsigned int codePage = CP_ACP);
+char *unicodeToAnsi(const wchar_t *s, uint codePage = CP_ACP);
-unsigned int getCurrentCharset();
+uint getCurrentCharset();
}
Commit: 6baa9c8ddb524f3d0ca91f34f2d3ddc2c055a6cd
https://github.com/scummvm/scummvm/commit/6baa9c8ddb524f3d0ca91f34f2d3ddc2c055a6cd
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TESTBED: Fix include guard in speech.h
Changed paths:
engines/testbed/speech.h
diff --git a/engines/testbed/speech.h b/engines/testbed/speech.h
index 715f252..96df9a3 100644
--- a/engines/testbed/speech.h
+++ b/engines/testbed/speech.h
@@ -20,8 +20,8 @@
*
*/
-#ifndef TESTBED_TEMPLATE_H
-#define TESTBED_TEMPLATE_H
+#ifndef TESTBED_SPEECH_H
+#define TESTBED_SPEECH_H
#include "testbed/testsuite.h"
#include "common/text-to-speech.h"
@@ -79,4 +79,4 @@ public:
} // End of namespace Testbed
-#endif // TESTBED_TEMPLATE_H
+#endif // TESTBED_SPEECH_H
Commit: 55c399c7c0ebe4084c1bdf31e647cfa50cc01c09
https://github.com/scummvm/scummvm/commit/55c399c7c0ebe4084c1bdf31e647cfa50cc01c09
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
TTS: Use Common::Encoding for encoding conversion.
Changed paths:
backends/text-to-speech/linux/linux-text-to-speech.cpp
backends/text-to-speech/windows/windows-text-to-speech.cpp
diff --git a/backends/text-to-speech/linux/linux-text-to-speech.cpp b/backends/text-to-speech/linux/linux-text-to-speech.cpp
index d201083..ccedb6b 100644
--- a/backends/text-to-speech/linux/linux-text-to-speech.cpp
+++ b/backends/text-to-speech/linux/linux-text-to-speech.cpp
@@ -27,12 +27,12 @@
#if defined(USE_TTS) && defined(USE_SPEECH_DISPATCHER) && defined(POSIX)
#include <speech-dispatcher/libspeechd.h>
-#include "backends/platform/sdl/sdl-sys.h"
#include "common/translation.h"
#include "common/system.h"
#include "common/ustr.h"
#include "common/config-manager.h"
+#include "common/encoding.h"
#include <pthread.h>
SPDConnection *_connection;
@@ -175,26 +175,6 @@ void SpeechDispatcherManager::updateState(SpeechDispatcherManager::SpeechEvent e
}
}
-Common::String SpeechDispatcherManager::strToUtf8(Common::String str, Common::String charset) {
-#if SDL_VERSION_ATLEAST(2, 0, 0)
-
- char *conv_text = SDL_iconv_string("UTF-8", charset.c_str(), str.c_str(), str.size() + 1);
- Common::String result;
- if (conv_text) {
- result = conv_text;
- SDL_free(conv_text);
- } else if (charset != "ASCII"){
- warning("Could not convert text from %s to UTF-8, trying ASCII", charset.c_str());
- return strToUtf8(str, "ASCII");
- } else
- warning("Could not convert text to UTF-8");
-
- return result;
-#else
- return Common::String();
-#endif
-}
-
bool SpeechDispatcherManager::say(Common::String str, Action action, Common::String charset) {
pthread_mutex_lock(&_speechMutex);
@@ -220,18 +200,25 @@ bool SpeechDispatcherManager::say(Common::String str, Action action, Common::Str
#endif
}
- str = strToUtf8(str, charset);
+ char *tmpStr = Common::Encoding::convert("UTF-8", charset, str.c_str(), str.size());
+ if (tmpStr == nullptr) {
+ warning("Cannot convert from %s encoding for text to speech", charset.c_str());
+ pthread_mutex_unlock(&_speechMutex);
+ return true;
+ }
+ Common::String strUtf8 = tmpStr;
+ free(tmpStr);
if (!_speechQueue.empty() && action == INTERRUPT_NO_REPEAT &&
- _speechQueue.front() == str && isSpeaking()) {
+ _speechQueue.front() == strUtf8 && isSpeaking()) {
_speechQueue.clear();
- _speechQueue.push_back(str);
+ _speechQueue.push_back(strUtf8);
pthread_mutex_unlock(&_speechMutex);
return true;
}
if (!_speechQueue.empty() && action == QUEUE_NO_REPEAT &&
- _speechQueue.back() == str && isSpeaking()) {
+ _speechQueue.back() == strUtf8 && isSpeaking()) {
pthread_mutex_unlock(&_speechMutex);
return true;
}
@@ -239,9 +226,9 @@ bool SpeechDispatcherManager::say(Common::String str, Action action, Common::Str
pthread_mutex_unlock(&_speechMutex);
if (isSpeaking() && (action == INTERRUPT || action == INTERRUPT_NO_REPEAT))
stop();
- if (!str.empty()) {
+ if (!strUtf8.empty()) {
pthread_mutex_lock(&_speechMutex);
- _speechQueue.push_back(str);
+ _speechQueue.push_back(strUtf8);
pthread_mutex_unlock(&_speechMutex);
if (isReady()) {
_speechState = SPEAKING;
diff --git a/backends/text-to-speech/windows/windows-text-to-speech.cpp b/backends/text-to-speech/windows/windows-text-to-speech.cpp
index b7d39f0..a59f219 100644
--- a/backends/text-to-speech/windows/windows-text-to-speech.cpp
+++ b/backends/text-to-speech/windows/windows-text-to-speech.cpp
@@ -32,7 +32,6 @@
#include <sapi.h>
#include "backends/text-to-speech/windows/sphelper-scummvm.h"
#include "backends/platform/sdl/win32/win32_wrapper.h"
-#include "backends/platform/sdl/win32/codepage.h"
#include "backends/text-to-speech/windows/windows-text-to-speech.h"
@@ -41,6 +40,7 @@
#include "common/system.h"
#include "common/ustr.h"
#include "common/config-manager.h"
+#include "common/encoding.h"
ISpVoice *_voice;
@@ -174,7 +174,11 @@ bool WindowsTextToSpeechManager::say(Common::String str, Action action, Common::
// We have to set the pitch by prepending xml code at the start of the said string;
Common::String pitch= Common::String::format("<pitch absmiddle=\"%d\">", _ttsState->_pitch / 10);
str.replace((uint32)0, 0, pitch);
- WCHAR *strW = Win32::ansiToUnicode(str.c_str(), Win32::getCodePageId(charset));
+ WCHAR *strW = (WCHAR *) Common::Encoding::convert("UTF-16", charset, str.c_str(), str.size());
+ if (strW == nullptr) {
+ warning("Cannot convert from %s encoding for text to speech", charset.c_str());
+ return true;
+ }
WaitForSingleObject(_speechMutex, INFINITE);
if (isSpeaking() && !_speechQueue.empty() && action == INTERRUPT_NO_REPEAT &&
Commit: 291360a280bef756f2733515a7bd532856572831
https://github.com/scummvm/scummvm/commit/291360a280bef756f2733515a7bd532856572831
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
COMMON: Add CP850 conversion.
CP850 is used by the mortevielle engine (and apparently by other
engines too). Anytime an engine using CP850 encoding wants to use
the TTS, the encoding has to be converted, so this is pretty
important encoding conversion to support. Unfortunately SDL
(when compiled without iconv) doesn't support this encoding
(which means, there might not be a way to convert this encoding
on some platforms), so I added a conversion table for this.
Changed paths:
common/encoding.cpp
common/encoding.h
diff --git a/common/encoding.cpp b/common/encoding.cpp
index 0fe490b..fd4f40a 100644
--- a/common/encoding.cpp
+++ b/common/encoding.cpp
@@ -167,6 +167,10 @@ char *Encoding::conversion(const String &to, const String &from, const char *str
result = convertTransManMapping(addUtfEndianness(to).c_str(), addUtfEndianness(from).c_str(), string, length);
}
+ if (result == nullptr) {
+ result = convertConversionTable(addUtfEndianness(to).c_str(), addUtfEndianness(from).c_str(), string, length);
+ }
+
return result;
}
@@ -317,6 +321,86 @@ char *Encoding::convertTransManMapping(const char *to, const char *from, const c
#endif // USE_TRANSLATION
}
+static uint32 g_cp850ConversionTable[] = {
+ 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
+ 0x25d8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
+ 0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
+ 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
+
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
+ 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+ 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
+ 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
+ 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0,
+ 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3,
+ 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4,
+ 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE,
+ 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580,
+ 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE,
+ 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4,
+ 0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8,
+ 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0
+};
+
+char *Encoding::convertConversionTable(const char *to, const char *from, const char *string, size_t length) {
+ if (String(from).equalsIgnoreCase("cp850")) {
+ uint32 *utf32Result = (uint32 *) calloc(sizeof(uint32), length + 1);
+ if (!utf32Result) {
+ warning("Could not allocate memory for encoding conversion");
+ return nullptr;
+ }
+ for (unsigned i = 0; i < length; i++) {
+ utf32Result[i] = g_cp850ConversionTable[(unsigned char) string[i]];
+ }
+ char *finalResult = convert(to, "utf-32", (char *)utf32Result, length * 4);
+ free(utf32Result);
+ return finalResult;
+ }
+ if (String(to).equalsIgnoreCase("cp850")) {
+ uint32 *utf32Result = (uint32 *) convert("utf-32", from, string, length);
+ if (String(from).hasPrefixIgnoreCase("utf-16"))
+ length /= 2;
+ if (String(from).hasPrefixIgnoreCase("utf-32"))
+ length /= 4;
+ char *finalResult = (char *) calloc(sizeof(char), length +1);
+ if (!finalResult) {
+ warning("Could not allocate memory for encoding conversion");
+ return nullptr;
+ }
+ for (unsigned i = 0; i < length; i++) {
+ for (unsigned j = 0; j < 257; j++) {
+ if (j == 256) {
+ // We have some character, that isn't a part of cp850, so
+ // we replace it with '?' to remain consistent with iconv
+ // and SDL
+ finalResult[i] = '?';
+ } else if (utf32Result[i] == g_cp850ConversionTable[j]){
+ finalResult[i] = j;
+ break;
+ }
+ }
+ }
+ free(utf32Result);
+ return finalResult;
+ }
+ return nullptr;
+}
+
static char g_cyrillicTransliterationTable[] = {
' ', 'E', 'D', 'G', 'E', 'Z', 'I', 'I', 'J', 'L', 'N', 'C', 'K', '-', 'U', 'D',
'A', 'B', 'V', 'G', 'D', 'E', 'Z', 'Z', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
diff --git a/common/encoding.h b/common/encoding.h
index 8245157..8a77c81 100644
--- a/common/encoding.h
+++ b/common/encoding.h
@@ -173,6 +173,22 @@ class Encoding {
static char *convertTransManMapping(const char *to, const char *from, const char *string, size_t length);
/**
+ * Uses conversion table to convert the string to unicode and from that
+ * to the final encoding. Important encodings, that aren't supported by
+ * all backends should go here.
+ *
+ * The result has to be freed after use.
+ *
+ * @param to Name of the encoding the strings will be converted to
+ * @param from Name of the encoding the strings will be converted from
+ * @param string String that should be converted.
+ * @param length Length of the string to convert in bytes.
+ *
+ * @return Converted string (must be freed) or nullptr if the conversion failed
+ */
+ static char *convertConversionTable(const char *to, const char *from, const char *string, size_t length);
+
+ /**
* Transliterates cyrillic string in iso-8859-5 encoding and returns
* it's ASCII (latin) form.
*
Commit: d4a98dcadbb9d374966c207be34f7a1926be26fe
https://github.com/scummvm/scummvm/commit/d4a98dcadbb9d374966c207be34f7a1926be26fe
Author: Jaromir Wysoglad (jaromirwysoglad at gmail.com)
Date: 2019-09-01T22:47:55+03:00
Commit Message:
COMMON: Add CP437 encoding conversion table.
Changed paths:
common/encoding.cpp
diff --git a/common/encoding.cpp b/common/encoding.cpp
index fd4f40a..fc743e5 100644
--- a/common/encoding.cpp
+++ b/common/encoding.cpp
@@ -357,21 +357,80 @@ static uint32 g_cp850ConversionTable[] = {
0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0
};
+static uint32 g_cp437ConversionTable[] = {
+ 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
+ 0x25d8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
+ 0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
+ 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
+
+ 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
+ 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+ 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
+ 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
+ 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
+ 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+ 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+ 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
+ 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
+ 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+ 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
+ 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+ 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
+ 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0
+};
+
+struct ConversionTable {
+ const char *name;
+ uint32 *table;
+};
+
+const ConversionTable g_encodingConversionTables[] = {
+ {"cp850", g_cp850ConversionTable},
+ {"cp437", g_cp437ConversionTable},
+ {nullptr, nullptr}
+};
+
char *Encoding::convertConversionTable(const char *to, const char *from, const char *string, size_t length) {
- if (String(from).equalsIgnoreCase("cp850")) {
+ uint32 *table = nullptr;
+ for (const ConversionTable *i = g_encodingConversionTables; i->name != nullptr; i++) {
+ if (String(from).equalsIgnoreCase(i->name)) {
+ table = i->table;
+ }
+ }
+ if (table != nullptr) {
uint32 *utf32Result = (uint32 *) calloc(sizeof(uint32), length + 1);
if (!utf32Result) {
warning("Could not allocate memory for encoding conversion");
return nullptr;
}
for (unsigned i = 0; i < length; i++) {
- utf32Result[i] = g_cp850ConversionTable[(unsigned char) string[i]];
+ utf32Result[i] = table[(unsigned char) string[i]];
}
char *finalResult = convert(to, "utf-32", (char *)utf32Result, length * 4);
free(utf32Result);
return finalResult;
}
- if (String(to).equalsIgnoreCase("cp850")) {
+
+ for (const ConversionTable *i = g_encodingConversionTables; i->name != nullptr; i++) {
+ if (String(to).equalsIgnoreCase(i->name)) {
+ table = i->table;
+ }
+ }
+ if (table != nullptr) {
uint32 *utf32Result = (uint32 *) convert("utf-32", from, string, length);
if (String(from).hasPrefixIgnoreCase("utf-16"))
length /= 2;
@@ -389,7 +448,7 @@ char *Encoding::convertConversionTable(const char *to, const char *from, const c
// we replace it with '?' to remain consistent with iconv
// and SDL
finalResult[i] = '?';
- } else if (utf32Result[i] == g_cp850ConversionTable[j]){
+ } else if (utf32Result[i] == table[j]){
finalResult[i] = j;
break;
}
More information about the Scummvm-git-logs
mailing list