From d07e1e34e0fe984b1794de629f5b5d3145c72e9b Mon Sep 17 00:00:00 2001 From: Demur Rumed Date: Sat, 19 Apr 2025 01:41:44 +0000 Subject: [PATCH] a11y: linux tts with espeak-ng --- .github/workflows/apt-deps.txt | 2 +- soh/CMakeLists.txt | 30 ++++++++++++------- .../ESpeakSpeechSynthesizer.cpp | 26 ++++++++++++++++ .../ESpeakSpeechSynthesizer.h | 17 +++++++++++ .../speechsynthesizer/SpeechSynthesizer.h | 2 ++ soh/soh/OTRGlobals.cpp | 6 ++-- 6 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 soh/soh/Enhancements/speechsynthesizer/ESpeakSpeechSynthesizer.cpp create mode 100644 soh/soh/Enhancements/speechsynthesizer/ESpeakSpeechSynthesizer.h diff --git a/.github/workflows/apt-deps.txt b/.github/workflows/apt-deps.txt index 51e9574f8..3461cdbac 100644 --- a/.github/workflows/apt-deps.txt +++ b/.github/workflows/apt-deps.txt @@ -1 +1 @@ -libusb-dev libusb-1.0-0-dev libsdl2-dev libsdl2-net-dev libpng-dev libglew-dev nlohmann-json3-dev libtinyxml2-dev libspdlog-dev ninja-build +libusb-dev libusb-1.0-0-dev libsdl2-dev libsdl2-net-dev libpng-dev libglew-dev nlohmann-json3-dev libtinyxml2-dev libspdlog-dev libespeak-ng-dev ninja-build diff --git a/soh/CMakeLists.txt b/soh/CMakeLists.txt index 7277b2dfb..b3faf9fa2 100644 --- a/soh/CMakeLists.txt +++ b/soh/CMakeLists.txt @@ -141,16 +141,21 @@ endif() # handle Network removals if (!BUILD_REMOTE_CONTROL) - list(FILTER soh__ EXCLUDE REGEX "soh/Enhancements/crowd-control/*") + list(FILTER soh__ EXCLUDE REGEX "soh/Enhancements/crowd-control/") endif() # handle speechsynthesizer removals if (CMAKE_SYSTEM_NAME STREQUAL "Windows") - list(FILTER soh__ EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/Darwin*") + list(FILTER soh__ EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/Darwin") elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") - list(FILTER soh__ EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/SAPI*") + list(FILTER soh__ EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/SAPI") else() - list(FILTER soh__ EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/(Darwin|SAPI).*") + list(FILTER soh__ EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/(Darwin|SAPI)") +endif() + +find_library(ESPEAK espeak-ng) +if (NOT ESPEAK) + list(FILTER soh__ EXCLUDE REGEX "soh/Enhancements/speechsynthesizer/ESpeak") endif() # soh/Extractor {{{ @@ -176,12 +181,12 @@ file(GLOB_RECURSE src__ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.c" "src/*.h" set_source_files_properties(${src__} PROPERTIES COMPILE_OPTIONS "${WARNING_OVERRIDE}") list(APPEND src__ ${CMAKE_CURRENT_SOURCE_DIR}/Resource.rc) -list(FILTER src__ EXCLUDE REGEX "src/dmadata/*") -list(FILTER src__ EXCLUDE REGEX "src/elf_message/*") -list(FILTER src__ EXCLUDE REGEX "src/libultra/io/*") -list(FILTER src__ EXCLUDE REGEX "src/libultra/libc/*") -list(FILTER src__ EXCLUDE REGEX "src/libultra/os/*") -list(FILTER src__ EXCLUDE REGEX "src/libultra/rmon/*") +list(FILTER src__ EXCLUDE REGEX "src/dmadata/") +list(FILTER src__ EXCLUDE REGEX "src/elf_message/") +list(FILTER src__ EXCLUDE REGEX "src/libultra/io/") +list(FILTER src__ EXCLUDE REGEX "src/libultra/libc/") +list(FILTER src__ EXCLUDE REGEX "src/libultra/os/") +list(FILTER src__ EXCLUDE REGEX "src/libultra/rmon/") list(APPEND src__ "src/libultra/libc/sprintf.c") list(REMOVE_ITEM src__ "src/libultra/gu/cosf.c") list(REMOVE_ITEM src__ "src/libultra/gu/lookat.c") @@ -317,6 +322,11 @@ if (BUILD_REMOTE_CONTROL) endif() endif() +if (ESPEAK) + add_compile_definitions(ESPEAK=1) + target_link_libraries(${PROJECT_NAME} PUBLIC espeak-ng) +endif() + target_include_directories(${PROJECT_NAME} PRIVATE assets ${CMAKE_CURRENT_SOURCE_DIR}/include/ ${CMAKE_CURRENT_SOURCE_DIR}/src/ diff --git a/soh/soh/Enhancements/speechsynthesizer/ESpeakSpeechSynthesizer.cpp b/soh/soh/Enhancements/speechsynthesizer/ESpeakSpeechSynthesizer.cpp new file mode 100644 index 000000000..b7dc3d932 --- /dev/null +++ b/soh/soh/Enhancements/speechsynthesizer/ESpeakSpeechSynthesizer.cpp @@ -0,0 +1,26 @@ +#include "ESpeakSpeechSynthesizer.h" +extern "C" { +#include +} + +ESpeakSpeechSynthesizer::ESpeakSpeechSynthesizer() { +} + +bool ESpeakSpeechSynthesizer::DoInit() { + return espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 100, NULL, 0) != -1; +} + +void ESpeakSpeechSynthesizer::DoUninitialize() { + espeak_Terminate(); +} + +void ESpeakSpeechSynthesizer::Speak(const char* text, const char* language) { + if (language != this->mLanguage) { + espeak_VOICE voice = { .languages = language }; + if (espeak_SetVoiceByProperties(&voice)) { + return; + } + this->mLanguage = language; + } + espeak_Synth(text, 100, 0, POS_CHARACTER, 0, espeakCHARS_UTF8, NULL, NULL); +} diff --git a/soh/soh/Enhancements/speechsynthesizer/ESpeakSpeechSynthesizer.h b/soh/soh/Enhancements/speechsynthesizer/ESpeakSpeechSynthesizer.h new file mode 100644 index 000000000..a7f454946 --- /dev/null +++ b/soh/soh/Enhancements/speechsynthesizer/ESpeakSpeechSynthesizer.h @@ -0,0 +1,17 @@ +#pragma once + +#include "SpeechSynthesizer.h" + +class ESpeakSpeechSynthesizer : public SpeechSynthesizer { + public: + ESpeakSpeechSynthesizer(); + + void Speak(const char* text, const char* language); + + protected: + bool DoInit(void); + void DoUninitialize(void); + + private: + const char* mLanguage; +}; diff --git a/soh/soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h b/soh/soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h index 29d209b5f..e82da907f 100644 --- a/soh/soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h +++ b/soh/soh/Enhancements/speechsynthesizer/SpeechSynthesizer.h @@ -35,6 +35,8 @@ class SpeechSynthesizer { #include "SAPISpeechSynthesizer.h" #elif defined(__APPLE__) #include "DarwinSpeechSynthesizer.h" +#elif ESPEAK +#include "ESpeakSpeechSynthesizer.h" #endif #include "SpeechLogger.h" diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 87cb361e1..85056daf6 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -1224,14 +1224,14 @@ extern "C" void InitOTR() { ActorDB::Instance = new ActorDB(); #ifdef __APPLE__ SpeechSynthesizer::Instance = new DarwinSpeechSynthesizer(); - SpeechSynthesizer::Instance->Init(); #elif defined(_WIN32) SpeechSynthesizer::Instance = new SAPISpeechSynthesizer(); - SpeechSynthesizer::Instance->Init(); +#elif ESPEAK + SpeechSynthesizer::Instance = new ESpeakSpeechSynthesizer(); #else SpeechSynthesizer::Instance = new SpeechLogger(); - SpeechSynthesizer::Instance->Init(); #endif + SpeechSynthesizer::Instance->Init(); #ifdef ENABLE_REMOTE_CONTROL CrowdControl::Instance = new CrowdControl();