Implement feedback voice 85/150385/12
authorMariusz Wachowicz <m.wachowicz@partner.samsung.com>
Wed, 13 Sep 2017 11:43:31 +0000 (13:43 +0200)
committerMariusz Wachowicz <m.wachowicz@partner.samsung.com>
Thu, 21 Sep 2017 14:37:22 +0000 (16:37 +0200)
Add ToggleVoiceFeedbackEnabledActivity class
responsible for changing VConf keys

Add TextToSpeech class responsible for communication
with tts-engine

Change-Id: Icea8ecce2fe72396c0dc4084a866ea9f2499f443

13 files changed:
CMakeLists.txt
packaging/org.tizen.universal-switch.spec
src/MenuBuilder.cpp
src/RowScanner.cpp
src/ScreenScannerManager.cpp
src/TextToSpeech.cpp [new file with mode: 0644]
src/TextToSpeech.hpp [new file with mode: 0644]
src/ToggleVoiceFeedbackEnabledActivity.cpp [new file with mode: 0644]
src/UniversalSwitch.cpp
src/UniversalSwitch.hpp
src/VConfKeys.hpp
src/utils.hpp [new file with mode: 0644]
utils/setVconfKeys.sh

index 9b595e0..5548274 100644 (file)
@@ -10,18 +10,19 @@ FIND_PACKAGE(PkgConfig REQUIRED)
 option(TESTS "enable/disable universal switch tests" ON)
 
 pkg_check_modules(pkgs REQUIRED
+    atspi-2
     capi-appfw-service-application
     capi-appfw-application
     capi-media-player
     capi-telephony
-    elementary
-    ecore
-    sqlite3
-    atspi-2
     capi-ui-efl-util
+    ecore
     efl-extension
-    gobject-2.0
     eldbus
+    elementary
+    gobject-2.0
+    sqlite3
+    tts
 )
 
 SET(COMMON_FLAGS "-fdiagnostics-color=always -fPIC")
index 18c4fb1..eb3e18e 100644 (file)
@@ -11,6 +11,8 @@ BuildRequires:  at-spi2-core
 BuildRequires:  at-spi2-core-devel
 BuildRequires:  cmake
 BuildRequires:  gettext-tools
+BuildRequires:  tts
+BuildRequires:  tts-devel
 BuildRequires:  pkgconfig(capi-appfw-service-application)
 BuildRequires:  pkgconfig(capi-appfw-application)
 BuildRequires:  pkgconfig(capi-media-player)
index d65a723..6631c38 100644 (file)
@@ -262,7 +262,7 @@ MenuMap::MenuMap()
        auto turnOnOffVoice                     =       std::make_shared<MenuItem>(
                                                                                std::vector<std::string> {"IDS_TURN_ON_VOICE", "IDS_TURN_OFF_VOICE"},
                                                                                defaultImg,
-                                                                               std::string {}/*TODO add activity*/,
+                                                                               std::string {"TOGGLE_VOICE_FEEDBACK_ENABLED_ACTIVITY"},
                                                                                VconfKeyType::BOOL,
                                                                                std::vector<std::string> {VCONF_KEY_FEEDBACK_VOICE_ENABLED});
        auto turnOnOffSound                     =       std::make_shared<MenuItem>(
index 7c82f52..f26927b 100644 (file)
@@ -4,6 +4,7 @@
 #include "RowScanner.hpp"
 #include "ScanningProperties.hpp"
 #include "Sound.hpp"
+#include "TextToSpeech.hpp"
 #include "UniversalSwitch.hpp"
 #include "UniversalSwitchLog.hpp"
 #include "Window.hpp"
@@ -11,8 +12,8 @@
 #include <Elementary.h>
 
 #include <functional>
-#include <vector>
 #include <utility>
+#include <vector>
 
 
 static const int DASHED_LINE_LENGTH = 20;
@@ -52,6 +53,19 @@ namespace
        }
 }
 
+namespace feedback
+{
+       void playSoundFeedback()
+       {
+               auto tts = Singleton<UniversalSwitch>::instance().getTextToSpeech();
+               if (tts->isEnabled()) {
+                       auto elem = Singleton<NavigationInterface>::instance().getCurrentElement();
+                       Singleton<UniversalSwitch>::instance().getTextToSpeech()->speak(elem);
+               }
+               Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED);
+       }
+}
+
 class RowScannerImpl : public ScreenScanner
 {
 public:
@@ -149,7 +163,7 @@ Optional<std::shared_ptr<UIElement>> RowScannerImpl::acceptAutoscanningPhase()
                if (state == State::ITEMS)
                        continueScanning = true;
                stopScanning();
-               Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED);
+               feedback::playSoundFeedback();
                state = State::START;
        }
 
@@ -160,13 +174,13 @@ Optional<std::shared_ptr<UIElement>> RowScannerImpl::acceptAutoscanningPhase()
                return {};
        case State::ROWS:
                stopScanning();
-               Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED);
                startItemInRowScanning();
+               feedback::playSoundFeedback();
                state = State::ITEMS;
                return {};
        case State::ITEMS:
                stopScanning();
-               Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED);
+               feedback::playSoundFeedback();
                state = State::END;
                DEBUG("Scanning complete");
                return Singleton<NavigationInterface>::instance().getCurrentElement();
@@ -329,7 +343,7 @@ bool RowScannerImpl::iterateToNextScanningElement()
                ASSERT(0, "State::END case should not be reached");
                return false;
        }
-       Sound::playSoundFeedback(Sound::ID::NAVIGATION_ITERATED);
+       feedback::playSoundFeedback();
 
        return true;
 }
index 87bb38a..731a24c 100644 (file)
@@ -2,6 +2,7 @@
 #include "UniversalSwitchLog.hpp"
 #include "Window.hpp"
 #include "Sound.hpp"
+#include "TextToSpeech.hpp"
 #include "UniversalSwitch.hpp"
 
 #include <functional>
@@ -111,6 +112,7 @@ void ScreenScannerManager::acceptAutoscanning()
        }
 
        stopAutoscanning();
+       Singleton<UniversalSwitch>::instance().getTextToSpeech()->speak(*element);
        notify(*element);
 }
 
diff --git a/src/TextToSpeech.cpp b/src/TextToSpeech.cpp
new file mode 100644 (file)
index 0000000..f9eeae6
--- /dev/null
@@ -0,0 +1,137 @@
+#include "Atspi.hpp"
+#include "TextToSpeech.hpp"
+#include "UniversalSwitch.hpp"
+#include "UniversalSwitchLog.hpp"
+#include "utils.hpp"
+#include "VConfKeys.hpp"
+
+
+#define EXIT_IF_ERROR(error) \
+       do { \
+               if (error != TTS_ERROR_NONE) { \
+                       ERROR("error = %s", get_tts_error(error)); \
+                       return; \
+               } \
+       } while (0)
+
+namespace
+{
+       const char *get_tts_error(int r)
+       {
+               switch (r) {
+               case TTS_ERROR_NONE:
+                       return "no error";
+               case TTS_ERROR_INVALID_PARAMETER:
+                       return "invalid parameter";
+               case TTS_ERROR_OUT_OF_MEMORY:
+                       return "out of memory";
+               case TTS_ERROR_OPERATION_FAILED:
+                       return "operation failed";
+               case TTS_ERROR_INVALID_STATE:
+                       return "invalid state";
+               default:
+                       return "unknown error";
+               }
+       }
+}
+
+TextToSpeech::TextToSpeech()
+{
+       auto error = tts_create(&ttsHandler);
+       EXIT_IF_ERROR(error);
+       error = tts_set_mode(ttsHandler, TTS_MODE_SCREEN_READER);
+       EXIT_IF_ERROR(error);
+       error = tts_prepare(ttsHandler);
+       EXIT_IF_ERROR(error);
+
+       callbackHandles.push_back(Singleton<VConfInterface>::instance().registerAndGet<int>(VCONF_KEY_FEEDBACK_VOICE_SPEECH_RATE, TTS_SPEED_AUTO,
+       [this](auto speed) {
+               int min, normal, max;
+               auto error = tts_get_speed_range(ttsHandler, &min, &normal, &max);
+               if (error != TTS_ERROR_NONE) {
+                       DEBUG("error = %s", get_tts_error(error));
+                       return;
+               }
+               speechRate = utils::clamp(speed, min, max);
+       }));
+
+       callbackHandles.push_back(Singleton<VConfInterface>::instance().registerAndGet<bool>(VCONF_KEY_FEEDBACK_VOICE_ENABLED, false,
+       [this](auto state) {
+               DEBUG("Feedback voice %s", state ? "enabled" : "disabled");
+               enabled = state;
+       }));
+}
+
+TextToSpeech::~TextToSpeech()
+{
+       if (!ttsHandler)
+               return;
+
+       auto error = tts_stop(ttsHandler);
+       EXIT_IF_ERROR(error);
+       error = tts_destroy(ttsHandler);
+       EXIT_IF_ERROR(error);
+       ttsHandler = nullptr;
+}
+
+//TODO: support for long texts
+void TextToSpeech::speak(std::string text) const
+{
+       if (!enabled) {
+               DEBUG("Voice feedback is disabled");
+               return;
+       }
+
+       if (text.empty()) {
+               ERROR("Empty string passed to speak method");
+               return;
+       }
+
+       tts_state_e state;
+       auto error = tts_get_state(ttsHandler, &state);
+       EXIT_IF_ERROR(error);
+       if (state == TTS_STATE_PLAYING || state == TTS_STATE_PAUSED) {
+               error = tts_stop(ttsHandler);
+               EXIT_IF_ERROR(error);
+       }
+
+       auto maxSize = 0u;
+       error = tts_get_max_text_size(ttsHandler, &maxSize);
+       EXIT_IF_ERROR(error);
+       if (text.size() > maxSize) {
+               ERROR("Text is too long. passed = %d max = %d", text.size(), maxSize);
+               //TODO: support for long texts
+               text.resize(maxSize);
+       }
+
+       int utteranceID;
+       error = tts_add_text(ttsHandler, text.c_str(), language, TTS_VOICE_TYPE_AUTO, speechRate, &utteranceID);
+       EXIT_IF_ERROR(error);
+       DEBUG("speaking: %s", text.c_str());
+       error = tts_play(ttsHandler);
+       EXIT_IF_ERROR(error);
+}
+
+void TextToSpeech::speak(const std::shared_ptr<UIElement> &element) const
+{
+       if (!enabled) {
+               DEBUG("Voice feedback is disabled");
+               return;
+       }
+
+       if (!element) {
+               DEBUG("Element is nullptr");
+               return;
+       }
+
+       Singleton<UniversalSwitch>::instance().getAtspi()->getName(element->getObject(),
+       [ptr = shared_from_this()](Optional<std::string> name) {
+               if (name)
+                       ptr->speak(*name);
+       });
+}
+
+bool TextToSpeech::isEnabled() const
+{
+       return enabled;
+}
diff --git a/src/TextToSpeech.hpp b/src/TextToSpeech.hpp
new file mode 100644 (file)
index 0000000..aebd2a7
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef TEXT_TO_SPEECH_HPP
+#define TEXT_TO_SPEECH_HPP
+
+#include "UIElement.hpp"
+#include "VConf.hpp"
+
+#include <tts.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+
+class TextToSpeech : public std::enable_shared_from_this<TextToSpeech>
+{
+public:
+       TextToSpeech();
+       ~TextToSpeech();
+
+       bool isEnabled() const;
+       void speak(std::string text) const;
+       void speak(const std::shared_ptr<UIElement> &element) const;
+
+private:
+       tts_h ttsHandler = nullptr;
+       bool enabled = false;
+       int speechRate = TTS_SPEED_AUTO;
+       const char *language = "en_US"; //TODO synchronize with system parameters
+
+       std::vector<VConfInterface::CallbackHandle> callbackHandles;
+};
+
+#endif
diff --git a/src/ToggleVoiceFeedbackEnabledActivity.cpp b/src/ToggleVoiceFeedbackEnabledActivity.cpp
new file mode 100644 (file)
index 0000000..697323c
--- /dev/null
@@ -0,0 +1,22 @@
+#include "Activity.hpp"
+#include "ActivityFactory.hpp"
+#include "UniversalSwitchLog.hpp"
+#include "VConf.hpp"
+#include "VConfKeys.hpp"
+
+
+class ToggleVoiceFeedbackEnabledActivity : public Activity, private RegisterActivity<ToggleVoiceFeedbackEnabledActivity>
+{
+public:
+       constexpr static const char *activityType = "TOGGLE_VOICE_FEEDBACK_ENABLED_ACTIVITY";
+       ToggleVoiceFeedbackEnabledActivity()
+               : Activity(activityType)
+       {}
+
+       bool process() override
+       {
+               auto isEnabled = Singleton<VConfInterface>::instance().get(VCONF_KEY_FEEDBACK_VOICE_ENABLED, true);
+               Singleton<VConfInterface>::instance().set(VCONF_KEY_FEEDBACK_VOICE_ENABLED, !isEnabled);
+               return true;
+       }
+};
index 8f92c0d..6c19c61 100644 (file)
@@ -1,5 +1,6 @@
 #include "AccessoriesSwitchProvider.hpp"
 #include "ActivityFactory.hpp"
+#include "Atspi.hpp"
 #include "CameraSwitchProvider.hpp"
 #include "CompositeSwitchProvider.hpp"
 #include "DBusInterface.hpp"
@@ -8,12 +9,11 @@
 #include "SQLiteConfiguration.hpp"
 #include "SwitchConfigurationItem.hpp"
 #include "SwitchManager.hpp"
+#include "TextToSpeech.hpp"
 #include "UniversalSwitch.hpp"
 #include "UniversalSwitchLog.hpp"
-#include "Window.hpp"
 #include "VConfKeys.hpp"
-#include "Atspi.hpp"
-
+#include "Window.hpp"
 
 void UniversalSwitch::initialize()
 {
@@ -46,9 +46,10 @@ void UniversalSwitch::startScanning()
        auto activityFactory = ActivityFactory::getInstance();
        switchManager = SwitchManager::create<SwitchManager>(compositeSwitchProvider, configuration, activityFactory);
 
+       textToSpeech = std::make_shared<TextToSpeech>();
+
        screenScannerManager = std::make_shared<ScreenScannerManager>();
        screenScannerManager->startAutoscanning();
-
 }
 
 void UniversalSwitch::stopScanning()
@@ -59,6 +60,7 @@ void UniversalSwitch::stopScanning()
                switchManager->terminate();
                switchManager.reset();
        }
+       textToSpeech.reset();
        atspi.reset();
 }
 
@@ -83,6 +85,11 @@ std::shared_ptr<DBusInterface> UniversalSwitch::getDBusInterface() const
        return dbusInterface;
 }
 
+std::shared_ptr<TextToSpeech> UniversalSwitch::getTextToSpeech() const
+{
+       return textToSpeech;
+}
+
 void UniversalSwitch::setSwitchManager(const std::shared_ptr<SwitchManager> &sm)
 {
        switchManager = sm;
index 1ff7ee6..aaf4b4f 100644 (file)
@@ -6,13 +6,16 @@
 
 #include <memory>
 
-class SwitchManager;
+
+class Atspi;
 class CompositeSwitchProvider;
 class Configuration;
 class DBusInterface;
 class ScreenScannerManager;
+class SwitchManager;
+class TextToSpeech;
 class Window;
-class Atspi;
+
 
 class UniversalSwitch
 {
@@ -25,6 +28,7 @@ public:
        std::shared_ptr<ScreenScannerManager> getScreenScannerManager() const;
        std::shared_ptr<Atspi> getAtspi() const;
        std::shared_ptr<Window> getMainWindow();
+       std::shared_ptr<TextToSpeech> getTextToSpeech() const;
 
        void setSwitchManager(const std::shared_ptr<SwitchManager> &sm);
        void setCompositeSwitchProvider(const std::shared_ptr<CompositeSwitchProvider> &csp);
@@ -43,6 +47,7 @@ private:
        std::shared_ptr<Configuration> configuration;
        std::shared_ptr<DBusInterface> dbusInterface;
        std::shared_ptr<Atspi> atspi;
+       std::shared_ptr<TextToSpeech> textToSpeech;
 
        std::shared_ptr<ScreenScannerManager> screenScannerManager;
        std::weak_ptr<Window> mainWindow;
index cacc244..1b595e5 100644 (file)
@@ -65,4 +65,5 @@
 
 #define VCONF_KEY_HID_DETECTED                                         "memory/isf/hw_keyboard_input_detected"
 
+#define VCONF_KEY_LANGUAGE                                                     "db/menu_widget/language"
 #endif
diff --git a/src/utils.hpp b/src/utils.hpp
new file mode 100644 (file)
index 0000000..98aed69
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef UTILS_HPP
+#define UTILS_HPP
+
+
+namespace utils
+{
+       // TODO it would be removed when c++17 came
+       template<class T>
+       T clamp(T v, const T &lo, const T &hi)
+       {
+               if (v < lo)
+                       v = lo;
+               else if (v > hi)
+                       v = hi;
+               return v;
+       }
+}
+
+#endif
index db55b31..5224326 100755 (executable)
@@ -35,8 +35,8 @@ $VCONFTOOL int "${VCONF_PROJECT_PREFIX}FEEDBACK_CURSOR_COLOR" 0
 $VCONFTOOL bool   "${VCONF_PROJECT_PREFIX}FEEDBACK_SOUND_ENABLED" 1
 $VCONFTOOL double "${VCONF_PROJECT_PREFIX}FEEDBACK_SOUND_VOLUME" 1.0
 
-$VCONFTOOL bool   "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_ENABLED" 0
-$VCONFTOOL double "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_SPEECH_RATE" 1.0
+$VCONFTOOL bool   "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_ENABLED"  0
+$VCONFTOOL int "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_SPEECH_RATE" 8           # value beetwen 1 and 15, 0 = TTS_SPEED_AUTO
 $VCONFTOOL double "${VCONF_PROJECT_PREFIX}FEEDBACK_VOICE_SPEECH_VOLUME" 1.0
 
 $VCONFTOOL int "${VCONF_PROJECT_PREFIX}GRANULARITY_UNIT" 0                                     # 0 = CHARACTER, 1 = WORD, 2 = LINE, 3 = PARAGRAPH