From 1c267013e69e69e692c0ca2b1c5036b95c5b96fd Mon Sep 17 00:00:00 2001 From: m1maker Date: Sat, 19 Oct 2024 00:45:50 +0300 Subject: [PATCH] Add SpeechDispatcher symbol level support. --- .github/workflows/cmake-build.yml | 94 +- .gitignore | 76 +- Bindings/Python/Example.py | 92 +- Bindings/Python/sral.py | 286 ++-- CMakeLists.txt | 174 +-- CMakePresets.json | 172 +-- Dep/UIAProvider.cpp | 292 ++-- Dep/UIAProvider.h | 90 +- Dep/blastspeak.c | 2130 ++++++++++++++--------------- Dep/blastspeak.h | 188 +-- Dep/fsapi.c | 170 +-- Dep/fsapi.h | 572 ++++---- Dep/wasapi.cpp | 748 +++++----- Dep/wasapi.h | 376 ++--- Examples/C/SRALExample.c | 130 +- Include/SRAL.h | 868 ++++++------ LICENSE | 42 +- README.md | 326 ++--- SRC/AVSpeech.h | 92 +- SRC/AVSpeech.mm | 282 ++-- SRC/Encoding.cpp | 136 +- SRC/Encoding.h | 14 +- SRC/Engine.h | 70 +- SRC/Jaws.cpp | 110 +- SRC/Jaws.h | 100 +- SRC/NVDA.cpp | 202 +-- SRC/NVDA.h | 122 +- SRC/SAPI.cpp | 428 +++--- SRC/SAPI.h | 92 +- SRC/SRAL.cpp | 1270 ++++++++--------- SRC/SpeechDispatcher.cpp | 150 +- SRC/SpeechDispatcher.h | 104 +- SRC/UIA.cpp | 116 +- SRC/UIA.h | 114 +- 34 files changed, 5119 insertions(+), 5109 deletions(-) diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml index 20df282..576539f 100644 --- a/.github/workflows/cmake-build.yml +++ b/.github/workflows/cmake-build.yml @@ -1,47 +1,47 @@ -name: CMake Build - -on: - push: - branches: - - main - pull_request: - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up CMake - uses: jwlawson/actions-setup-cmake@v2 - with: - cmake-version: '3.25.0' - - - name: Install packages - if: matrix.os == 'ubuntu-latest' - run: | - sudo apt-get update - sudo apt-get install -y libspeechd-dev - - - - - name: Configure CMake - run: cmake . -B build -DCMAKE_OSX_ARCHITECTURES=x86_64 - - - name: Build with CMake - run: cmake --build build --config Release - - - - - name: Archive artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.os }}-build - path: build - - +name: CMake Build + +on: + push: + branches: + - main + pull_request: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up CMake + uses: jwlawson/actions-setup-cmake@v2 + with: + cmake-version: '3.25.0' + + - name: Install packages + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libspeechd-dev + + + + - name: Configure CMake + run: cmake . -B build -DCMAKE_OSX_ARCHITECTURES=x86_64 + + - name: Build with CMake + run: cmake --build build --config Release + + + + - name: Archive artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.os }}-build + path: build + + diff --git a/.gitignore b/.gitignore index 1617507..7b4d517 100644 --- a/.gitignore +++ b/.gitignore @@ -1,38 +1,38 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app -# Other -.vs/ -out/ -Release/ -*.pyc -*.pyd +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +# Other +.vs/ +out/ +Release/ +*.pyc +*.pyd diff --git a/Bindings/Python/Example.py b/Bindings/Python/Example.py index 5508a73..45fe3b9 100644 --- a/Bindings/Python/Example.py +++ b/Bindings/Python/Example.py @@ -1,46 +1,46 @@ -import time - -import sral - -def sleep_ms(milliseconds): - time.sleep(milliseconds / 1000.0) # Convert milliseconds to seconds - -def main(): - text = "" - # Initialize the SRAL library - instance = sral.Sral(32) - - instance.register_keyboard_hooks() - - # Speak some text - if instance.get_engine_features(0) & 128: - text = input("Enter the text you want to be spoken:\n") - instance.speak(text, False) - - # Output text to a Braille display - if instance.get_engine_features(0) & 256: - text = input("Enter the text you want to be shown on braille display:\n") - instance.braille(text) - - # Delay example - instance.output("Delay example: Enter any text", False) - instance.delay(5000) - instance.output("Press enter to continue", False) - input() # Wait for user to press enter - - instance.stop_speech() # Stops the delay thread - - # Speech rate - if instance.get_engine_features(0) & 512: - rate = instance.get_rate() - max_rate = rate + 10 - for rate in range(rate, max_rate): - instance.set_rate(rate) - instance.speak(text, False) - sleep_ms(500) - - # Uninitialize the SRAL library - instance = None -if __name__ == "__main__": - main() # invoke_main() - +import time + +import sral + +def sleep_ms(milliseconds): + time.sleep(milliseconds / 1000.0) # Convert milliseconds to seconds + +def main(): + text = "" + # Initialize the SRAL library + instance = sral.Sral(32) + + instance.register_keyboard_hooks() + + # Speak some text + if instance.get_engine_features(0) & 128: + text = input("Enter the text you want to be spoken:\n") + instance.speak(text, False) + + # Output text to a Braille display + if instance.get_engine_features(0) & 256: + text = input("Enter the text you want to be shown on braille display:\n") + instance.braille(text) + + # Delay example + instance.output("Delay example: Enter any text", False) + instance.delay(5000) + instance.output("Press enter to continue", False) + input() # Wait for user to press enter + + instance.stop_speech() # Stops the delay thread + + # Speech rate + if instance.get_engine_features(0) & 512: + rate = instance.get_rate() + max_rate = rate + 10 + for rate in range(rate, max_rate): + instance.set_rate(rate) + instance.speak(text, False) + sleep_ms(500) + + # Uninitialize the SRAL library + instance = None +if __name__ == "__main__": + main() # invoke_main() + diff --git a/Bindings/Python/sral.py b/Bindings/Python/sral.py index 05f60de..48075fd 100644 --- a/Bindings/Python/sral.py +++ b/Bindings/Python/sral.py @@ -1,143 +1,143 @@ -import ctypes -from ctypes import c_bool, c_char_p, c_uint64, c_int -import platform - - -class Sral: - def __init__(self, engines_exclude=0): - system = platform.system() - if system == "Windows": - self.lib = ctypes.CDLL("./SRAL.dll") - elif system == "Linux": - self.lib = ctypes.CDLL("./libSRAL.so") - elif system == "Darwin": # MacOS - self.lib = ctypes.CDLL("./libSRAL.dylib") - else: - raise OSError(f"Unsupported operating system: {system}") - - - if not self.lib.SRAL_Initialize(c_int(engines_exclude)): - raise RuntimeError("Failed to initialize SRAL") - - def __del__(self): - self.lib.SRAL_Uninitialize() - - def speak(self, text, interrupt=True): - return self.lib.SRAL_Speak(c_char_p(text.encode('utf-8')), c_bool(interrupt)) - - def speak_ssml(self, text, interrupt=True): - return self.lib.SRAL_SpeakSsml(c_char_p(text.encode('utf-8')), c_bool(interrupt)) - - def braille(self, text): - return self.lib.SRAL_Braille(c_char_p(text.encode('utf-8'))) - - def output(self, text, interrupt=True): - return self.lib.SRAL_Output(c_char_p(text.encode('utf-8')), c_bool(interrupt)) - - def stop_speech(self): - return self.lib.SRAL_StopSpeech() - - def pause_speech(self): - return self.lib.SRAL_PauseSpeech() - - def resume_speech(self): - return self.lib.SRAL_ResumeSpeech() - - def get_current_engine(self): - return self.lib.SRAL_GetCurrentEngine() - - def get_engine_features(self, engine): - return self.lib.SRAL_GetEngineFeatures(c_int(engine)) - - def set_engine_parameter(self, engine, param, value): - return self.lib.SRAL_SetEngineParameter(c_int(engine), c_int(param), c_int(value)) - - - def set_volume(self, value): - return self.lib.SRAL_SetVolume(c_uint64(value)) - - def get_volume(self): - return self.lib.SRAL_GetVolume() - - def set_rate(self, value): - return self.lib.SRAL_SetRate(c_uint64(value)) - - def get_rate(self): - return self.lib.SRAL_GetRate() - - def get_voice_count(self): - return self.lib.SRAL_GetVoiceCount() - - def get_voice_name(self, index): - self.lib.SRAL_GetVoiceName.restype = c_char_p - voice_name = self.lib.SRAL_GetVoiceName(c_uint64(index)) - - if voice_name is None: - raise ValueError(f"No voice found at index {index}") - - return voice_name.decode('utf-8') - - def set_voice(self, index): - return self.lib.SRAL_SetVoice(c_uint64(index)) - - def speak_ex(self, engine, text, interrupt=True): - return self.lib.SRAL_SpeakEx(c_int(engine), c_char_p(text.encode('utf-8')), c_bool(interrupt)) - - def speak_ssml_ex(self, engine, text, interrupt=True): - return self.lib.SRAL_SpeakSsmlEx(c_int(engine), c_char_p(text.encode('utf-8')), c_bool(interrupt)) - - - def braille_ex(self, engine, text): - return self.lib.SRAL_BrailleEx(c_int(engine), c_char_p(text.encode('utf-8'))) - - def output_ex(self, engine, text, interrupt=True): - return self.lib.SRAL_OutputEx(c_int(engine), c_char_p(text.encode('utf-8')), c_bool(interrupt)) - - def stop_speech_ex(self, engine): - return self.lib.SRAL_StopSpeechEx(c_int(engine)) - - def pause_speech_ex(self, engine): - return self.lib.SRAL_PauseSpeechEx(c_int(engine)) - - def resume_speech_ex(self, engine): - return self.lib.SRAL_ResumeSpeechEx(c_int(engine)) - - def set_volume_ex(self, engine, value): - return self.lib.SRAL_SetVolumeEx(c_int(engine), c_uint64(value)) - - def get_volume_ex(self, engine): - return self.lib.SRAL_GetVolumeEx(c_int(engine)) - - def set_rate_ex(self, engine, value): - return self.lib.SRAL_SetRateEx(c_int(engine), c_uint64(value)) - - def get_rate_ex(self, engine): - return self.lib.SRAL_GetRateEx(c_int(engine)) - - def get_voice_count_ex(self, engine): - return self.lib.SRAL_GetVoiceCountEx(c_int(engine)) - - def get_voice_name_ex(self, engine, index): - self.lib.SRAL_GetVoiceName.restype = c_char_p - voice_name = self.lib.SRAL_GetVoiceNameEx(c_int(engine), c_uint64(index)) - - if voice_name is None: - raise ValueError(f"No voice found at index {index}") - - return voice_name.decode('utf-8') - - - def set_voice_ex(self, engine, index): - return self.lib.SRAL_SetVoiceEx(c_int(engine), c_uint64(index)) - - def is_initialized(self): - return self.lib.SRAL_IsInitialized() - - def delay(self, time): - return self.lib.SRAL_Delay(c_int(time)) - - def register_keyboard_hooks(self): - return self.lib.SRAL_RegisterKeyboardHooks() - - def unregister_keyboard_hooks(self): - return self.lib.SRAL_UnregisterKeyboardHooks() +import ctypes +from ctypes import c_bool, c_char_p, c_uint64, c_int +import platform + + +class Sral: + def __init__(self, engines_exclude=0): + system = platform.system() + if system == "Windows": + self.lib = ctypes.CDLL("./SRAL.dll") + elif system == "Linux": + self.lib = ctypes.CDLL("./libSRAL.so") + elif system == "Darwin": # MacOS + self.lib = ctypes.CDLL("./libSRAL.dylib") + else: + raise OSError(f"Unsupported operating system: {system}") + + + if not self.lib.SRAL_Initialize(c_int(engines_exclude)): + raise RuntimeError("Failed to initialize SRAL") + + def __del__(self): + self.lib.SRAL_Uninitialize() + + def speak(self, text, interrupt=True): + return self.lib.SRAL_Speak(c_char_p(text.encode('utf-8')), c_bool(interrupt)) + + def speak_ssml(self, text, interrupt=True): + return self.lib.SRAL_SpeakSsml(c_char_p(text.encode('utf-8')), c_bool(interrupt)) + + def braille(self, text): + return self.lib.SRAL_Braille(c_char_p(text.encode('utf-8'))) + + def output(self, text, interrupt=True): + return self.lib.SRAL_Output(c_char_p(text.encode('utf-8')), c_bool(interrupt)) + + def stop_speech(self): + return self.lib.SRAL_StopSpeech() + + def pause_speech(self): + return self.lib.SRAL_PauseSpeech() + + def resume_speech(self): + return self.lib.SRAL_ResumeSpeech() + + def get_current_engine(self): + return self.lib.SRAL_GetCurrentEngine() + + def get_engine_features(self, engine): + return self.lib.SRAL_GetEngineFeatures(c_int(engine)) + + def set_engine_parameter(self, engine, param, value): + return self.lib.SRAL_SetEngineParameter(c_int(engine), c_int(param), c_int(value)) + + + def set_volume(self, value): + return self.lib.SRAL_SetVolume(c_uint64(value)) + + def get_volume(self): + return self.lib.SRAL_GetVolume() + + def set_rate(self, value): + return self.lib.SRAL_SetRate(c_uint64(value)) + + def get_rate(self): + return self.lib.SRAL_GetRate() + + def get_voice_count(self): + return self.lib.SRAL_GetVoiceCount() + + def get_voice_name(self, index): + self.lib.SRAL_GetVoiceName.restype = c_char_p + voice_name = self.lib.SRAL_GetVoiceName(c_uint64(index)) + + if voice_name is None: + raise ValueError(f"No voice found at index {index}") + + return voice_name.decode('utf-8') + + def set_voice(self, index): + return self.lib.SRAL_SetVoice(c_uint64(index)) + + def speak_ex(self, engine, text, interrupt=True): + return self.lib.SRAL_SpeakEx(c_int(engine), c_char_p(text.encode('utf-8')), c_bool(interrupt)) + + def speak_ssml_ex(self, engine, text, interrupt=True): + return self.lib.SRAL_SpeakSsmlEx(c_int(engine), c_char_p(text.encode('utf-8')), c_bool(interrupt)) + + + def braille_ex(self, engine, text): + return self.lib.SRAL_BrailleEx(c_int(engine), c_char_p(text.encode('utf-8'))) + + def output_ex(self, engine, text, interrupt=True): + return self.lib.SRAL_OutputEx(c_int(engine), c_char_p(text.encode('utf-8')), c_bool(interrupt)) + + def stop_speech_ex(self, engine): + return self.lib.SRAL_StopSpeechEx(c_int(engine)) + + def pause_speech_ex(self, engine): + return self.lib.SRAL_PauseSpeechEx(c_int(engine)) + + def resume_speech_ex(self, engine): + return self.lib.SRAL_ResumeSpeechEx(c_int(engine)) + + def set_volume_ex(self, engine, value): + return self.lib.SRAL_SetVolumeEx(c_int(engine), c_uint64(value)) + + def get_volume_ex(self, engine): + return self.lib.SRAL_GetVolumeEx(c_int(engine)) + + def set_rate_ex(self, engine, value): + return self.lib.SRAL_SetRateEx(c_int(engine), c_uint64(value)) + + def get_rate_ex(self, engine): + return self.lib.SRAL_GetRateEx(c_int(engine)) + + def get_voice_count_ex(self, engine): + return self.lib.SRAL_GetVoiceCountEx(c_int(engine)) + + def get_voice_name_ex(self, engine, index): + self.lib.SRAL_GetVoiceName.restype = c_char_p + voice_name = self.lib.SRAL_GetVoiceNameEx(c_int(engine), c_uint64(index)) + + if voice_name is None: + raise ValueError(f"No voice found at index {index}") + + return voice_name.decode('utf-8') + + + def set_voice_ex(self, engine, index): + return self.lib.SRAL_SetVoiceEx(c_int(engine), c_uint64(index)) + + def is_initialized(self): + return self.lib.SRAL_IsInitialized() + + def delay(self, time): + return self.lib.SRAL_Delay(c_int(time)) + + def register_keyboard_hooks(self): + return self.lib.SRAL_RegisterKeyboardHooks() + + def unregister_keyboard_hooks(self): + return self.lib.SRAL_UnregisterKeyboardHooks() diff --git a/CMakeLists.txt b/CMakeLists.txt index 88a65ff..3760180 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,87 +1,87 @@ -# CMakeList.txt : CMake project for SRAL, include source and define -# project specific logic here. -# -cmake_minimum_required (VERSION 3.25) -set(CMAKE_CXX_STANDARD 20) - -set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") -set(INCLUDES "Include") - -# Enable Hot Reload for MSVC compilers if supported. -if (POLICY CMP0141) - cmake_policy(SET CMP0141 NEW) - set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") -endif() - -project ("SRAL") -add_library(${PROJECT_NAME}_obj OBJECT) -target_sources(${PROJECT_NAME}_obj PRIVATE - "SRC/SRAL.cpp" "SRC/Engine.h") -target_sources(${PROJECT_NAME}_obj PUBLIC - FILE_SET HEADERS - BASE_DIRS "${INCLUDES}" - FILES "${INCLUDES}/SRAL.h") -if(WIN32) - target_sources(${PROJECT_NAME}_obj PRIVATE - "SRC/Encoding.h" "SRC/Encoding.cpp" - "SRC/NVDA.h" "SRC/NVDA.cpp" "SRC/SAPI.h" "SRC/SAPI.cpp" - "Dep/blastspeak.h" "Dep/blastspeak.c" "Dep/fsapi.h" "Dep/fsapi.c" - "SRC/Jaws.h" "SRC/Jaws.cpp" "SRC/UIA.cpp" - "Dep/UIAProvider.h" "Dep/UIAProvider.cpp" "Dep/wasapi.h" "Dep/wasapi.cpp") -elseif(APPLE) - target_sources(${PROJECT_NAME}_obj PRIVATE - "SRC/AVSpeech.h" "SRC/AVSpeech.mm") -else() - target_sources(${PROJECT_NAME}_obj PRIVATE - "SRC/SpeechDispatcher.h" "SRC/SpeechDispatcher.cpp") -endif() - -set_property(TARGET ${PROJECT_NAME}_obj - PROPERTY POSITION_INDEPENDENT_CODE on) - -add_library(${PROJECT_NAME} SHARED - $) -add_library(${PROJECT_NAME}_static STATIC - $) - -target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDES}) -target_include_directories(${PROJECT_NAME}_static PUBLIC ${INCLUDES}) - -install(TARGETS ${PROJECT_NAME}_obj FILE_SET HEADERS - INCLUDES DESTINATION "include") - -install(TARGETS ${PROJECT_NAME} - RUNTIME DESTINATION "bin" - LIBRARY DESTINATION "lib" - ARCHIVE DESTINATION "lib") -install(TARGETS ${PROJECT_NAME}_static DESTINATION "lib") - - -add_executable(${PROJECT_NAME}_test "Examples/C/SRALExample.c" "Include/SRAL.h") -target_link_libraries(${PROJECT_NAME}_test ${PROJECT_NAME}_static) - -if (WIN32) - set(LIBS "uiautomationcore.lib") - target_link_libraries(${PROJECT_NAME} ${LIBS}) - target_link_libraries(${PROJECT_NAME}_static ${LIBS}) -elseif (APPLE) - enable_language(OBJCXX) - set(CMAKE_C_COMPILER clang) - set(CMAKE_CXX_COMPILER clang++) - target_link_libraries(${PROJECT_NAME} "-framework Foundation" "-framework AVFoundation") - target_link_libraries(${PROJECT_NAME}_test "-framework Foundation" "-framework AVFoundation") -else() - find_package(X11 REQUIRED) - target_link_libraries(${PROJECT_NAME} ${X11_LIBRARIES}) - target_link_libraries(${PROJECT_NAME}_test ${X11_LIBRARIES}) - find_package(PkgConfig REQUIRED) - pkg_check_modules(SpeechD REQUIRED speech-dispatcher) - - target_link_libraries(${PROJECT_NAME} ${SpeechD_LIBRARIES}) - target_link_libraries(${PROJECT_NAME}_test ${SpeechD_LIBRARIES}) - -endif() - - - - +# CMakeList.txt : CMake project for SRAL, include source and define +# project specific logic here. +# +cmake_minimum_required (VERSION 3.25) +set(CMAKE_CXX_STANDARD 20) + +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +set(INCLUDES "Include") + +# Enable Hot Reload for MSVC compilers if supported. +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") +endif() + +project ("SRAL") +add_library(${PROJECT_NAME}_obj OBJECT) +target_sources(${PROJECT_NAME}_obj PRIVATE + "SRC/SRAL.cpp" "SRC/Engine.h") +target_sources(${PROJECT_NAME}_obj PUBLIC + FILE_SET HEADERS + BASE_DIRS "${INCLUDES}" + FILES "${INCLUDES}/SRAL.h") +if(WIN32) + target_sources(${PROJECT_NAME}_obj PRIVATE + "SRC/Encoding.h" "SRC/Encoding.cpp" + "SRC/NVDA.h" "SRC/NVDA.cpp" "SRC/SAPI.h" "SRC/SAPI.cpp" + "Dep/blastspeak.h" "Dep/blastspeak.c" "Dep/fsapi.h" "Dep/fsapi.c" + "SRC/Jaws.h" "SRC/Jaws.cpp" "SRC/UIA.cpp" + "Dep/UIAProvider.h" "Dep/UIAProvider.cpp" "Dep/wasapi.h" "Dep/wasapi.cpp") +elseif(APPLE) + target_sources(${PROJECT_NAME}_obj PRIVATE + "SRC/AVSpeech.h" "SRC/AVSpeech.mm") +else() + target_sources(${PROJECT_NAME}_obj PRIVATE + "SRC/SpeechDispatcher.h" "SRC/SpeechDispatcher.cpp") +endif() + +set_property(TARGET ${PROJECT_NAME}_obj + PROPERTY POSITION_INDEPENDENT_CODE on) + +add_library(${PROJECT_NAME} SHARED + $) +add_library(${PROJECT_NAME}_static STATIC + $) + +target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDES}) +target_include_directories(${PROJECT_NAME}_static PUBLIC ${INCLUDES}) + +install(TARGETS ${PROJECT_NAME}_obj FILE_SET HEADERS + INCLUDES DESTINATION "include") + +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION "bin" + LIBRARY DESTINATION "lib" + ARCHIVE DESTINATION "lib") +install(TARGETS ${PROJECT_NAME}_static DESTINATION "lib") + + +add_executable(${PROJECT_NAME}_test "Examples/C/SRALExample.c" "Include/SRAL.h") +target_link_libraries(${PROJECT_NAME}_test ${PROJECT_NAME}_static) + +if (WIN32) + set(LIBS "uiautomationcore.lib") + target_link_libraries(${PROJECT_NAME} ${LIBS}) + target_link_libraries(${PROJECT_NAME}_static ${LIBS}) +elseif (APPLE) + enable_language(OBJCXX) + set(CMAKE_C_COMPILER clang) + set(CMAKE_CXX_COMPILER clang++) + target_link_libraries(${PROJECT_NAME} "-framework Foundation" "-framework AVFoundation") + target_link_libraries(${PROJECT_NAME}_test "-framework Foundation" "-framework AVFoundation") +else() + find_package(X11 REQUIRED) + target_link_libraries(${PROJECT_NAME} ${X11_LIBRARIES}) + target_link_libraries(${PROJECT_NAME}_test ${X11_LIBRARIES}) + find_package(PkgConfig REQUIRED) + pkg_check_modules(SpeechD REQUIRED speech-dispatcher) + + target_link_libraries(${PROJECT_NAME} ${SpeechD_LIBRARIES}) + target_link_libraries(${PROJECT_NAME}_test ${SpeechD_LIBRARIES}) + +endif() + + + + diff --git a/CMakePresets.json b/CMakePresets.json index 1514cb1..59c684b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,87 +1,87 @@ -{ - "version": 3, - "configurePresets": [ - { - "name": "windows-base", - "hidden": true, - "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", - "installDir": "${sourceDir}/out/install/${presetName}", - "cacheVariables": { - }, - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Windows" - } - }, - { - "name": "x64-debug", - "displayName": "x64 Debug", - "inherits": "windows-base", - "architecture": { - "value": "x64", - "strategy": "external" - }, - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug" - } - }, - { - "name": "x64-release", - "displayName": "x64 Release", - "inherits": "x64-debug", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release" - } - }, - { - "name": "x86-debug", - "displayName": "x86 Debug", - "inherits": "windows-base", - "architecture": { - "value": "x86", - "strategy": "external" - }, - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Debug" - } - }, - { - "name": "x86-release", - "displayName": "x86 Release", - "inherits": "x86-debug", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release" - } - }, - { - "name": "linux-debug", - "displayName": "Linux Debug", - "description": "Target the Windows Subsystem for Linux (WSL) or a remote Linux system.", - "generator": "Ninja", - "binaryDir": "${sourceDir}/out/build/${presetName}", - "installDir": "${sourceDir}/out/install/${presetName}", - "cacheVariables": {}, - "condition": { - "type": "equals", - "lhs": "${hostSystemName}", - "rhs": "Linux" - }, - "vendor": { - "microsoft.com/VisualStudioRemoteSettings/CMake/2.0": { - "remoteSourceRootDir": "$env{HOME}/.vs/$ms{projectDirName}", - "copyBuildOutput": true - } - } - }, - { - "name": "linux-release", - "displayName": "Linux Release", - "inherits": "linux-debug", - "cacheVariables": { - "CMAKE_BUILD_TYPE": "Release" - } - } - ] +{ + "version": 3, + "configurePresets": [ + { + "name": "windows-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "x64-debug", + "displayName": "x64 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x64-release", + "displayName": "x64 Release", + "inherits": "x64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "x86-debug", + "displayName": "x86 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x86-release", + "displayName": "x86 Release", + "inherits": "x86-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "linux-debug", + "displayName": "Linux Debug", + "description": "Target the Windows Subsystem for Linux (WSL) or a remote Linux system.", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": {}, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/2.0": { + "remoteSourceRootDir": "$env{HOME}/.vs/$ms{projectDirName}", + "copyBuildOutput": true + } + } + }, + { + "name": "linux-release", + "displayName": "Linux Release", + "inherits": "linux-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ] } \ No newline at end of file diff --git a/Dep/UIAProvider.cpp b/Dep/UIAProvider.cpp index e0db8b7..d1e8366 100644 --- a/Dep/UIAProvider.cpp +++ b/Dep/UIAProvider.cpp @@ -1,146 +1,146 @@ -/************************************************************************************************* - * Description: Implementation of the Provider class, which implements IRawElementProviderSimple - * and IInvokeProvider for a simple custom control. - * - * Copyright (C) Microsoft Corporation. All rights reserved. - * - * This source code is intended only as a supplement to Microsoft - * Development Tools and/or on-line documentation. See these other - * materials for detailed information regarding Microsoft code samples. - * - * THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY - * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A - * PARTICULAR PURPOSE. - * - *************************************************************************************************/ - -#define INITGUID -#include -#include -#include -#include - -#include -#include - -#include "UIAProvider.h" - -Provider::Provider(HWND hwnd) : m_refCount(1), m_controlHWnd(hwnd) -{ - // Nothing to do. -} - -Provider::~Provider() -{ - // Nothing to do. -} - -// IUnknown implementation. - -IFACEMETHODIMP_(ULONG) Provider::AddRef() -{ - return InterlockedIncrement(&m_refCount); -} - -IFACEMETHODIMP_(ULONG) Provider::Release() -{ - long val = InterlockedDecrement(&m_refCount); - if (val == 0) - { - delete this; - } - return val; -} - -IFACEMETHODIMP Provider::QueryInterface(REFIID riid, void** ppInterface) -{ - if (riid == __uuidof(IUnknown)) - { - *ppInterface = static_cast(this); - } - else if (riid == __uuidof(IRawElementProviderSimple)) - { - *ppInterface = static_cast(this); - } - else if (riid == __uuidof(IInvokeProvider)) - { - *ppInterface = static_cast(this); - } - else - { - *ppInterface = NULL; - return E_NOINTERFACE; - } - - (static_cast(*ppInterface))->AddRef(); - return S_OK; -} - - -// IRawElementProviderSimple implementation - -// Get provider options. - -IFACEMETHODIMP Provider::get_ProviderOptions(ProviderOptions* pRetVal) -{ - *pRetVal = ProviderOptions_ServerSideProvider; - return S_OK; -} - -// Get the object that supports IInvokePattern. - -IFACEMETHODIMP Provider::GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) -{ - if (patternId == UIA_InvokePatternId) - { - AddRef(); - *pRetVal = static_cast(this); - } - else - { - *pRetVal = NULL; - } - return S_OK; -} - -// Gets custom properties. - -IFACEMETHODIMP Provider::GetPropertyValue(PROPERTYID propertyId, VARIANT* pRetVal) -{ - if (propertyId == UIA_ControlTypePropertyId) - { - pRetVal->vt = VT_I4; - pRetVal->lVal = UIA_ButtonControlTypeId; - } - - // The Name property comes from the Caption property of the control window, if it has one. - // The Name is overridden here for the sake of illustration. - else if (propertyId == UIA_NamePropertyId) - { - pRetVal->vt = VT_BSTR; - pRetVal->bstrVal = SysAllocString(L"ColorButton"); - } - else - { - pRetVal->vt = VT_EMPTY; - // UI Automation will attempt to get the property from the host window provider. - } - return S_OK; -} - -// Gets the UI Automation provider for the host window. This provider supplies most properties. - -IFACEMETHODIMP Provider::get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) -{ - return UiaHostProviderFromHwnd(m_controlHWnd, pRetVal); -} - - -// IInvokeProvider implementation. - -IFACEMETHODIMP Provider::Invoke() -{ - PostMessage(m_controlHWnd, WM_LBUTTONDOWN, NULL, NULL); - return S_OK; -} +/************************************************************************************************* + * Description: Implementation of the Provider class, which implements IRawElementProviderSimple + * and IInvokeProvider for a simple custom control. + * + * Copyright (C) Microsoft Corporation. All rights reserved. + * + * This source code is intended only as a supplement to Microsoft + * Development Tools and/or on-line documentation. See these other + * materials for detailed information regarding Microsoft code samples. + * + * THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY + * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A + * PARTICULAR PURPOSE. + * + *************************************************************************************************/ + +#define INITGUID +#include +#include +#include +#include + +#include +#include + +#include "UIAProvider.h" + +Provider::Provider(HWND hwnd) : m_refCount(1), m_controlHWnd(hwnd) +{ + // Nothing to do. +} + +Provider::~Provider() +{ + // Nothing to do. +} + +// IUnknown implementation. + +IFACEMETHODIMP_(ULONG) Provider::AddRef() +{ + return InterlockedIncrement(&m_refCount); +} + +IFACEMETHODIMP_(ULONG) Provider::Release() +{ + long val = InterlockedDecrement(&m_refCount); + if (val == 0) + { + delete this; + } + return val; +} + +IFACEMETHODIMP Provider::QueryInterface(REFIID riid, void** ppInterface) +{ + if (riid == __uuidof(IUnknown)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IRawElementProviderSimple)) + { + *ppInterface = static_cast(this); + } + else if (riid == __uuidof(IInvokeProvider)) + { + *ppInterface = static_cast(this); + } + else + { + *ppInterface = NULL; + return E_NOINTERFACE; + } + + (static_cast(*ppInterface))->AddRef(); + return S_OK; +} + + +// IRawElementProviderSimple implementation + +// Get provider options. + +IFACEMETHODIMP Provider::get_ProviderOptions(ProviderOptions* pRetVal) +{ + *pRetVal = ProviderOptions_ServerSideProvider; + return S_OK; +} + +// Get the object that supports IInvokePattern. + +IFACEMETHODIMP Provider::GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal) +{ + if (patternId == UIA_InvokePatternId) + { + AddRef(); + *pRetVal = static_cast(this); + } + else + { + *pRetVal = NULL; + } + return S_OK; +} + +// Gets custom properties. + +IFACEMETHODIMP Provider::GetPropertyValue(PROPERTYID propertyId, VARIANT* pRetVal) +{ + if (propertyId == UIA_ControlTypePropertyId) + { + pRetVal->vt = VT_I4; + pRetVal->lVal = UIA_ButtonControlTypeId; + } + + // The Name property comes from the Caption property of the control window, if it has one. + // The Name is overridden here for the sake of illustration. + else if (propertyId == UIA_NamePropertyId) + { + pRetVal->vt = VT_BSTR; + pRetVal->bstrVal = SysAllocString(L"ColorButton"); + } + else + { + pRetVal->vt = VT_EMPTY; + // UI Automation will attempt to get the property from the host window provider. + } + return S_OK; +} + +// Gets the UI Automation provider for the host window. This provider supplies most properties. + +IFACEMETHODIMP Provider::get_HostRawElementProvider(IRawElementProviderSimple** pRetVal) +{ + return UiaHostProviderFromHwnd(m_controlHWnd, pRetVal); +} + + +// IInvokeProvider implementation. + +IFACEMETHODIMP Provider::Invoke() +{ + PostMessage(m_controlHWnd, WM_LBUTTONDOWN, NULL, NULL); + return S_OK; +} diff --git a/Dep/UIAProvider.h b/Dep/UIAProvider.h index 3024080..3de18a8 100644 --- a/Dep/UIAProvider.h +++ b/Dep/UIAProvider.h @@ -1,45 +1,45 @@ -/************************************************************************************************* - * - * Description: Declaration of the Provider class. - * - * Copyright (C) Microsoft Corporation. All rights reserved. - * - * This source code is intended only as a supplement to Microsoft - * Development Tools and/or on-line documentation. See these other - * materials for detailed information regarding Microsoft code samples. - * - * THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY - * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A - * PARTICULAR PURPOSE. - * - *************************************************************************************************/ -#include -#include -class Provider : public IRawElementProviderSimple, - public IInvokeProvider -{ -public: - Provider(HWND hwnd); - - // IUnknown methods - IFACEMETHODIMP_(ULONG) AddRef(); - IFACEMETHODIMP_(ULONG) Release(); - IFACEMETHODIMP QueryInterface(REFIID riid, void**); - - // IRawElementProviderSimple methods - IFACEMETHODIMP get_ProviderOptions(ProviderOptions* pRetVal); - IFACEMETHODIMP GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal); - IFACEMETHODIMP GetPropertyValue(PROPERTYID propertyId, VARIANT* pRetVal); - IFACEMETHODIMP get_HostRawElementProvider(IRawElementProviderSimple** pRetVal); - - // IInvokeProvider methods - IFACEMETHODIMP Invoke(); - -private: - virtual ~Provider(); - // Ref Counter for this COM object - ULONG m_refCount; - - HWND m_controlHWnd; // The HWND for the control. -}; +/************************************************************************************************* + * + * Description: Declaration of the Provider class. + * + * Copyright (C) Microsoft Corporation. All rights reserved. + * + * This source code is intended only as a supplement to Microsoft + * Development Tools and/or on-line documentation. See these other + * materials for detailed information regarding Microsoft code samples. + * + * THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY + * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A + * PARTICULAR PURPOSE. + * + *************************************************************************************************/ +#include +#include +class Provider : public IRawElementProviderSimple, + public IInvokeProvider +{ +public: + Provider(HWND hwnd); + + // IUnknown methods + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + IFACEMETHODIMP QueryInterface(REFIID riid, void**); + + // IRawElementProviderSimple methods + IFACEMETHODIMP get_ProviderOptions(ProviderOptions* pRetVal); + IFACEMETHODIMP GetPatternProvider(PATTERNID patternId, IUnknown** pRetVal); + IFACEMETHODIMP GetPropertyValue(PROPERTYID propertyId, VARIANT* pRetVal); + IFACEMETHODIMP get_HostRawElementProvider(IRawElementProviderSimple** pRetVal); + + // IInvokeProvider methods + IFACEMETHODIMP Invoke(); + +private: + virtual ~Provider(); + // Ref Counter for this COM object + ULONG m_refCount; + + HWND m_controlHWnd; // The HWND for the control. +}; diff --git a/Dep/blastspeak.c b/Dep/blastspeak.c index a34763c..d6e8652 100644 --- a/Dep/blastspeak.c +++ b/Dep/blastspeak.c @@ -1,1065 +1,1065 @@ -/* -Blastspeak text to speech library -Copyright (c) 2019-2020 Philip Bennefall - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. -*/ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "blastspeak.h" -#include -#include -#include -#include -#include -#include - - const IID BS_IID_null = { 0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} }; - const IID BS_IID_IDispatch = { 0x00020400, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46} }; - const IID BS_IID_SpVoice = { 0x96749377, 0x3391, 0x11D2, {0x9E, 0xE3, 0x00, 0xC0, 0x4F, 0x79, 0x73, 0x96} }; - const IID BS_IID_SpMemoryStream = { 0x5FB7EF7D, 0xDFF4, 0x468a, {0xB6, 0xB7, 0x2F, 0xCB, 0xD1, 0x88, 0xF9, 0x94} }; - -#if 0 - static void print_error(HRESULT hr, UINT puArgErr) - { - switch (hr) - { - case DISP_E_BADPARAMCOUNT: - printf("The number of elements provided to DISPPARAMS is different from the number of arguments accepted by the method or property."); - break; - case DISP_E_BADVARTYPE: - printf("One of the arguments in DISPPARAMS is not a valid variant type."); - break; - case DISP_E_EXCEPTION: - printf("The application needs to raise an exception."); - break; - case DISP_E_MEMBERNOTFOUND: - printf("The requested member does not exist."); - break; - case DISP_E_NONAMEDARGS: - printf("This implementation of IDispatch does not support named arguments."); - break; - case DISP_E_OVERFLOW: - printf("One of the arguments in DISPPARAMS could not be coerced to the specified type."); - break; - case DISP_E_PARAMNOTFOUND: - printf("One of the parameter IDs does not correspond to a parameter on the method."); - break; - case DISP_E_TYPEMISMATCH: - printf("One or more of the arguments could not be coerced."); - break; - case DISP_E_UNKNOWNINTERFACE: - printf("The interface identifier passed in riid is not IID_NULL."); - break; - case DISP_E_UNKNOWNLCID: - printf("The member being invoked interprets string arguments according to the LCID, and the LCID is not recognized."); - break; - case DISP_E_PARAMNOTOPTIONAL: - printf("A required parameter was omitted."); - break; - default: - printf("Unknown error"); - } - printf("\nArgument error: %u\n", puArgErr); - } -#endif - - HRESULT InitVariantFromString(__in PCWSTR psz, __out VARIANT* pvar) - { - pvar->vt = VT_BSTR; - pvar->bstrVal = SysAllocString(psz); - HRESULT hr = pvar->bstrVal ? S_OK : (psz ? E_OUTOFMEMORY : E_INVALIDARG); - if (FAILED(hr)) - { - VariantInit(pvar); - } - return hr; - } - - HRESULT InitVariantFromUInt32(__in ULONG ulVal, __out VARIANT* pvar) - { - pvar->vt = VT_UI4; - pvar->ulVal = ulVal; - return S_OK; - } - - static char* blastspeak_get_temporary_memory(blastspeak* instance, unsigned int bytes) - { - if (bytes <= blastspeak_static_memory_length) - { - return instance->static_memory; - } - if (instance->allocated_memory && bytes <= instance->allocated_memory_length) - { - return instance->allocated_memory; - } - instance->allocated_memory = (char*)realloc(instance->allocated_memory, (size_t)bytes); - if (instance->allocated_memory == NULL) - { - instance->allocated_memory_length = 0; - return NULL; - } - instance->allocated_memory_length = bytes; - return instance->allocated_memory; - } - - /* If length_in_bytes is nonzero, it must be the length of the string excluding the NULL terminator. */ - /* If length_in_bytes is 0, the function looks for the NULL terminator to figure out the length. */ - /* The input string must always be NULL terminated. */ - /* The returned string is always NULL terminated. The function returns a NULL pointer on failure. */ - static WCHAR* blastspeak_get_wchar_from_utf8(blastspeak* instance, const char* the_string, unsigned int length_in_bytes) - { - WCHAR* result; - int needed_size; - - if (length_in_bytes == 0) - { - length_in_bytes = strlen(the_string); - } - if (length_in_bytes == 0) - { - return NULL; - } - ++length_in_bytes; - needed_size = MultiByteToWideChar(CP_UTF8, 0, the_string, length_in_bytes, NULL, 0); - if (needed_size == 0) - { - return NULL; - } - result = (WCHAR*)blastspeak_get_temporary_memory(instance, needed_size * sizeof(WCHAR)); - if (result == NULL) - { - return NULL; - } - if (MultiByteToWideChar(CP_UTF8, 0, the_string, length_in_bytes, result, needed_size) != needed_size) - { - return NULL; - } - return result; - } - - /* The returned string is always NULL terminated. The function returns a NULL pointer on failure. */ - /* The input BSTR is not freed. */ - static char* blastspeak_get_UTF8_from_BSTR(blastspeak* instance, BSTR the_string) - { - char* result; - int needed_size; - unsigned int length_in_chars = SysStringLen(the_string); - if (length_in_chars == 0) - { - return NULL; - } - - ++length_in_chars; - needed_size = WideCharToMultiByte(CP_UTF8, 0, the_string, length_in_chars, NULL, 0, NULL, NULL); - if (needed_size == 0) - { - return NULL; - } - result = blastspeak_get_temporary_memory(instance, needed_size); - if (result == NULL) - { - return NULL; - } - if (WideCharToMultiByte(CP_UTF8, 0, the_string, length_in_chars, result, needed_size, NULL, NULL) != needed_size) - { - return NULL; - } - return result; - } - - static int blastspeak_get_stream_format(blastspeak* instance, int retrieve_dispids, unsigned long* sample_rate, unsigned char* bits_per_sample, unsigned char* channels); - - int blastspeak_initialize(blastspeak* instance) - { - OLECHAR* voice_names[] = { L"AllowAudioOutputFormatChangesOnNextSet", L"AudioOutputStream", L"GetVoices", L"Rate", L"Speak", L"Status", L"Voice", L"Volume" }; - OLECHAR* voice_token_names[] = { L"GetAttribute", L"GetDescription" }; - OLECHAR* voice_collection_names[] = { L"Count", L"Item" }; - OLECHAR* memory_stream_names[] = { L"GetData", L"Format", L"SetData" }; - IDispatch* stream; - HRESULT hr; - DISPPARAMS parameters; - VARIANT return_value; - UINT puArgErr; - DISPID voice_collection_dispids[2]; - LONG voice_count = 0; - - instance->allocated_memory = NULL; - instance->allocated_memory_length = 0; - instance->static_memory[0] = 0; - instance->must_reset_output = 0; - - CoInitializeEx(NULL, COINIT_MULTITHREADED); - - hr = CoCreateInstance(&BS_IID_SpVoice, NULL, CLSCTX_INPROC_SERVER, &BS_IID_IDispatch, (void**)&instance->voice); - if (FAILED(hr)) - { - CoUninitialize(); - return 0; - } - - hr = CoCreateInstance(&BS_IID_SpMemoryStream, NULL, CLSCTX_INPROC_SERVER, &BS_IID_IDispatch, (void**)&stream); - if (FAILED(hr)) - { - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - - for (puArgErr = 0; puArgErr < 8; ++puArgErr) - { - hr = instance->voice->lpVtbl->GetIDsOfNames(instance->voice, &BS_IID_null, &voice_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &instance->voice_dispids[puArgErr]); - if (FAILED(hr)) - { - stream->lpVtbl->Release(stream); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - } - - for (puArgErr = 0; puArgErr < 3; ++puArgErr) - { - hr = stream->lpVtbl->GetIDsOfNames(stream, &BS_IID_null, &memory_stream_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &instance->memory_stream_dispids[puArgErr]); - if (FAILED(hr)) - { - stream->lpVtbl->Release(stream); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - } - - stream->lpVtbl->Release(stream); - - parameters.rgvarg = NULL; - parameters.cArgs = 0; - parameters.rgdispidNamedArgs = NULL; - parameters.cNamedArgs = 0; - - hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[6], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - - if (return_value.vt != VT_DISPATCH) - { - VariantClear(&return_value); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - - instance->default_voice_token = return_value.pdispVal; - for (puArgErr = 0; puArgErr < 2; ++puArgErr) - { - hr = instance->default_voice_token->lpVtbl->GetIDsOfNames(instance->default_voice_token, &BS_IID_null, &voice_token_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &instance->voice_token_dispids[puArgErr]); - if (FAILED(hr)) - { - instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - } - - hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[2], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - - if (return_value.vt != VT_DISPATCH) - { - VariantClear(&return_value); - instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - - instance->voices = return_value.pdispVal; - - for (puArgErr = 0; puArgErr < 2; ++puArgErr) - { - hr = instance->voices->lpVtbl->GetIDsOfNames(instance->voices, &BS_IID_null, &voice_collection_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &voice_collection_dispids[puArgErr]); - if (FAILED(hr)) - { - instance->voices->lpVtbl->Release(instance->voices); - instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - } - - hr = instance->voices->lpVtbl->Invoke(instance->voices, voice_collection_dispids[0], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - instance->voices->lpVtbl->Release(instance->voices); - instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - if (return_value.vt == VT_I4) - { - voice_count = return_value.lVal; - } - else - { - VariantClear(&return_value); - } - if (voice_count <= 0) - { - instance->voices->lpVtbl->Release(instance->voices); - instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - instance->voice_count = (unsigned int)voice_count; - instance->voice_collection_item_dispid = voice_collection_dispids[1]; - instance->format = NULL; - instance->current_voice_token = NULL; - - if (blastspeak_get_stream_format(instance, 1, &instance->sample_rate, &instance->bits_per_sample, &instance->channels) == 0) - { - instance->voices->lpVtbl->Release(instance->voices); - instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); - instance->voice->lpVtbl->Release(instance->voice); - CoUninitialize(); - return 0; - } - - return 1; - } - - void blastspeak_destroy(blastspeak* instance) - { - if (instance->allocated_memory) - { - free(instance->allocated_memory); - } - instance->voice->lpVtbl->Release(instance->voice); - instance->voices->lpVtbl->Release(instance->voices); - instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); - instance->format->lpVtbl->Release(instance->format); - if (instance->current_voice_token) - { - instance->current_voice_token->lpVtbl->Release(instance->current_voice_token); - } - CoUninitialize(); - } - - static int blastspeak_speak_internal(blastspeak* instance, const char* text) - { - HRESULT hr; - VARIANT return_value; - VARIANT arguments[2]; - DISPPARAMS parameters; - UINT puArgErr; - WCHAR* utf16_string = blastspeak_get_wchar_from_utf8(instance, text, 0); - - if (utf16_string == NULL) - { - return 0; - } - - hr = InitVariantFromString(utf16_string, &arguments[1]); - if (FAILED(hr)) - { - return 0; - } - - InitVariantFromUInt32(0, &arguments[0]); - - parameters.rgvarg = arguments; - parameters.rgdispidNamedArgs = NULL; - parameters.cArgs = 2; - parameters.cNamedArgs = 0; - - hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[4], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); - VariantClear(&arguments[1]); - VariantClear(&return_value); - if (FAILED(hr)) - { - return 0; - } - return 1; - } - - int blastspeak_speak(blastspeak* instance, const char* text) - { - if (instance->must_reset_output) - { - HRESULT hr; - VARIANT return_value; - VARIANT argument; - DISPPARAMS parameters; - UINT puArgErr; - DISPID dispid_named = DISPID_PROPERTYPUT; - parameters.rgvarg = &argument; - parameters.cArgs = 1; - parameters.rgdispidNamedArgs = &dispid_named; - parameters.cNamedArgs = 1; - argument.vt = VT_EMPTY; - - hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - return 0; - } - VariantClear(&return_value); - instance->must_reset_output = 0; - } - - return blastspeak_speak_internal(instance, text); - } - - static IDispatch* blastspeak_get_voice(blastspeak* instance, unsigned int voice_index) - { - HRESULT hr; - DISPPARAMS parameters; - VARIANT argument; - VARIANT return_value; - UINT puArgErr; - - if (voice_index >= instance->voice_count) - { - return NULL; - } - - parameters.rgvarg = &argument; - parameters.cArgs = 1; - parameters.rgdispidNamedArgs = NULL; - parameters.cNamedArgs = 0; - - InitVariantFromUInt32(voice_index, &argument); - - hr = instance->voices->lpVtbl->Invoke(instance->voices, instance->voice_collection_item_dispid, &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - return NULL; - } - - if (return_value.vt != VT_DISPATCH) - { - VariantClear(&return_value); - return 0; - } - - return return_value.pdispVal; - } - - int blastspeak_set_voice(blastspeak* instance, unsigned int voice_index) - { - IDispatch* voice_token = blastspeak_get_voice(instance, voice_index); - HRESULT hr; - DISPPARAMS parameters; - VARIANT argument; - VARIANT return_value; - UINT puArgErr; - DISPID dispid_named = DISPID_PROPERTYPUT; - - if (voice_token == NULL) - { - return 0; - } - - parameters.rgvarg = &argument; - parameters.cArgs = 1; - parameters.rgdispidNamedArgs = &dispid_named; - parameters.cNamedArgs = 1; - - argument.vt = VT_DISPATCH; - argument.pdispVal = voice_token; - - hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[6], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - voice_token->lpVtbl->Release(voice_token); - return 0; - } - VariantClear(&return_value); - if (instance->current_voice_token) - { - instance->current_voice_token->lpVtbl->Release(instance->current_voice_token); - } - instance->current_voice_token = voice_token; - argument.vt = VT_EMPTY; - hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - return 0; - } - VariantClear(&return_value); - - if (blastspeak_get_stream_format(instance, 0, &instance->sample_rate, &instance->bits_per_sample, &instance->channels) == 0) - { - return 0; - } - - return 1; - } - - const char* blastspeak_get_voice_description(blastspeak* instance, unsigned int voice_index) - { - char* result; - IDispatch* voice_token = blastspeak_get_voice(instance, voice_index); - HRESULT hr; - DISPPARAMS parameters; - VARIANT return_value; - UINT puArgErr; - - if (voice_token == NULL) - { - return NULL; - } - - parameters.rgvarg = NULL; - parameters.cArgs = 0; - parameters.rgdispidNamedArgs = NULL; - parameters.cNamedArgs = 0; - - hr = voice_token->lpVtbl->Invoke(voice_token, instance->voice_token_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); - voice_token->lpVtbl->Release(voice_token); - if (FAILED(hr)) - { - return NULL; - } - - if (return_value.vt != VT_BSTR) - { - VariantClear(&return_value); - return NULL; - } - - result = blastspeak_get_UTF8_from_BSTR(instance, return_value.bstrVal); - VariantClear(&return_value); - return result; - } - - const char* blastspeak_get_voice_attribute(blastspeak* instance, unsigned int voice_index, const char* attribute) - { - char* result; - IDispatch* voice_token = blastspeak_get_voice(instance, voice_index); - HRESULT hr; - DISPPARAMS parameters; - VARIANT argument; - VARIANT return_value; - UINT puArgErr; - WCHAR* utf16_string; - - if (voice_token == NULL) - { - return NULL; - } - - utf16_string = blastspeak_get_wchar_from_utf8(instance, attribute, 0); - if (utf16_string == NULL) - { - voice_token->lpVtbl->Release(voice_token); - return NULL; - } - - parameters.rgvarg = &argument; - parameters.cArgs = 1; - parameters.rgdispidNamedArgs = NULL; - parameters.cNamedArgs = 0; - - hr = InitVariantFromString(utf16_string, &argument); - if (FAILED(hr)) - { - voice_token->lpVtbl->Release(voice_token); - return NULL; - } - - hr = voice_token->lpVtbl->Invoke(voice_token, instance->voice_token_dispids[0], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); - VariantClear(&argument); - voice_token->lpVtbl->Release(voice_token); - if (FAILED(hr)) - { - return NULL; - } - - if (return_value.vt != VT_BSTR) - { - VariantClear(&return_value); - return NULL; - } - - result = blastspeak_get_UTF8_from_BSTR(instance, return_value.bstrVal); - VariantClear(&return_value); - return result; - } - - const char* blastspeak_get_voice_languages(blastspeak* instance, unsigned int voice_index) - { - char* the_string = (char*)blastspeak_get_voice_attribute(instance, voice_index, "language"); - long codes[blastspeak_max_languages_per_voice]; - int languages = 0; - int i; - char current_name[9]; - - if (the_string == NULL) - { - return NULL; - } - - for (;; ) - { - int should_continue = 0; - codes[languages] = strtol(the_string, &the_string, 16); - if (codes[languages] == 0L) - { - return NULL; - } - else if (codes[languages] < -32768L || codes[languages] > 32767L) - { - return NULL; - } - for (i = 0; i < languages; ++i) - { - if (codes[i] == codes[languages]) - { - should_continue = 1; - break; - } - } - if (should_continue) - { - continue; - } - ++languages; - if (languages > blastspeak_max_languages_per_voice) - { - return NULL; - } - for (; *the_string; ++the_string) - { - if (*the_string == ' ' || *the_string == ';') - { - continue; - } - break; - } - if (*the_string == 0) - { - break; - } - } - - if (languages == 0) - { - return NULL; - } - - the_string = blastspeak_get_temporary_memory(instance, (languages * 20) + 1); - if (the_string == NULL) - { - return NULL; - } - the_string[0] = 0; - - for (i = 0; i < languages; ++i) - { - if (i) - { - strcat(the_string, " "); - } - current_name[0] = 0; - if (GetLocaleInfoA(codes[i], LOCALE_SISO639LANGNAME, current_name, 9) == 0) - { - return NULL; - } - strcat(the_string, current_name); - - if (SUBLANGID((short)codes[i]) == 0) /* Neutral. */ - { - continue; - } - - current_name[0] = 0; - if (GetLocaleInfoA(codes[i], LOCALE_SISO3166CTRYNAME, current_name, 9) == 0) - { - continue; - } - strcat(the_string, "-"); - strcat(the_string, current_name); - } - - return the_string; - } - - static int blastspeak_set_long_property(blastspeak* instance, DISPID dispid, long value) - { - HRESULT hr; - DISPPARAMS parameters; - VARIANT argument; - VARIANT return_value; - UINT puArgErr; - DISPID dispid_named = DISPID_PROPERTYPUT; - - parameters.rgvarg = &argument; - parameters.cArgs = 1; - parameters.rgdispidNamedArgs = &dispid_named; - parameters.cNamedArgs = 1; - argument.vt = VT_I4; - argument.lVal = value; - - hr = instance->voice->lpVtbl->Invoke(instance->voice, dispid, &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - return 0; - } - VariantClear(&return_value); - - return 1; - } - - static int blastspeak_get_long_property(blastspeak* instance, DISPID dispid, long* value, IDispatch* object) - { - HRESULT hr; - DISPPARAMS parameters; - VARIANT return_value; - UINT puArgErr; - - if (object == NULL) - { - object = instance->voice; - } - - parameters.rgvarg = NULL; - parameters.cArgs = 0; - parameters.rgdispidNamedArgs = NULL; - parameters.cNamedArgs = 0; - - hr = object->lpVtbl->Invoke(object, dispid, &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - return 0; - } - if (return_value.vt != VT_I4 && return_value.vt != VT_I2) - { - VariantClear(&return_value); - return 0; - } - - if (return_value.vt == VT_I4) - { - *value = return_value.lVal; - } - else - { - *value = return_value.iVal; - } - - return 1; - } - - static int blastspeak_get_stream_format(blastspeak* instance, int retrieve_dispids, unsigned long* sample_rate, unsigned char* bits_per_sample, unsigned char* channels) - { - OLECHAR* audio_format_getwaveformatex_name = L"GetWaveFormatEx"; - OLECHAR* waveformatex_names[] = { L"BitsPerSample", L"Channels", L"FormatTag", L"SamplesPerSec" }; - IDispatch* audio_device_stream; - IDispatch* formatex = NULL; - HRESULT hr; - DISPPARAMS parameters; - VARIANT return_value; - UINT puArgErr; - long temp; - - parameters.rgvarg = NULL; - parameters.cArgs = 0; - parameters.rgdispidNamedArgs = NULL; - parameters.cNamedArgs = 0; - - if (instance->format) - { - instance->format->lpVtbl->Release(instance->format); - instance->format = NULL; - } - - hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - return 0; - } - if (return_value.vt != VT_DISPATCH) - { - VariantClear(&return_value); - return 0; - } - audio_device_stream = return_value.pdispVal; - - hr = audio_device_stream->lpVtbl->Invoke(audio_device_stream, instance->memory_stream_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); - audio_device_stream->lpVtbl->Release(audio_device_stream); - if (FAILED(hr)) - { - return 0; - } - if (return_value.vt != VT_DISPATCH) - { - VariantClear(&return_value); - return 0; - } - instance->format = return_value.pdispVal; - - if (retrieve_dispids) - { - hr = instance->format->lpVtbl->GetIDsOfNames(instance->format, &BS_IID_null, &audio_format_getwaveformatex_name, 1, LOCALE_SYSTEM_DEFAULT, &instance->audio_format_getwaveformatex_dispid); - if (FAILED(hr)) - { - goto error; - } - } - - hr = instance->format->lpVtbl->Invoke(instance->format, instance->audio_format_getwaveformatex_dispid, &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - goto error; - } - if (return_value.vt != VT_DISPATCH) - { - VariantClear(&return_value); - goto error; - } - formatex = return_value.pdispVal; - - if (retrieve_dispids) - { - for (puArgErr = 0; puArgErr < 4; ++puArgErr) - { - hr = formatex->lpVtbl->GetIDsOfNames(formatex, &BS_IID_null, &waveformatex_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &instance->waveformatex_dispids[puArgErr]); - if (FAILED(hr)) - { - goto error; - } - } - - } - - if (blastspeak_get_long_property(instance, instance->waveformatex_dispids[2], &temp, formatex) == 0) - { - goto error; - } - if (temp != 1) - { - goto error; - } - - if (blastspeak_get_long_property(instance, instance->waveformatex_dispids[0], &temp, formatex) == 0) - { - goto error; - } - if (temp != 8 && temp != 16) - { - goto error; - } - *bits_per_sample = (unsigned char)temp; - - if (blastspeak_get_long_property(instance, instance->waveformatex_dispids[1], &temp, formatex) == 0) - { - goto error; - } - if (temp != 1 && temp != 2) - { - goto error; - } - *channels = (unsigned char)temp; - - if (blastspeak_get_long_property(instance, instance->waveformatex_dispids[3], &temp, formatex) == 0) - { - goto error; - } - if (temp >= 8000 && temp <= 192000) - { - *sample_rate = (unsigned long)temp; - } - else - { - goto error; - } - - if (formatex) - { - formatex->lpVtbl->Release(formatex); - } - return 1; - - error: - if (formatex) - { - formatex->lpVtbl->Release(formatex); - } - return 0; - } - - int blastspeak_get_voice_rate(blastspeak* instance, long* result) - { - if (result == NULL) - { - return 0; - } - return blastspeak_get_long_property(instance, instance->voice_dispids[3], result, NULL); - } - - int blastspeak_set_voice_rate(blastspeak* instance, long value) - { - if (value < -10) - { - return 0; - } - if (value > 10) - { - return 0; - } - return blastspeak_set_long_property(instance, instance->voice_dispids[3], value); - } - - int blastspeak_get_voice_volume(blastspeak* instance, long* result) - { - if (result == NULL) - { - return 0; - } - return blastspeak_get_long_property(instance, instance->voice_dispids[7], result, NULL); - } - - int blastspeak_set_voice_volume(blastspeak* instance, long value) - { - if (value < -100) - { - return 0; - } - if (value > 100) - { - return 0; - } - return blastspeak_set_long_property(instance, instance->voice_dispids[7], value); - } - - char* blastspeak_speak_to_memory(blastspeak* instance, unsigned long* bytes, const char* text) - { - int temp; - HRESULT hr; - DISPPARAMS parameters; - VARIANT argument; - VARIANT return_value; - UINT puArgErr; - char* ptr; - LONGLONG elements; - char* data; - IDispatch* stream; - DISPID dispid_named = DISPID_PROPERTYPUT; - - if (instance->format == NULL) - { - return NULL; - } - - hr = CoCreateInstance(&BS_IID_SpMemoryStream, NULL, CLSCTX_INPROC_SERVER, &BS_IID_IDispatch, (void**)&stream); - if (FAILED(hr)) - { - return NULL; - } - - parameters.rgvarg = &argument; - parameters.cArgs = 1; - parameters.rgdispidNamedArgs = &dispid_named; - parameters.cNamedArgs = 1; - argument.vt = VT_DISPATCH; - argument.pdispVal = instance->format; - - hr = stream->lpVtbl->Invoke(stream, instance->memory_stream_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - stream->lpVtbl->Release(stream); - return 0; - } - VariantClear(&return_value); - - argument.vt = VT_DISPATCH; - argument.pdispVal = stream; - - hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); - if (FAILED(hr)) - { - stream->lpVtbl->Release(stream); - return 0; - } - VariantClear(&return_value); - instance->must_reset_output = 1; - - temp = blastspeak_speak_internal(instance, text); - if (temp == 0) - { - stream->lpVtbl->Release(stream); - return NULL; - } - - parameters.rgvarg = NULL; - parameters.cArgs = 0; - parameters.rgdispidNamedArgs = NULL; - parameters.cNamedArgs = 0; - - hr = stream->lpVtbl->Invoke(stream, instance->memory_stream_dispids[0], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); - stream->lpVtbl->Release(stream); - if (FAILED(hr)) - { - return NULL; - } - if (return_value.vt != 8209) - { - VariantClear(&return_value); - return NULL; - } - - ptr = (char*)return_value.parray->pvData; - elements = return_value.parray->rgsabound[0].cElements; - ptr += return_value.parray->rgsabound[0].lLbound; - data = blastspeak_get_temporary_memory(instance, (unsigned int)elements); - if (data) - { - memcpy(data, ptr, (size_t)elements); - *bytes = (unsigned long)elements; - } - else - { - *bytes = 0; - } - VariantClear(&return_value); - - return data; - } - -#ifdef __cplusplus -} -#endif +/* +Blastspeak text to speech library +Copyright (c) 2019-2020 Philip Bennefall + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "blastspeak.h" +#include +#include +#include +#include +#include +#include + + const IID BS_IID_null = { 0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} }; + const IID BS_IID_IDispatch = { 0x00020400, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46} }; + const IID BS_IID_SpVoice = { 0x96749377, 0x3391, 0x11D2, {0x9E, 0xE3, 0x00, 0xC0, 0x4F, 0x79, 0x73, 0x96} }; + const IID BS_IID_SpMemoryStream = { 0x5FB7EF7D, 0xDFF4, 0x468a, {0xB6, 0xB7, 0x2F, 0xCB, 0xD1, 0x88, 0xF9, 0x94} }; + +#if 0 + static void print_error(HRESULT hr, UINT puArgErr) + { + switch (hr) + { + case DISP_E_BADPARAMCOUNT: + printf("The number of elements provided to DISPPARAMS is different from the number of arguments accepted by the method or property."); + break; + case DISP_E_BADVARTYPE: + printf("One of the arguments in DISPPARAMS is not a valid variant type."); + break; + case DISP_E_EXCEPTION: + printf("The application needs to raise an exception."); + break; + case DISP_E_MEMBERNOTFOUND: + printf("The requested member does not exist."); + break; + case DISP_E_NONAMEDARGS: + printf("This implementation of IDispatch does not support named arguments."); + break; + case DISP_E_OVERFLOW: + printf("One of the arguments in DISPPARAMS could not be coerced to the specified type."); + break; + case DISP_E_PARAMNOTFOUND: + printf("One of the parameter IDs does not correspond to a parameter on the method."); + break; + case DISP_E_TYPEMISMATCH: + printf("One or more of the arguments could not be coerced."); + break; + case DISP_E_UNKNOWNINTERFACE: + printf("The interface identifier passed in riid is not IID_NULL."); + break; + case DISP_E_UNKNOWNLCID: + printf("The member being invoked interprets string arguments according to the LCID, and the LCID is not recognized."); + break; + case DISP_E_PARAMNOTOPTIONAL: + printf("A required parameter was omitted."); + break; + default: + printf("Unknown error"); + } + printf("\nArgument error: %u\n", puArgErr); + } +#endif + + HRESULT InitVariantFromString(__in PCWSTR psz, __out VARIANT* pvar) + { + pvar->vt = VT_BSTR; + pvar->bstrVal = SysAllocString(psz); + HRESULT hr = pvar->bstrVal ? S_OK : (psz ? E_OUTOFMEMORY : E_INVALIDARG); + if (FAILED(hr)) + { + VariantInit(pvar); + } + return hr; + } + + HRESULT InitVariantFromUInt32(__in ULONG ulVal, __out VARIANT* pvar) + { + pvar->vt = VT_UI4; + pvar->ulVal = ulVal; + return S_OK; + } + + static char* blastspeak_get_temporary_memory(blastspeak* instance, unsigned int bytes) + { + if (bytes <= blastspeak_static_memory_length) + { + return instance->static_memory; + } + if (instance->allocated_memory && bytes <= instance->allocated_memory_length) + { + return instance->allocated_memory; + } + instance->allocated_memory = (char*)realloc(instance->allocated_memory, (size_t)bytes); + if (instance->allocated_memory == NULL) + { + instance->allocated_memory_length = 0; + return NULL; + } + instance->allocated_memory_length = bytes; + return instance->allocated_memory; + } + + /* If length_in_bytes is nonzero, it must be the length of the string excluding the NULL terminator. */ + /* If length_in_bytes is 0, the function looks for the NULL terminator to figure out the length. */ + /* The input string must always be NULL terminated. */ + /* The returned string is always NULL terminated. The function returns a NULL pointer on failure. */ + static WCHAR* blastspeak_get_wchar_from_utf8(blastspeak* instance, const char* the_string, unsigned int length_in_bytes) + { + WCHAR* result; + int needed_size; + + if (length_in_bytes == 0) + { + length_in_bytes = strlen(the_string); + } + if (length_in_bytes == 0) + { + return NULL; + } + ++length_in_bytes; + needed_size = MultiByteToWideChar(CP_UTF8, 0, the_string, length_in_bytes, NULL, 0); + if (needed_size == 0) + { + return NULL; + } + result = (WCHAR*)blastspeak_get_temporary_memory(instance, needed_size * sizeof(WCHAR)); + if (result == NULL) + { + return NULL; + } + if (MultiByteToWideChar(CP_UTF8, 0, the_string, length_in_bytes, result, needed_size) != needed_size) + { + return NULL; + } + return result; + } + + /* The returned string is always NULL terminated. The function returns a NULL pointer on failure. */ + /* The input BSTR is not freed. */ + static char* blastspeak_get_UTF8_from_BSTR(blastspeak* instance, BSTR the_string) + { + char* result; + int needed_size; + unsigned int length_in_chars = SysStringLen(the_string); + if (length_in_chars == 0) + { + return NULL; + } + + ++length_in_chars; + needed_size = WideCharToMultiByte(CP_UTF8, 0, the_string, length_in_chars, NULL, 0, NULL, NULL); + if (needed_size == 0) + { + return NULL; + } + result = blastspeak_get_temporary_memory(instance, needed_size); + if (result == NULL) + { + return NULL; + } + if (WideCharToMultiByte(CP_UTF8, 0, the_string, length_in_chars, result, needed_size, NULL, NULL) != needed_size) + { + return NULL; + } + return result; + } + + static int blastspeak_get_stream_format(blastspeak* instance, int retrieve_dispids, unsigned long* sample_rate, unsigned char* bits_per_sample, unsigned char* channels); + + int blastspeak_initialize(blastspeak* instance) + { + OLECHAR* voice_names[] = { L"AllowAudioOutputFormatChangesOnNextSet", L"AudioOutputStream", L"GetVoices", L"Rate", L"Speak", L"Status", L"Voice", L"Volume" }; + OLECHAR* voice_token_names[] = { L"GetAttribute", L"GetDescription" }; + OLECHAR* voice_collection_names[] = { L"Count", L"Item" }; + OLECHAR* memory_stream_names[] = { L"GetData", L"Format", L"SetData" }; + IDispatch* stream; + HRESULT hr; + DISPPARAMS parameters; + VARIANT return_value; + UINT puArgErr; + DISPID voice_collection_dispids[2]; + LONG voice_count = 0; + + instance->allocated_memory = NULL; + instance->allocated_memory_length = 0; + instance->static_memory[0] = 0; + instance->must_reset_output = 0; + + CoInitializeEx(NULL, COINIT_MULTITHREADED); + + hr = CoCreateInstance(&BS_IID_SpVoice, NULL, CLSCTX_INPROC_SERVER, &BS_IID_IDispatch, (void**)&instance->voice); + if (FAILED(hr)) + { + CoUninitialize(); + return 0; + } + + hr = CoCreateInstance(&BS_IID_SpMemoryStream, NULL, CLSCTX_INPROC_SERVER, &BS_IID_IDispatch, (void**)&stream); + if (FAILED(hr)) + { + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + + for (puArgErr = 0; puArgErr < 8; ++puArgErr) + { + hr = instance->voice->lpVtbl->GetIDsOfNames(instance->voice, &BS_IID_null, &voice_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &instance->voice_dispids[puArgErr]); + if (FAILED(hr)) + { + stream->lpVtbl->Release(stream); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + } + + for (puArgErr = 0; puArgErr < 3; ++puArgErr) + { + hr = stream->lpVtbl->GetIDsOfNames(stream, &BS_IID_null, &memory_stream_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &instance->memory_stream_dispids[puArgErr]); + if (FAILED(hr)) + { + stream->lpVtbl->Release(stream); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + } + + stream->lpVtbl->Release(stream); + + parameters.rgvarg = NULL; + parameters.cArgs = 0; + parameters.rgdispidNamedArgs = NULL; + parameters.cNamedArgs = 0; + + hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[6], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + + if (return_value.vt != VT_DISPATCH) + { + VariantClear(&return_value); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + + instance->default_voice_token = return_value.pdispVal; + for (puArgErr = 0; puArgErr < 2; ++puArgErr) + { + hr = instance->default_voice_token->lpVtbl->GetIDsOfNames(instance->default_voice_token, &BS_IID_null, &voice_token_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &instance->voice_token_dispids[puArgErr]); + if (FAILED(hr)) + { + instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + } + + hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[2], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + + if (return_value.vt != VT_DISPATCH) + { + VariantClear(&return_value); + instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + + instance->voices = return_value.pdispVal; + + for (puArgErr = 0; puArgErr < 2; ++puArgErr) + { + hr = instance->voices->lpVtbl->GetIDsOfNames(instance->voices, &BS_IID_null, &voice_collection_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &voice_collection_dispids[puArgErr]); + if (FAILED(hr)) + { + instance->voices->lpVtbl->Release(instance->voices); + instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + } + + hr = instance->voices->lpVtbl->Invoke(instance->voices, voice_collection_dispids[0], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + instance->voices->lpVtbl->Release(instance->voices); + instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + if (return_value.vt == VT_I4) + { + voice_count = return_value.lVal; + } + else + { + VariantClear(&return_value); + } + if (voice_count <= 0) + { + instance->voices->lpVtbl->Release(instance->voices); + instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + instance->voice_count = (unsigned int)voice_count; + instance->voice_collection_item_dispid = voice_collection_dispids[1]; + instance->format = NULL; + instance->current_voice_token = NULL; + + if (blastspeak_get_stream_format(instance, 1, &instance->sample_rate, &instance->bits_per_sample, &instance->channels) == 0) + { + instance->voices->lpVtbl->Release(instance->voices); + instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); + instance->voice->lpVtbl->Release(instance->voice); + CoUninitialize(); + return 0; + } + + return 1; + } + + void blastspeak_destroy(blastspeak* instance) + { + if (instance->allocated_memory) + { + free(instance->allocated_memory); + } + instance->voice->lpVtbl->Release(instance->voice); + instance->voices->lpVtbl->Release(instance->voices); + instance->default_voice_token->lpVtbl->Release(instance->default_voice_token); + instance->format->lpVtbl->Release(instance->format); + if (instance->current_voice_token) + { + instance->current_voice_token->lpVtbl->Release(instance->current_voice_token); + } + CoUninitialize(); + } + + static int blastspeak_speak_internal(blastspeak* instance, const char* text) + { + HRESULT hr; + VARIANT return_value; + VARIANT arguments[2]; + DISPPARAMS parameters; + UINT puArgErr; + WCHAR* utf16_string = blastspeak_get_wchar_from_utf8(instance, text, 0); + + if (utf16_string == NULL) + { + return 0; + } + + hr = InitVariantFromString(utf16_string, &arguments[1]); + if (FAILED(hr)) + { + return 0; + } + + InitVariantFromUInt32(0, &arguments[0]); + + parameters.rgvarg = arguments; + parameters.rgdispidNamedArgs = NULL; + parameters.cArgs = 2; + parameters.cNamedArgs = 0; + + hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[4], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); + VariantClear(&arguments[1]); + VariantClear(&return_value); + if (FAILED(hr)) + { + return 0; + } + return 1; + } + + int blastspeak_speak(blastspeak* instance, const char* text) + { + if (instance->must_reset_output) + { + HRESULT hr; + VARIANT return_value; + VARIANT argument; + DISPPARAMS parameters; + UINT puArgErr; + DISPID dispid_named = DISPID_PROPERTYPUT; + parameters.rgvarg = &argument; + parameters.cArgs = 1; + parameters.rgdispidNamedArgs = &dispid_named; + parameters.cNamedArgs = 1; + argument.vt = VT_EMPTY; + + hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + return 0; + } + VariantClear(&return_value); + instance->must_reset_output = 0; + } + + return blastspeak_speak_internal(instance, text); + } + + static IDispatch* blastspeak_get_voice(blastspeak* instance, unsigned int voice_index) + { + HRESULT hr; + DISPPARAMS parameters; + VARIANT argument; + VARIANT return_value; + UINT puArgErr; + + if (voice_index >= instance->voice_count) + { + return NULL; + } + + parameters.rgvarg = &argument; + parameters.cArgs = 1; + parameters.rgdispidNamedArgs = NULL; + parameters.cNamedArgs = 0; + + InitVariantFromUInt32(voice_index, &argument); + + hr = instance->voices->lpVtbl->Invoke(instance->voices, instance->voice_collection_item_dispid, &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + return NULL; + } + + if (return_value.vt != VT_DISPATCH) + { + VariantClear(&return_value); + return 0; + } + + return return_value.pdispVal; + } + + int blastspeak_set_voice(blastspeak* instance, unsigned int voice_index) + { + IDispatch* voice_token = blastspeak_get_voice(instance, voice_index); + HRESULT hr; + DISPPARAMS parameters; + VARIANT argument; + VARIANT return_value; + UINT puArgErr; + DISPID dispid_named = DISPID_PROPERTYPUT; + + if (voice_token == NULL) + { + return 0; + } + + parameters.rgvarg = &argument; + parameters.cArgs = 1; + parameters.rgdispidNamedArgs = &dispid_named; + parameters.cNamedArgs = 1; + + argument.vt = VT_DISPATCH; + argument.pdispVal = voice_token; + + hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[6], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + voice_token->lpVtbl->Release(voice_token); + return 0; + } + VariantClear(&return_value); + if (instance->current_voice_token) + { + instance->current_voice_token->lpVtbl->Release(instance->current_voice_token); + } + instance->current_voice_token = voice_token; + argument.vt = VT_EMPTY; + hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + return 0; + } + VariantClear(&return_value); + + if (blastspeak_get_stream_format(instance, 0, &instance->sample_rate, &instance->bits_per_sample, &instance->channels) == 0) + { + return 0; + } + + return 1; + } + + const char* blastspeak_get_voice_description(blastspeak* instance, unsigned int voice_index) + { + char* result; + IDispatch* voice_token = blastspeak_get_voice(instance, voice_index); + HRESULT hr; + DISPPARAMS parameters; + VARIANT return_value; + UINT puArgErr; + + if (voice_token == NULL) + { + return NULL; + } + + parameters.rgvarg = NULL; + parameters.cArgs = 0; + parameters.rgdispidNamedArgs = NULL; + parameters.cNamedArgs = 0; + + hr = voice_token->lpVtbl->Invoke(voice_token, instance->voice_token_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); + voice_token->lpVtbl->Release(voice_token); + if (FAILED(hr)) + { + return NULL; + } + + if (return_value.vt != VT_BSTR) + { + VariantClear(&return_value); + return NULL; + } + + result = blastspeak_get_UTF8_from_BSTR(instance, return_value.bstrVal); + VariantClear(&return_value); + return result; + } + + const char* blastspeak_get_voice_attribute(blastspeak* instance, unsigned int voice_index, const char* attribute) + { + char* result; + IDispatch* voice_token = blastspeak_get_voice(instance, voice_index); + HRESULT hr; + DISPPARAMS parameters; + VARIANT argument; + VARIANT return_value; + UINT puArgErr; + WCHAR* utf16_string; + + if (voice_token == NULL) + { + return NULL; + } + + utf16_string = blastspeak_get_wchar_from_utf8(instance, attribute, 0); + if (utf16_string == NULL) + { + voice_token->lpVtbl->Release(voice_token); + return NULL; + } + + parameters.rgvarg = &argument; + parameters.cArgs = 1; + parameters.rgdispidNamedArgs = NULL; + parameters.cNamedArgs = 0; + + hr = InitVariantFromString(utf16_string, &argument); + if (FAILED(hr)) + { + voice_token->lpVtbl->Release(voice_token); + return NULL; + } + + hr = voice_token->lpVtbl->Invoke(voice_token, instance->voice_token_dispids[0], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); + VariantClear(&argument); + voice_token->lpVtbl->Release(voice_token); + if (FAILED(hr)) + { + return NULL; + } + + if (return_value.vt != VT_BSTR) + { + VariantClear(&return_value); + return NULL; + } + + result = blastspeak_get_UTF8_from_BSTR(instance, return_value.bstrVal); + VariantClear(&return_value); + return result; + } + + const char* blastspeak_get_voice_languages(blastspeak* instance, unsigned int voice_index) + { + char* the_string = (char*)blastspeak_get_voice_attribute(instance, voice_index, "language"); + long codes[blastspeak_max_languages_per_voice]; + int languages = 0; + int i; + char current_name[9]; + + if (the_string == NULL) + { + return NULL; + } + + for (;; ) + { + int should_continue = 0; + codes[languages] = strtol(the_string, &the_string, 16); + if (codes[languages] == 0L) + { + return NULL; + } + else if (codes[languages] < -32768L || codes[languages] > 32767L) + { + return NULL; + } + for (i = 0; i < languages; ++i) + { + if (codes[i] == codes[languages]) + { + should_continue = 1; + break; + } + } + if (should_continue) + { + continue; + } + ++languages; + if (languages > blastspeak_max_languages_per_voice) + { + return NULL; + } + for (; *the_string; ++the_string) + { + if (*the_string == ' ' || *the_string == ';') + { + continue; + } + break; + } + if (*the_string == 0) + { + break; + } + } + + if (languages == 0) + { + return NULL; + } + + the_string = blastspeak_get_temporary_memory(instance, (languages * 20) + 1); + if (the_string == NULL) + { + return NULL; + } + the_string[0] = 0; + + for (i = 0; i < languages; ++i) + { + if (i) + { + strcat(the_string, " "); + } + current_name[0] = 0; + if (GetLocaleInfoA(codes[i], LOCALE_SISO639LANGNAME, current_name, 9) == 0) + { + return NULL; + } + strcat(the_string, current_name); + + if (SUBLANGID((short)codes[i]) == 0) /* Neutral. */ + { + continue; + } + + current_name[0] = 0; + if (GetLocaleInfoA(codes[i], LOCALE_SISO3166CTRYNAME, current_name, 9) == 0) + { + continue; + } + strcat(the_string, "-"); + strcat(the_string, current_name); + } + + return the_string; + } + + static int blastspeak_set_long_property(blastspeak* instance, DISPID dispid, long value) + { + HRESULT hr; + DISPPARAMS parameters; + VARIANT argument; + VARIANT return_value; + UINT puArgErr; + DISPID dispid_named = DISPID_PROPERTYPUT; + + parameters.rgvarg = &argument; + parameters.cArgs = 1; + parameters.rgdispidNamedArgs = &dispid_named; + parameters.cNamedArgs = 1; + argument.vt = VT_I4; + argument.lVal = value; + + hr = instance->voice->lpVtbl->Invoke(instance->voice, dispid, &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + return 0; + } + VariantClear(&return_value); + + return 1; + } + + static int blastspeak_get_long_property(blastspeak* instance, DISPID dispid, long* value, IDispatch* object) + { + HRESULT hr; + DISPPARAMS parameters; + VARIANT return_value; + UINT puArgErr; + + if (object == NULL) + { + object = instance->voice; + } + + parameters.rgvarg = NULL; + parameters.cArgs = 0; + parameters.rgdispidNamedArgs = NULL; + parameters.cNamedArgs = 0; + + hr = object->lpVtbl->Invoke(object, dispid, &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + return 0; + } + if (return_value.vt != VT_I4 && return_value.vt != VT_I2) + { + VariantClear(&return_value); + return 0; + } + + if (return_value.vt == VT_I4) + { + *value = return_value.lVal; + } + else + { + *value = return_value.iVal; + } + + return 1; + } + + static int blastspeak_get_stream_format(blastspeak* instance, int retrieve_dispids, unsigned long* sample_rate, unsigned char* bits_per_sample, unsigned char* channels) + { + OLECHAR* audio_format_getwaveformatex_name = L"GetWaveFormatEx"; + OLECHAR* waveformatex_names[] = { L"BitsPerSample", L"Channels", L"FormatTag", L"SamplesPerSec" }; + IDispatch* audio_device_stream; + IDispatch* formatex = NULL; + HRESULT hr; + DISPPARAMS parameters; + VARIANT return_value; + UINT puArgErr; + long temp; + + parameters.rgvarg = NULL; + parameters.cArgs = 0; + parameters.rgdispidNamedArgs = NULL; + parameters.cNamedArgs = 0; + + if (instance->format) + { + instance->format->lpVtbl->Release(instance->format); + instance->format = NULL; + } + + hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + return 0; + } + if (return_value.vt != VT_DISPATCH) + { + VariantClear(&return_value); + return 0; + } + audio_device_stream = return_value.pdispVal; + + hr = audio_device_stream->lpVtbl->Invoke(audio_device_stream, instance->memory_stream_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶meters, &return_value, NULL, &puArgErr); + audio_device_stream->lpVtbl->Release(audio_device_stream); + if (FAILED(hr)) + { + return 0; + } + if (return_value.vt != VT_DISPATCH) + { + VariantClear(&return_value); + return 0; + } + instance->format = return_value.pdispVal; + + if (retrieve_dispids) + { + hr = instance->format->lpVtbl->GetIDsOfNames(instance->format, &BS_IID_null, &audio_format_getwaveformatex_name, 1, LOCALE_SYSTEM_DEFAULT, &instance->audio_format_getwaveformatex_dispid); + if (FAILED(hr)) + { + goto error; + } + } + + hr = instance->format->lpVtbl->Invoke(instance->format, instance->audio_format_getwaveformatex_dispid, &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + goto error; + } + if (return_value.vt != VT_DISPATCH) + { + VariantClear(&return_value); + goto error; + } + formatex = return_value.pdispVal; + + if (retrieve_dispids) + { + for (puArgErr = 0; puArgErr < 4; ++puArgErr) + { + hr = formatex->lpVtbl->GetIDsOfNames(formatex, &BS_IID_null, &waveformatex_names[puArgErr], 1, LOCALE_SYSTEM_DEFAULT, &instance->waveformatex_dispids[puArgErr]); + if (FAILED(hr)) + { + goto error; + } + } + + } + + if (blastspeak_get_long_property(instance, instance->waveformatex_dispids[2], &temp, formatex) == 0) + { + goto error; + } + if (temp != 1) + { + goto error; + } + + if (blastspeak_get_long_property(instance, instance->waveformatex_dispids[0], &temp, formatex) == 0) + { + goto error; + } + if (temp != 8 && temp != 16) + { + goto error; + } + *bits_per_sample = (unsigned char)temp; + + if (blastspeak_get_long_property(instance, instance->waveformatex_dispids[1], &temp, formatex) == 0) + { + goto error; + } + if (temp != 1 && temp != 2) + { + goto error; + } + *channels = (unsigned char)temp; + + if (blastspeak_get_long_property(instance, instance->waveformatex_dispids[3], &temp, formatex) == 0) + { + goto error; + } + if (temp >= 8000 && temp <= 192000) + { + *sample_rate = (unsigned long)temp; + } + else + { + goto error; + } + + if (formatex) + { + formatex->lpVtbl->Release(formatex); + } + return 1; + + error: + if (formatex) + { + formatex->lpVtbl->Release(formatex); + } + return 0; + } + + int blastspeak_get_voice_rate(blastspeak* instance, long* result) + { + if (result == NULL) + { + return 0; + } + return blastspeak_get_long_property(instance, instance->voice_dispids[3], result, NULL); + } + + int blastspeak_set_voice_rate(blastspeak* instance, long value) + { + if (value < -10) + { + return 0; + } + if (value > 10) + { + return 0; + } + return blastspeak_set_long_property(instance, instance->voice_dispids[3], value); + } + + int blastspeak_get_voice_volume(blastspeak* instance, long* result) + { + if (result == NULL) + { + return 0; + } + return blastspeak_get_long_property(instance, instance->voice_dispids[7], result, NULL); + } + + int blastspeak_set_voice_volume(blastspeak* instance, long value) + { + if (value < -100) + { + return 0; + } + if (value > 100) + { + return 0; + } + return blastspeak_set_long_property(instance, instance->voice_dispids[7], value); + } + + char* blastspeak_speak_to_memory(blastspeak* instance, unsigned long* bytes, const char* text) + { + int temp; + HRESULT hr; + DISPPARAMS parameters; + VARIANT argument; + VARIANT return_value; + UINT puArgErr; + char* ptr; + LONGLONG elements; + char* data; + IDispatch* stream; + DISPID dispid_named = DISPID_PROPERTYPUT; + + if (instance->format == NULL) + { + return NULL; + } + + hr = CoCreateInstance(&BS_IID_SpMemoryStream, NULL, CLSCTX_INPROC_SERVER, &BS_IID_IDispatch, (void**)&stream); + if (FAILED(hr)) + { + return NULL; + } + + parameters.rgvarg = &argument; + parameters.cArgs = 1; + parameters.rgdispidNamedArgs = &dispid_named; + parameters.cNamedArgs = 1; + argument.vt = VT_DISPATCH; + argument.pdispVal = instance->format; + + hr = stream->lpVtbl->Invoke(stream, instance->memory_stream_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + stream->lpVtbl->Release(stream); + return 0; + } + VariantClear(&return_value); + + argument.vt = VT_DISPATCH; + argument.pdispVal = stream; + + hr = instance->voice->lpVtbl->Invoke(instance->voice, instance->voice_dispids[1], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, ¶meters, &return_value, NULL, &puArgErr); + if (FAILED(hr)) + { + stream->lpVtbl->Release(stream); + return 0; + } + VariantClear(&return_value); + instance->must_reset_output = 1; + + temp = blastspeak_speak_internal(instance, text); + if (temp == 0) + { + stream->lpVtbl->Release(stream); + return NULL; + } + + parameters.rgvarg = NULL; + parameters.cArgs = 0; + parameters.rgdispidNamedArgs = NULL; + parameters.cNamedArgs = 0; + + hr = stream->lpVtbl->Invoke(stream, instance->memory_stream_dispids[0], &BS_IID_null, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, ¶meters, &return_value, NULL, &puArgErr); + stream->lpVtbl->Release(stream); + if (FAILED(hr)) + { + return NULL; + } + if (return_value.vt != 8209) + { + VariantClear(&return_value); + return NULL; + } + + ptr = (char*)return_value.parray->pvData; + elements = return_value.parray->rgsabound[0].cElements; + ptr += return_value.parray->rgsabound[0].lLbound; + data = blastspeak_get_temporary_memory(instance, (unsigned int)elements); + if (data) + { + memcpy(data, ptr, (size_t)elements); + *bytes = (unsigned long)elements; + } + else + { + *bytes = 0; + } + VariantClear(&return_value); + + return data; + } + +#ifdef __cplusplus +} +#endif diff --git a/Dep/blastspeak.h b/Dep/blastspeak.h index 3255629..934bcf9 100644 --- a/Dep/blastspeak.h +++ b/Dep/blastspeak.h @@ -1,94 +1,94 @@ -/* -Blastspeak text to speech library -Copyright (c) 2019-2020 Philip Bennefall - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - - 3. This notice may not be removed or altered from any source - distribution. -*/ - -#ifndef BLASTSPEAK_H -#define BLASTSPEAK_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -#ifndef blastspeak_static_memory_length -#define blastspeak_static_memory_length 64 /* In bytes. */ -#endif - -#ifndef blastspeak_max_languages_per_voice -#define blastspeak_max_languages_per_voice 4 -#endif - - typedef struct blastspeak - { - char static_memory[blastspeak_static_memory_length]; - char* allocated_memory; - IDispatch* voice; - IDispatch* format; - IDispatch* voices; - IDispatch* default_voice_token; - IDispatch* current_voice_token; - unsigned int voice_count; - unsigned int allocated_memory_length; - DISPID voice_dispids[8]; - DISPID voice_collection_item_dispid; - DISPID voice_token_dispids[2]; - DISPID memory_stream_dispids[3]; - DISPID audio_format_getwaveformatex_dispid; - DISPID waveformatex_dispids[4]; - unsigned long sample_rate; - unsigned char bits_per_sample; - unsigned char channels; - unsigned char must_reset_output; - } blastspeak; - - int blastspeak_initialize(blastspeak* instance); - - void blastspeak_destroy(blastspeak* instance); - - int blastspeak_speak(blastspeak* instance, const char* text); - - int blastspeak_set_voice(blastspeak* instance, unsigned int voice_index); - - const char* blastspeak_get_voice_description(blastspeak* instance, unsigned int voice_index); - - const char* blastspeak_get_voice_attribute(blastspeak* instance, unsigned int voice_index, const char* attribute); - - const char* blastspeak_get_voice_languages(blastspeak* instance, unsigned int voice_index); - - int blastspeak_get_voice_rate(blastspeak* instance, long* result); - - int blastspeak_set_voice_rate(blastspeak* instance, long value); - - int blastspeak_get_voice_volume(blastspeak* instance, long* result); - - int blastspeak_set_voice_volume(blastspeak* instance, long value); - - char* blastspeak_speak_to_memory(blastspeak* instance, unsigned long* bytes, const char* text); - -#ifdef __cplusplus -} -#endif - -#endif +/* +Blastspeak text to speech library +Copyright (c) 2019-2020 Philip Bennefall + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef BLASTSPEAK_H +#define BLASTSPEAK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifndef blastspeak_static_memory_length +#define blastspeak_static_memory_length 64 /* In bytes. */ +#endif + +#ifndef blastspeak_max_languages_per_voice +#define blastspeak_max_languages_per_voice 4 +#endif + + typedef struct blastspeak + { + char static_memory[blastspeak_static_memory_length]; + char* allocated_memory; + IDispatch* voice; + IDispatch* format; + IDispatch* voices; + IDispatch* default_voice_token; + IDispatch* current_voice_token; + unsigned int voice_count; + unsigned int allocated_memory_length; + DISPID voice_dispids[8]; + DISPID voice_collection_item_dispid; + DISPID voice_token_dispids[2]; + DISPID memory_stream_dispids[3]; + DISPID audio_format_getwaveformatex_dispid; + DISPID waveformatex_dispids[4]; + unsigned long sample_rate; + unsigned char bits_per_sample; + unsigned char channels; + unsigned char must_reset_output; + } blastspeak; + + int blastspeak_initialize(blastspeak* instance); + + void blastspeak_destroy(blastspeak* instance); + + int blastspeak_speak(blastspeak* instance, const char* text); + + int blastspeak_set_voice(blastspeak* instance, unsigned int voice_index); + + const char* blastspeak_get_voice_description(blastspeak* instance, unsigned int voice_index); + + const char* blastspeak_get_voice_attribute(blastspeak* instance, unsigned int voice_index, const char* attribute); + + const char* blastspeak_get_voice_languages(blastspeak* instance, unsigned int voice_index); + + int blastspeak_get_voice_rate(blastspeak* instance, long* result); + + int blastspeak_set_voice_rate(blastspeak* instance, long value); + + int blastspeak_get_voice_volume(blastspeak* instance, long* result); + + int blastspeak_set_voice_volume(blastspeak* instance, long value); + + char* blastspeak_speak_to_memory(blastspeak* instance, unsigned long* bytes, const char* text); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Dep/fsapi.c b/Dep/fsapi.c index 7adc4c2..9f62f45 100644 --- a/Dep/fsapi.c +++ b/Dep/fsapi.c @@ -1,85 +1,85 @@ - - -/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */ - -/* link this file in with the server and any clients */ - - - /* File created by MIDL compiler version 8.00.0595 */ -/* at Wed Aug 28 17:26:05 2013 - */ -/* Compiler settings for ..\..\FSAPI.IDL: - Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0595 - protocol : dce , ms_ext, c_ext, robust - error checks: allocation ref bounds_check enum stub_data - VC __declspec() decoration level: - __declspec(uuid()), __declspec(selectany), __declspec(novtable) - DECLSPEC_UUID(), MIDL_INTERFACE() -*/ -/* @@MIDL_FILE_HEADING( ) */ - -#pragma warning( disable: 4049 ) /* more than 64k source lines */ - - -#ifdef __cplusplus -extern "C"{ -#endif - - -#include -#include - -#ifdef _MIDL_USE_GUIDDEF_ - -#ifndef INITGUID -#define INITGUID -#include -#undef INITGUID -#else -#include -#endif - -#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ - DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) - -#else // !_MIDL_USE_GUIDDEF_ - -#ifndef __IID_DEFINED__ -#define __IID_DEFINED__ - -typedef struct _IID -{ - unsigned long x; - unsigned short s1; - unsigned short s2; - unsigned char c[8]; -} IID; - -#endif // __IID_DEFINED__ - -#ifndef CLSID_DEFINED -#define CLSID_DEFINED -typedef IID CLSID; -#endif // CLSID_DEFINED - -#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ - const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} - -#endif !_MIDL_USE_GUIDDEF_ - -MIDL_DEFINE_GUID(IID, LIBID_FSAPILib,0xF152C4EF,0xB92F,0x4139,0xA9,0x01,0xE8,0xF1,0xE2,0x8C,0xC8,0xE0); - - -MIDL_DEFINE_GUID(IID, IID_IJawsApi,0x123DEDB4,0x2CF6,0x429C,0xA2,0xAB,0xCC,0x80,0x9E,0x55,0x16,0xCE); - - -MIDL_DEFINE_GUID(CLSID, CLSID_JawsApi,0xCCE5B1E5,0xB2ED,0x45D5,0xB0,0x9F,0x8E,0xC5,0x4B,0x75,0xAB,0xF4); - -#undef MIDL_DEFINE_GUID - -#ifdef __cplusplus -} -#endif - - - + + +/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */ + +/* link this file in with the server and any clients */ + + + /* File created by MIDL compiler version 8.00.0595 */ +/* at Wed Aug 28 17:26:05 2013 + */ +/* Compiler settings for ..\..\FSAPI.IDL: + Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0595 + protocol : dce , ms_ext, c_ext, robust + error checks: allocation ref bounds_check enum stub_data + VC __declspec() decoration level: + __declspec(uuid()), __declspec(selectany), __declspec(novtable) + DECLSPEC_UUID(), MIDL_INTERFACE() +*/ +/* @@MIDL_FILE_HEADING( ) */ + +#pragma warning( disable: 4049 ) /* more than 64k source lines */ + + +#ifdef __cplusplus +extern "C"{ +#endif + + +#include +#include + +#ifdef _MIDL_USE_GUIDDEF_ + +#ifndef INITGUID +#define INITGUID +#include +#undef INITGUID +#else +#include +#endif + +#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) + +#else // !_MIDL_USE_GUIDDEF_ + +#ifndef __IID_DEFINED__ +#define __IID_DEFINED__ + +typedef struct _IID +{ + unsigned long x; + unsigned short s1; + unsigned short s2; + unsigned char c[8]; +} IID; + +#endif // __IID_DEFINED__ + +#ifndef CLSID_DEFINED +#define CLSID_DEFINED +typedef IID CLSID; +#endif // CLSID_DEFINED + +#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} + +#endif !_MIDL_USE_GUIDDEF_ + +MIDL_DEFINE_GUID(IID, LIBID_FSAPILib,0xF152C4EF,0xB92F,0x4139,0xA9,0x01,0xE8,0xF1,0xE2,0x8C,0xC8,0xE0); + + +MIDL_DEFINE_GUID(IID, IID_IJawsApi,0x123DEDB4,0x2CF6,0x429C,0xA2,0xAB,0xCC,0x80,0x9E,0x55,0x16,0xCE); + + +MIDL_DEFINE_GUID(CLSID, CLSID_JawsApi,0xCCE5B1E5,0xB2ED,0x45D5,0xB0,0x9F,0x8E,0xC5,0x4B,0x75,0xAB,0xF4); + +#undef MIDL_DEFINE_GUID + +#ifdef __cplusplus +} +#endif + + + diff --git a/Dep/fsapi.h b/Dep/fsapi.h index 1d3e078..354554d 100644 --- a/Dep/fsapi.h +++ b/Dep/fsapi.h @@ -1,286 +1,286 @@ - - -/* this ALWAYS GENERATED file contains the definitions for the interfaces */ - - - /* File created by MIDL compiler version 8.00.0595 */ -/* at Wed Aug 28 17:26:05 2013 - */ -/* Compiler settings for ..\..\FSAPI.IDL: - Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0595 - protocol : dce , ms_ext, c_ext, robust - error checks: allocation ref bounds_check enum stub_data - VC __declspec() decoration level: - __declspec(uuid()), __declspec(selectany), __declspec(novtable) - DECLSPEC_UUID(), MIDL_INTERFACE() -*/ -/* @@MIDL_FILE_HEADING( ) */ - -#pragma warning( disable: 4049 ) /* more than 64k source lines */ - - -/* verify that the version is high enough to compile this file*/ -#ifndef __REQUIRED_RPCNDR_H_VERSION__ -#define __REQUIRED_RPCNDR_H_VERSION__ 475 -#endif - -#include "rpc.h" -#include "rpcndr.h" - -#ifndef __RPCNDR_H_VERSION__ -#error this stub requires an updated version of -#endif // __RPCNDR_H_VERSION__ - - -#ifndef __FSAPI_h_h__ -#define __FSAPI_h_h__ - -#if defined(_MSC_VER) && (_MSC_VER >= 1020) -#pragma once -#endif - -/* Forward Declarations */ - -#ifndef __IJawsApi_FWD_DEFINED__ -#define __IJawsApi_FWD_DEFINED__ -typedef interface IJawsApi IJawsApi; - -#endif /* __IJawsApi_FWD_DEFINED__ */ - - -#ifndef __JawsApi_FWD_DEFINED__ -#define __JawsApi_FWD_DEFINED__ - -#ifdef __cplusplus -typedef class JawsApi JawsApi; -#else -typedef struct JawsApi JawsApi; -#endif /* __cplusplus */ - -#endif /* __JawsApi_FWD_DEFINED__ */ - - -#ifdef __cplusplus -extern "C"{ -#endif - - - -#ifndef __FSAPILib_LIBRARY_DEFINED__ -#define __FSAPILib_LIBRARY_DEFINED__ - -/* library FSAPILib */ -/* [custom][custom][custom][helpstring][version][uuid] */ - - - -EXTERN_C const IID LIBID_FSAPILib; - -#ifndef __IJawsApi_INTERFACE_DEFINED__ -#define __IJawsApi_INTERFACE_DEFINED__ - -/* interface IJawsApi */ -/* [object][oleautomation][dual][helpstring][uuid] */ - - -EXTERN_C const IID IID_IJawsApi; - -#if defined(__cplusplus) && !defined(CINTERFACE) - - MIDL_INTERFACE("123DEDB4-2CF6-429C-A2AB-CC809E5516CE") - IJawsApi : public IDispatch - { - public: - virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE RunScript( - /* [in] */ BSTR ScriptName, - /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; - - virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE SayString( - /* [in] */ BSTR StringToSpeak, - /* [defaultvalue][optional][in] */ VARIANT_BOOL bFlush, - /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; - - virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE StopSpeech( void) = 0; - - virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Enable( - /* [in] */ VARIANT_BOOL vbNoDDIHooks, - /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; - - virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Disable( - /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; - - virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE RunFunction( - /* [in] */ BSTR FunctionName, - /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; - - }; - - -#else /* C style interface */ - - typedef struct IJawsApiVtbl - { - BEGIN_INTERFACE - - HRESULT ( STDMETHODCALLTYPE *QueryInterface )( - IJawsApi * This, - /* [in] */ REFIID riid, - /* [annotation][iid_is][out] */ - _COM_Outptr_ void **ppvObject); - - ULONG ( STDMETHODCALLTYPE *AddRef )( - IJawsApi * This); - - ULONG ( STDMETHODCALLTYPE *Release )( - IJawsApi * This); - - HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( - IJawsApi * This, - /* [out] */ UINT *pctinfo); - - HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( - IJawsApi * This, - /* [in] */ UINT iTInfo, - /* [in] */ LCID lcid, - /* [out] */ ITypeInfo **ppTInfo); - - HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( - IJawsApi * This, - /* [in] */ REFIID riid, - /* [size_is][in] */ LPOLESTR *rgszNames, - /* [range][in] */ UINT cNames, - /* [in] */ LCID lcid, - /* [size_is][out] */ DISPID *rgDispId); - - /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( - IJawsApi * This, - /* [annotation][in] */ - _In_ DISPID dispIdMember, - /* [annotation][in] */ - _In_ REFIID riid, - /* [annotation][in] */ - _In_ LCID lcid, - /* [annotation][in] */ - _In_ WORD wFlags, - /* [annotation][out][in] */ - _In_ DISPPARAMS *pDispParams, - /* [annotation][out] */ - _Out_opt_ VARIANT *pVarResult, - /* [annotation][out] */ - _Out_opt_ EXCEPINFO *pExcepInfo, - /* [annotation][out] */ - _Out_opt_ UINT *puArgErr); - - /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *RunScript )( - IJawsApi * This, - /* [in] */ BSTR ScriptName, - /* [retval][out] */ VARIANT_BOOL *vbSuccess); - - /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *SayString )( - IJawsApi * This, - /* [in] */ BSTR StringToSpeak, - /* [defaultvalue][optional][in] */ VARIANT_BOOL bFlush, - /* [retval][out] */ VARIANT_BOOL *vbSuccess); - - /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *StopSpeech )( - IJawsApi * This); - - /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Enable )( - IJawsApi * This, - /* [in] */ VARIANT_BOOL vbNoDDIHooks, - /* [retval][out] */ VARIANT_BOOL *vbSuccess); - - /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Disable )( - IJawsApi * This, - /* [retval][out] */ VARIANT_BOOL *vbSuccess); - - /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *RunFunction )( - IJawsApi * This, - /* [in] */ BSTR FunctionName, - /* [retval][out] */ VARIANT_BOOL *vbSuccess); - - END_INTERFACE - } IJawsApiVtbl; - - interface IJawsApi - { - CONST_VTBL struct IJawsApiVtbl *lpVtbl; - }; - - - -#ifdef COBJMACROS - - -#define IJawsApi_QueryInterface(This,riid,ppvObject) \ - ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) - -#define IJawsApi_AddRef(This) \ - ( (This)->lpVtbl -> AddRef(This) ) - -#define IJawsApi_Release(This) \ - ( (This)->lpVtbl -> Release(This) ) - - -#define IJawsApi_GetTypeInfoCount(This,pctinfo) \ - ( (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) ) - -#define IJawsApi_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ - ( (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) ) - -#define IJawsApi_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ - ( (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) ) - -#define IJawsApi_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ - ( (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) ) - - -#define IJawsApi_RunScript(This,ScriptName,vbSuccess) \ - ( (This)->lpVtbl -> RunScript(This,ScriptName,vbSuccess) ) - -#define IJawsApi_SayString(This,StringToSpeak,bFlush,vbSuccess) \ - ( (This)->lpVtbl -> SayString(This,StringToSpeak,bFlush,vbSuccess) ) - -#define IJawsApi_StopSpeech(This) \ - ( (This)->lpVtbl -> StopSpeech(This) ) - -#define IJawsApi_Enable(This,vbNoDDIHooks,vbSuccess) \ - ( (This)->lpVtbl -> Enable(This,vbNoDDIHooks,vbSuccess) ) - -#define IJawsApi_Disable(This,vbSuccess) \ - ( (This)->lpVtbl -> Disable(This,vbSuccess) ) - -#define IJawsApi_RunFunction(This,FunctionName,vbSuccess) \ - ( (This)->lpVtbl -> RunFunction(This,FunctionName,vbSuccess) ) - -#endif /* COBJMACROS */ - - -#endif /* C style interface */ - - - - -#endif /* __IJawsApi_INTERFACE_DEFINED__ */ - - -EXTERN_C const CLSID CLSID_JawsApi; - -#ifdef __cplusplus - -class DECLSPEC_UUID("CCE5B1E5-B2ED-45D5-B09F-8EC54B75ABF4") -JawsApi; -#endif -#endif /* __FSAPILib_LIBRARY_DEFINED__ */ - -/* Additional Prototypes for ALL interfaces */ - -/* end of Additional Prototypes */ - -#ifdef __cplusplus -} -#endif - -#endif - - + + +/* this ALWAYS GENERATED file contains the definitions for the interfaces */ + + + /* File created by MIDL compiler version 8.00.0595 */ +/* at Wed Aug 28 17:26:05 2013 + */ +/* Compiler settings for ..\..\FSAPI.IDL: + Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0595 + protocol : dce , ms_ext, c_ext, robust + error checks: allocation ref bounds_check enum stub_data + VC __declspec() decoration level: + __declspec(uuid()), __declspec(selectany), __declspec(novtable) + DECLSPEC_UUID(), MIDL_INTERFACE() +*/ +/* @@MIDL_FILE_HEADING( ) */ + +#pragma warning( disable: 4049 ) /* more than 64k source lines */ + + +/* verify that the version is high enough to compile this file*/ +#ifndef __REQUIRED_RPCNDR_H_VERSION__ +#define __REQUIRED_RPCNDR_H_VERSION__ 475 +#endif + +#include "rpc.h" +#include "rpcndr.h" + +#ifndef __RPCNDR_H_VERSION__ +#error this stub requires an updated version of +#endif // __RPCNDR_H_VERSION__ + + +#ifndef __FSAPI_h_h__ +#define __FSAPI_h_h__ + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +#pragma once +#endif + +/* Forward Declarations */ + +#ifndef __IJawsApi_FWD_DEFINED__ +#define __IJawsApi_FWD_DEFINED__ +typedef interface IJawsApi IJawsApi; + +#endif /* __IJawsApi_FWD_DEFINED__ */ + + +#ifndef __JawsApi_FWD_DEFINED__ +#define __JawsApi_FWD_DEFINED__ + +#ifdef __cplusplus +typedef class JawsApi JawsApi; +#else +typedef struct JawsApi JawsApi; +#endif /* __cplusplus */ + +#endif /* __JawsApi_FWD_DEFINED__ */ + + +#ifdef __cplusplus +extern "C"{ +#endif + + + +#ifndef __FSAPILib_LIBRARY_DEFINED__ +#define __FSAPILib_LIBRARY_DEFINED__ + +/* library FSAPILib */ +/* [custom][custom][custom][helpstring][version][uuid] */ + + + +EXTERN_C const IID LIBID_FSAPILib; + +#ifndef __IJawsApi_INTERFACE_DEFINED__ +#define __IJawsApi_INTERFACE_DEFINED__ + +/* interface IJawsApi */ +/* [object][oleautomation][dual][helpstring][uuid] */ + + +EXTERN_C const IID IID_IJawsApi; + +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE("123DEDB4-2CF6-429C-A2AB-CC809E5516CE") + IJawsApi : public IDispatch + { + public: + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE RunScript( + /* [in] */ BSTR ScriptName, + /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE SayString( + /* [in] */ BSTR StringToSpeak, + /* [defaultvalue][optional][in] */ VARIANT_BOOL bFlush, + /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE StopSpeech( void) = 0; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Enable( + /* [in] */ VARIANT_BOOL vbNoDDIHooks, + /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Disable( + /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; + + virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE RunFunction( + /* [in] */ BSTR FunctionName, + /* [retval][out] */ VARIANT_BOOL *vbSuccess) = 0; + + }; + + +#else /* C style interface */ + + typedef struct IJawsApiVtbl + { + BEGIN_INTERFACE + + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + IJawsApi * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + ULONG ( STDMETHODCALLTYPE *AddRef )( + IJawsApi * This); + + ULONG ( STDMETHODCALLTYPE *Release )( + IJawsApi * This); + + HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )( + IJawsApi * This, + /* [out] */ UINT *pctinfo); + + HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )( + IJawsApi * This, + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ ITypeInfo **ppTInfo); + + HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )( + IJawsApi * This, + /* [in] */ REFIID riid, + /* [size_is][in] */ LPOLESTR *rgszNames, + /* [range][in] */ UINT cNames, + /* [in] */ LCID lcid, + /* [size_is][out] */ DISPID *rgDispId); + + /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )( + IJawsApi * This, + /* [annotation][in] */ + _In_ DISPID dispIdMember, + /* [annotation][in] */ + _In_ REFIID riid, + /* [annotation][in] */ + _In_ LCID lcid, + /* [annotation][in] */ + _In_ WORD wFlags, + /* [annotation][out][in] */ + _In_ DISPPARAMS *pDispParams, + /* [annotation][out] */ + _Out_opt_ VARIANT *pVarResult, + /* [annotation][out] */ + _Out_opt_ EXCEPINFO *pExcepInfo, + /* [annotation][out] */ + _Out_opt_ UINT *puArgErr); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *RunScript )( + IJawsApi * This, + /* [in] */ BSTR ScriptName, + /* [retval][out] */ VARIANT_BOOL *vbSuccess); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *SayString )( + IJawsApi * This, + /* [in] */ BSTR StringToSpeak, + /* [defaultvalue][optional][in] */ VARIANT_BOOL bFlush, + /* [retval][out] */ VARIANT_BOOL *vbSuccess); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *StopSpeech )( + IJawsApi * This); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Enable )( + IJawsApi * This, + /* [in] */ VARIANT_BOOL vbNoDDIHooks, + /* [retval][out] */ VARIANT_BOOL *vbSuccess); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *Disable )( + IJawsApi * This, + /* [retval][out] */ VARIANT_BOOL *vbSuccess); + + /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE *RunFunction )( + IJawsApi * This, + /* [in] */ BSTR FunctionName, + /* [retval][out] */ VARIANT_BOOL *vbSuccess); + + END_INTERFACE + } IJawsApiVtbl; + + interface IJawsApi + { + CONST_VTBL struct IJawsApiVtbl *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define IJawsApi_QueryInterface(This,riid,ppvObject) \ + ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) + +#define IJawsApi_AddRef(This) \ + ( (This)->lpVtbl -> AddRef(This) ) + +#define IJawsApi_Release(This) \ + ( (This)->lpVtbl -> Release(This) ) + + +#define IJawsApi_GetTypeInfoCount(This,pctinfo) \ + ( (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) ) + +#define IJawsApi_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \ + ( (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) ) + +#define IJawsApi_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \ + ( (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) ) + +#define IJawsApi_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \ + ( (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) ) + + +#define IJawsApi_RunScript(This,ScriptName,vbSuccess) \ + ( (This)->lpVtbl -> RunScript(This,ScriptName,vbSuccess) ) + +#define IJawsApi_SayString(This,StringToSpeak,bFlush,vbSuccess) \ + ( (This)->lpVtbl -> SayString(This,StringToSpeak,bFlush,vbSuccess) ) + +#define IJawsApi_StopSpeech(This) \ + ( (This)->lpVtbl -> StopSpeech(This) ) + +#define IJawsApi_Enable(This,vbNoDDIHooks,vbSuccess) \ + ( (This)->lpVtbl -> Enable(This,vbNoDDIHooks,vbSuccess) ) + +#define IJawsApi_Disable(This,vbSuccess) \ + ( (This)->lpVtbl -> Disable(This,vbSuccess) ) + +#define IJawsApi_RunFunction(This,FunctionName,vbSuccess) \ + ( (This)->lpVtbl -> RunFunction(This,FunctionName,vbSuccess) ) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + + + +#endif /* __IJawsApi_INTERFACE_DEFINED__ */ + + +EXTERN_C const CLSID CLSID_JawsApi; + +#ifdef __cplusplus + +class DECLSPEC_UUID("CCE5B1E5-B2ED-45D5-B09F-8EC54B75ABF4") +JawsApi; +#endif +#endif /* __FSAPILib_LIBRARY_DEFINED__ */ + +/* Additional Prototypes for ALL interfaces */ + +/* end of Additional Prototypes */ + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/Dep/wasapi.cpp b/Dep/wasapi.cpp index d14ce0c..b99b756 100644 --- a/Dep/wasapi.cpp +++ b/Dep/wasapi.cpp @@ -1,374 +1,374 @@ -#define NOMINMAX -#include "wasapi.h" -#include -#include - -#ifndef PKEY_Device_FriendlyName -#undef DEFINE_PROPERTYKEY -#define DEFINE_PROPERTYKEY(id, a, b, c, d, e, f, g, h, i, j, k, l) \ - const PROPERTYKEY id = { { a, b, c, { d, e, f, g, h, i, j, k, } }, l }; -DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); -#endif - - -constexpr REFERENCE_TIME REFTIMES_PER_MILLISEC = 10000; -constexpr REFERENCE_TIME BUFFER_SIZE = 400 * REFTIMES_PER_MILLISEC; - -const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); -const IID IID_IMMDevice = __uuidof(IMMDevice); -const IID IID_IMMDeviceCollection = __uuidof(IMMDeviceCollection); -const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); -const IID IID_IAudioClient = __uuidof(IAudioClient); -const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); -const IID IID_IAudioClock = __uuidof(IAudioClock); -const IID IID_IMMNotificationClient = __uuidof(IMMNotificationClient); -const IID IID_IAudioStreamVolume = __uuidof(IAudioStreamVolume); -const IID IID_IPropertyStore = __uuidof(IPropertyStore); -NotificationClientPtr notificationClient; - -WasapiPlayer::WasapiPlayer(wchar_t* deviceName, WAVEFORMATEX format, ChunkCompletedCallback callback) - : deviceName(deviceName), format(format), callback(callback) { - wakeEvent = CreateEvent(nullptr, false, false, nullptr); - IMMDeviceEnumeratorPtr enumerator; - HRESULT hr = enumerator.CreateInstance(CLSID_MMDeviceEnumerator); - notificationClient = new NotificationClient(); - enumerator->RegisterEndpointNotificationCallback(notificationClient); - -} - -HRESULT WasapiPlayer::open(bool force) { - if (client && !force) { - return S_OK; - } - defaultDeviceChangeCount = notificationClient->getDefaultDeviceChangeCount(); - deviceStateChangeCount = notificationClient->getDeviceStateChangeCount(); - IMMDeviceEnumeratorPtr enumerator; - HRESULT hr = enumerator.CreateInstance(CLSID_MMDeviceEnumerator); - if (FAILED(hr)) { - return hr; - } - IMMDevicePtr device; - isUsingPreferredDevice = false; - if (deviceName.empty()) { - hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); - } - else { - hr = getPreferredDevice(device); - if (SUCCEEDED(hr)) { - isUsingPreferredDevice = true; - } - else { - hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); - } - } - if (FAILED(hr)) { - return hr; - } - hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&client); - if (FAILED(hr)) { - return hr; - } - hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, - BUFFER_SIZE, 0, &format, nullptr); - if (FAILED(hr)) { - return hr; - } - hr = client->GetBufferSize(&bufferFrames); - if (FAILED(hr)) { - return hr; - } - hr = client->GetService(IID_IAudioRenderClient, (void**)&render); - if (FAILED(hr)) { - return hr; - } - hr = client->GetService(IID_IAudioClock, (void**)&clock); - if (FAILED(hr)) { - return hr; - } - hr = clock->GetFrequency(&clockFreq); - if (FAILED(hr)) { - return hr; - } - playState = PlayState::stopped; - return S_OK; -} - -HRESULT WasapiPlayer::feed(unsigned char* data, unsigned int size, unsigned int* id -) { - if (playState == PlayState::stopping) { - completeStop(); - } - UINT32 remainingFrames = size / format.nBlockAlign; - HRESULT hr; - - auto reopenUsingNewDev = [&] { - hr = open(true); - if (FAILED(hr)) { - return false; - } - for (auto& [itemId, itemEnd] : feedEnds) { - callback(this, itemId); - } - feedEnds.clear(); - sentFrames = 0; - return true; - }; - - while (remainingFrames > 0) { - UINT32 paddingFrames; - - auto getPaddingHandlingStopOrDevChange = [&] { - if (playState == PlayState::stopping) { - completeStop(); - hr = S_OK; - return false; - } - if ( - didPreferredDeviceBecomeAvailable() || - (!isUsingPreferredDevice && defaultDeviceChangeCount != - notificationClient->getDefaultDeviceChangeCount()) - ) { - if (!reopenUsingNewDev()) { - return false; - } - } - hr = client->GetCurrentPadding(&paddingFrames); - if ( - hr == AUDCLNT_E_DEVICE_INVALIDATED - || hr == AUDCLNT_E_NOT_INITIALIZED - ) { - if (!reopenUsingNewDev()) { - return false; - } - hr = client->GetCurrentPadding(&paddingFrames); - } - return SUCCEEDED(hr); - }; - - if (!getPaddingHandlingStopOrDevChange()) { - return hr; - } - if (paddingFrames > bufferFrames / 2) { - waitUntilNeeded(framesToMs(paddingFrames - bufferFrames / 2)); - if (!getPaddingHandlingStopOrDevChange()) { - return hr; - } - } - const UINT32 sendFrames = std::min(remainingFrames, bufferFrames - paddingFrames); - const UINT32 sendBytes = sendFrames * format.nBlockAlign; - BYTE* buffer; - hr = render->GetBuffer(sendFrames, &buffer); - if (FAILED(hr)) { - return hr; - } - memcpy(buffer, data, sendBytes); - hr = render->ReleaseBuffer(sendFrames, 0); - if (FAILED(hr)) { - return hr; - } - if (playState == PlayState::stopped) { - hr = client->Start(); - if (FAILED(hr)) { - return hr; - } - if (playState == PlayState::stopping) { - completeStop(); - return S_OK; - } - playState = PlayState::playing; - } - maybeFireCallback(); - data += sendBytes; - size -= sendBytes; - remainingFrames -= sendFrames; - sentFrames += sendFrames; - } - - if (playState == PlayState::playing) { - maybeFireCallback(); - } - if (id) { - *id = nextFeedId++; - feedEnds.push_back({ *id, framesToMs(sentFrames) }); - } - return S_OK; -} - -void WasapiPlayer::maybeFireCallback() { - const UINT64 playPos = getPlayPos(); - std::erase_if(feedEnds, [&](auto& val) { - auto [id, end] = val; - if (playPos >= end) { - callback(this, id); - return true; - } - return false; - }); -} - -UINT64 WasapiPlayer::getPlayPos() { - UINT64 pos; - HRESULT hr = clock->GetPosition(&pos, nullptr); - if (FAILED(hr)) { - return framesToMs(sentFrames); - } - return pos * 1000 / clockFreq; -} - -void WasapiPlayer::waitUntilNeeded(UINT64 maxWait) { - if (!feedEnds.empty()) { - UINT64 feedEnd = feedEnds[0].second; - const UINT64 nextCallbackTime = feedEnd - getPlayPos(); - if (nextCallbackTime < maxWait) { - maxWait = nextCallbackTime; - } - } - WaitForSingleObject(wakeEvent, (DWORD)maxWait); -} - -HRESULT WasapiPlayer::getPreferredDevice(IMMDevicePtr& preferredDevice) { - IMMDeviceEnumeratorPtr enumerator; - HRESULT hr = enumerator.CreateInstance(CLSID_MMDeviceEnumerator); - if (FAILED(hr)) { - return hr; - } - IMMDeviceCollectionPtr devices; - hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); - if (FAILED(hr)) { - return hr; - } - UINT count = 0; - devices->GetCount(&count); - for (UINT d = 0; d < count; ++d) { - IMMDevicePtr device; - hr = devices->Item(d, &device); - if (FAILED(hr)) { - return hr; - } - IPropertyStorePtr props; - hr = device->OpenPropertyStore(STGM_READ, &props); - if (FAILED(hr)) { - return hr; - } - PROPVARIANT val; - hr = props->GetValue(PKEY_Device_FriendlyName, &val); - if (FAILED(hr)) { - return hr; - } - constexpr size_t MAX_CHARS = MAXPNAMELEN - 1; - if (wcsncmp(val.pwszVal, deviceName.c_str(), MAX_CHARS) == 0) { - PropVariantClear(&val); - preferredDevice = std::move(device); - return S_OK; - } - PropVariantClear(&val); - } - return E_NOTFOUND; -} - -bool WasapiPlayer::didPreferredDeviceBecomeAvailable() { - if ( - isUsingPreferredDevice || - deviceName.empty() || - deviceStateChangeCount == notificationClient->getDeviceStateChangeCount() - ) { - return false; - } - IMMDevicePtr device; - return SUCCEEDED(getPreferredDevice(device)); -} - -HRESULT WasapiPlayer::stop() { - playState = PlayState::stopping; - HRESULT hr = client->Stop(); - if ( - hr != AUDCLNT_E_DEVICE_INVALIDATED - && hr != AUDCLNT_E_NOT_INITIALIZED - ) { - if (FAILED(hr)) { - return hr; - } - hr = client->Reset(); - if (FAILED(hr)) { - return hr; - } - } - SetEvent(wakeEvent); - return S_OK; -} - -void WasapiPlayer::completeStop() { - nextFeedId = 0; - sentFrames = 0; - feedEnds.clear(); - playState = PlayState::stopped; -} - -HRESULT WasapiPlayer::sync() { - UINT64 sentMs = framesToMs(sentFrames); - for (UINT64 playPos = getPlayPos(); playPos < sentMs; - playPos = getPlayPos()) { - if (playState != PlayState::playing) { - return S_OK; - } - maybeFireCallback(); - waitUntilNeeded(sentMs - playPos); - } - if (playState == PlayState::playing) { - maybeFireCallback(); - } - return S_OK; -} - -HRESULT WasapiPlayer::idle() { - HRESULT hr = sync(); - if (FAILED(hr)) { - return hr; - } - hr = stop(); - if (FAILED(hr)) { - return hr; - } - completeStop(); - return S_OK; -} - -HRESULT WasapiPlayer::pause() { - if (playState != PlayState::playing) { - return S_OK; - } - HRESULT hr = client->Stop(); - if (FAILED(hr)) { - return hr; - } - return S_OK; -} - -HRESULT WasapiPlayer::resume() { - if (playState != PlayState::playing) { - return S_OK; - } - HRESULT hr = client->Start(); - if (FAILED(hr)) { - return hr; - } - return S_OK; -} - -HRESULT WasapiPlayer::setChannelVolume(unsigned int channel, float level) { - IAudioStreamVolumePtr volume; - HRESULT hr = client->GetService(IID_IAudioStreamVolume, (void**)&volume); - if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { - hr = open(true); - if (FAILED(hr)) { - return hr; - } - hr = client->GetService(IID_IAudioStreamVolume, (void**)&volume); - } - if (FAILED(hr)) { - return hr; - } - return volume->SetChannelVolume(channel, level); -} - +#define NOMINMAX +#include "wasapi.h" +#include +#include + +#ifndef PKEY_Device_FriendlyName +#undef DEFINE_PROPERTYKEY +#define DEFINE_PROPERTYKEY(id, a, b, c, d, e, f, g, h, i, j, k, l) \ + const PROPERTYKEY id = { { a, b, c, { d, e, f, g, h, i, j, k, } }, l }; +DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); +#endif + + +constexpr REFERENCE_TIME REFTIMES_PER_MILLISEC = 10000; +constexpr REFERENCE_TIME BUFFER_SIZE = 400 * REFTIMES_PER_MILLISEC; + +const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); +const IID IID_IMMDevice = __uuidof(IMMDevice); +const IID IID_IMMDeviceCollection = __uuidof(IMMDeviceCollection); +const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); +const IID IID_IAudioClient = __uuidof(IAudioClient); +const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient); +const IID IID_IAudioClock = __uuidof(IAudioClock); +const IID IID_IMMNotificationClient = __uuidof(IMMNotificationClient); +const IID IID_IAudioStreamVolume = __uuidof(IAudioStreamVolume); +const IID IID_IPropertyStore = __uuidof(IPropertyStore); +NotificationClientPtr notificationClient; + +WasapiPlayer::WasapiPlayer(wchar_t* deviceName, WAVEFORMATEX format, ChunkCompletedCallback callback) + : deviceName(deviceName), format(format), callback(callback) { + wakeEvent = CreateEvent(nullptr, false, false, nullptr); + IMMDeviceEnumeratorPtr enumerator; + HRESULT hr = enumerator.CreateInstance(CLSID_MMDeviceEnumerator); + notificationClient = new NotificationClient(); + enumerator->RegisterEndpointNotificationCallback(notificationClient); + +} + +HRESULT WasapiPlayer::open(bool force) { + if (client && !force) { + return S_OK; + } + defaultDeviceChangeCount = notificationClient->getDefaultDeviceChangeCount(); + deviceStateChangeCount = notificationClient->getDeviceStateChangeCount(); + IMMDeviceEnumeratorPtr enumerator; + HRESULT hr = enumerator.CreateInstance(CLSID_MMDeviceEnumerator); + if (FAILED(hr)) { + return hr; + } + IMMDevicePtr device; + isUsingPreferredDevice = false; + if (deviceName.empty()) { + hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + } + else { + hr = getPreferredDevice(device); + if (SUCCEEDED(hr)) { + isUsingPreferredDevice = true; + } + else { + hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device); + } + } + if (FAILED(hr)) { + return hr; + } + hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)&client); + if (FAILED(hr)) { + return hr; + } + hr = client->Initialize(AUDCLNT_SHAREMODE_SHARED, + AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, + BUFFER_SIZE, 0, &format, nullptr); + if (FAILED(hr)) { + return hr; + } + hr = client->GetBufferSize(&bufferFrames); + if (FAILED(hr)) { + return hr; + } + hr = client->GetService(IID_IAudioRenderClient, (void**)&render); + if (FAILED(hr)) { + return hr; + } + hr = client->GetService(IID_IAudioClock, (void**)&clock); + if (FAILED(hr)) { + return hr; + } + hr = clock->GetFrequency(&clockFreq); + if (FAILED(hr)) { + return hr; + } + playState = PlayState::stopped; + return S_OK; +} + +HRESULT WasapiPlayer::feed(unsigned char* data, unsigned int size, unsigned int* id +) { + if (playState == PlayState::stopping) { + completeStop(); + } + UINT32 remainingFrames = size / format.nBlockAlign; + HRESULT hr; + + auto reopenUsingNewDev = [&] { + hr = open(true); + if (FAILED(hr)) { + return false; + } + for (auto& [itemId, itemEnd] : feedEnds) { + callback(this, itemId); + } + feedEnds.clear(); + sentFrames = 0; + return true; + }; + + while (remainingFrames > 0) { + UINT32 paddingFrames; + + auto getPaddingHandlingStopOrDevChange = [&] { + if (playState == PlayState::stopping) { + completeStop(); + hr = S_OK; + return false; + } + if ( + didPreferredDeviceBecomeAvailable() || + (!isUsingPreferredDevice && defaultDeviceChangeCount != + notificationClient->getDefaultDeviceChangeCount()) + ) { + if (!reopenUsingNewDev()) { + return false; + } + } + hr = client->GetCurrentPadding(&paddingFrames); + if ( + hr == AUDCLNT_E_DEVICE_INVALIDATED + || hr == AUDCLNT_E_NOT_INITIALIZED + ) { + if (!reopenUsingNewDev()) { + return false; + } + hr = client->GetCurrentPadding(&paddingFrames); + } + return SUCCEEDED(hr); + }; + + if (!getPaddingHandlingStopOrDevChange()) { + return hr; + } + if (paddingFrames > bufferFrames / 2) { + waitUntilNeeded(framesToMs(paddingFrames - bufferFrames / 2)); + if (!getPaddingHandlingStopOrDevChange()) { + return hr; + } + } + const UINT32 sendFrames = std::min(remainingFrames, bufferFrames - paddingFrames); + const UINT32 sendBytes = sendFrames * format.nBlockAlign; + BYTE* buffer; + hr = render->GetBuffer(sendFrames, &buffer); + if (FAILED(hr)) { + return hr; + } + memcpy(buffer, data, sendBytes); + hr = render->ReleaseBuffer(sendFrames, 0); + if (FAILED(hr)) { + return hr; + } + if (playState == PlayState::stopped) { + hr = client->Start(); + if (FAILED(hr)) { + return hr; + } + if (playState == PlayState::stopping) { + completeStop(); + return S_OK; + } + playState = PlayState::playing; + } + maybeFireCallback(); + data += sendBytes; + size -= sendBytes; + remainingFrames -= sendFrames; + sentFrames += sendFrames; + } + + if (playState == PlayState::playing) { + maybeFireCallback(); + } + if (id) { + *id = nextFeedId++; + feedEnds.push_back({ *id, framesToMs(sentFrames) }); + } + return S_OK; +} + +void WasapiPlayer::maybeFireCallback() { + const UINT64 playPos = getPlayPos(); + std::erase_if(feedEnds, [&](auto& val) { + auto [id, end] = val; + if (playPos >= end) { + callback(this, id); + return true; + } + return false; + }); +} + +UINT64 WasapiPlayer::getPlayPos() { + UINT64 pos; + HRESULT hr = clock->GetPosition(&pos, nullptr); + if (FAILED(hr)) { + return framesToMs(sentFrames); + } + return pos * 1000 / clockFreq; +} + +void WasapiPlayer::waitUntilNeeded(UINT64 maxWait) { + if (!feedEnds.empty()) { + UINT64 feedEnd = feedEnds[0].second; + const UINT64 nextCallbackTime = feedEnd - getPlayPos(); + if (nextCallbackTime < maxWait) { + maxWait = nextCallbackTime; + } + } + WaitForSingleObject(wakeEvent, (DWORD)maxWait); +} + +HRESULT WasapiPlayer::getPreferredDevice(IMMDevicePtr& preferredDevice) { + IMMDeviceEnumeratorPtr enumerator; + HRESULT hr = enumerator.CreateInstance(CLSID_MMDeviceEnumerator); + if (FAILED(hr)) { + return hr; + } + IMMDeviceCollectionPtr devices; + hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices); + if (FAILED(hr)) { + return hr; + } + UINT count = 0; + devices->GetCount(&count); + for (UINT d = 0; d < count; ++d) { + IMMDevicePtr device; + hr = devices->Item(d, &device); + if (FAILED(hr)) { + return hr; + } + IPropertyStorePtr props; + hr = device->OpenPropertyStore(STGM_READ, &props); + if (FAILED(hr)) { + return hr; + } + PROPVARIANT val; + hr = props->GetValue(PKEY_Device_FriendlyName, &val); + if (FAILED(hr)) { + return hr; + } + constexpr size_t MAX_CHARS = MAXPNAMELEN - 1; + if (wcsncmp(val.pwszVal, deviceName.c_str(), MAX_CHARS) == 0) { + PropVariantClear(&val); + preferredDevice = std::move(device); + return S_OK; + } + PropVariantClear(&val); + } + return E_NOTFOUND; +} + +bool WasapiPlayer::didPreferredDeviceBecomeAvailable() { + if ( + isUsingPreferredDevice || + deviceName.empty() || + deviceStateChangeCount == notificationClient->getDeviceStateChangeCount() + ) { + return false; + } + IMMDevicePtr device; + return SUCCEEDED(getPreferredDevice(device)); +} + +HRESULT WasapiPlayer::stop() { + playState = PlayState::stopping; + HRESULT hr = client->Stop(); + if ( + hr != AUDCLNT_E_DEVICE_INVALIDATED + && hr != AUDCLNT_E_NOT_INITIALIZED + ) { + if (FAILED(hr)) { + return hr; + } + hr = client->Reset(); + if (FAILED(hr)) { + return hr; + } + } + SetEvent(wakeEvent); + return S_OK; +} + +void WasapiPlayer::completeStop() { + nextFeedId = 0; + sentFrames = 0; + feedEnds.clear(); + playState = PlayState::stopped; +} + +HRESULT WasapiPlayer::sync() { + UINT64 sentMs = framesToMs(sentFrames); + for (UINT64 playPos = getPlayPos(); playPos < sentMs; + playPos = getPlayPos()) { + if (playState != PlayState::playing) { + return S_OK; + } + maybeFireCallback(); + waitUntilNeeded(sentMs - playPos); + } + if (playState == PlayState::playing) { + maybeFireCallback(); + } + return S_OK; +} + +HRESULT WasapiPlayer::idle() { + HRESULT hr = sync(); + if (FAILED(hr)) { + return hr; + } + hr = stop(); + if (FAILED(hr)) { + return hr; + } + completeStop(); + return S_OK; +} + +HRESULT WasapiPlayer::pause() { + if (playState != PlayState::playing) { + return S_OK; + } + HRESULT hr = client->Stop(); + if (FAILED(hr)) { + return hr; + } + return S_OK; +} + +HRESULT WasapiPlayer::resume() { + if (playState != PlayState::playing) { + return S_OK; + } + HRESULT hr = client->Start(); + if (FAILED(hr)) { + return hr; + } + return S_OK; +} + +HRESULT WasapiPlayer::setChannelVolume(unsigned int channel, float level) { + IAudioStreamVolumePtr volume; + HRESULT hr = client->GetService(IID_IAudioStreamVolume, (void**)&volume); + if (hr == AUDCLNT_E_DEVICE_INVALIDATED) { + hr = open(true); + if (FAILED(hr)) { + return hr; + } + hr = client->GetService(IID_IAudioStreamVolume, (void**)&volume); + } + if (FAILED(hr)) { + return hr; + } + return volume->SetChannelVolume(channel, level); +} + diff --git a/Dep/wasapi.h b/Dep/wasapi.h index 1ac6421..8527879 100644 --- a/Dep/wasapi.h +++ b/Dep/wasapi.h @@ -1,188 +1,188 @@ -#pragma once - -#ifndef WASAPI_H_ -#define WASAPI_H_ - -#if defined(_WIN32) -#ifdef WASAPI_EXPORTS -#define WASAPI_API __declspec(dllexport) -#else -#define WASAPI_API __declspec(dllimport) -#endif -#else -#define WASAPI_API -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - - -_COM_SMARTPTR_TYPEDEF(IMMDevice, IID_IMMDevice); -_COM_SMARTPTR_TYPEDEF(IMMDeviceCollection, IID_IMMDeviceCollection); -_COM_SMARTPTR_TYPEDEF(IMMDeviceEnumerator, IID_IMMDeviceEnumerator); -_COM_SMARTPTR_TYPEDEF(IAudioClient, IID_IAudioClient); -_COM_SMARTPTR_TYPEDEF(IAudioRenderClient, IID_IAudioRenderClient); -_COM_SMARTPTR_TYPEDEF(IAudioClock, IID_IAudioClock); -_COM_SMARTPTR_TYPEDEF(IAudioStreamVolume, IID_IAudioStreamVolume); -_COM_SMARTPTR_TYPEDEF(IPropertyStore, IID_IPropertyStore); - -class AutoHandle { -public: - AutoHandle() : handle(nullptr) {} - AutoHandle(HANDLE handle) : handle(handle) {} - - ~AutoHandle() { - if (handle) { - CloseHandle(handle); - } - } - - AutoHandle& operator=(HANDLE newHandle) { - if (handle) { - CloseHandle(handle); - } - handle = newHandle; - return *this; - } - - operator HANDLE() { - return handle; - } - -private: - HANDLE handle; -}; - -class NotificationClient : public IMMNotificationClient { -public: - ULONG STDMETHODCALLTYPE AddRef() override { - return InterlockedIncrement(&refCount); - } - - ULONG STDMETHODCALLTYPE Release() override { - LONG result = InterlockedDecrement(&refCount); - if (result == 0) { - delete this; - } - return result; - } - - STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) final { - if (riid == IID_IUnknown || riid == IID_IMMNotificationClient) { - AddRef(); - *ppvObject = (void*)this; - return S_OK; - } - return E_NOINTERFACE; - } - - STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, - LPCWSTR defaultDeviceId - ) final { - if (flow == eRender && role == eConsole) { - ++defaultDeviceChangeCount; - } - return S_OK; - } - - STDMETHODIMP OnDeviceAdded(LPCWSTR deviceId) final { - return S_OK; - } - - STDMETHODIMP OnDeviceRemoved(LPCWSTR deviceId) final { - return S_OK; - } - - STDMETHODIMP OnDeviceStateChanged(LPCWSTR deviceId, DWORD newState) final { - ++deviceStateChangeCount; - return S_OK; - } - - STDMETHODIMP OnPropertyValueChanged(LPCWSTR deviceId, - const PROPERTYKEY key - ) final { - return S_OK; - } - - unsigned int getDefaultDeviceChangeCount() { - return defaultDeviceChangeCount; - } - - unsigned int getDeviceStateChangeCount() { - return deviceStateChangeCount; - } - -private: - LONG refCount = 0; - unsigned int defaultDeviceChangeCount = 0; - unsigned int deviceStateChangeCount = 0; -}; -_COM_SMARTPTR_TYPEDEF(NotificationClient, IID_IMMNotificationClient); - -class WasapiPlayer { -public: - using ChunkCompletedCallback = void(*)(WasapiPlayer* player, unsigned int id); - - WasapiPlayer(wchar_t* deviceName, WAVEFORMATEX format, ChunkCompletedCallback callback); - HRESULT open(bool force = false); - HRESULT feed(unsigned char* data, unsigned int size, unsigned int* id); - HRESULT stop(); - HRESULT sync(); - HRESULT idle(); - HRESULT pause(); - HRESULT resume(); - HRESULT setChannelVolume(unsigned int channel, float level); - - WAVEFORMATEX format; - -private: - void maybeFireCallback(); - void completeStop(); - - UINT64 framesToMs(UINT32 frames) { - return frames * 1000 / format.nSamplesPerSec; - } - - UINT64 getPlayPos(); - void waitUntilNeeded(UINT64 maxWait = INFINITE); - HRESULT getPreferredDevice(IMMDevicePtr& preferredDevice); - bool didPreferredDeviceBecomeAvailable(); - - enum class PlayState { - stopped, - playing, - stopping, - }; - - IAudioClientPtr client; - IAudioRenderClientPtr render; - IAudioClockPtr clock; - UINT32 bufferFrames; - std::wstring deviceName; - ChunkCompletedCallback callback; - PlayState playState = PlayState::stopped; - std::vector> feedEnds; - UINT64 clockFreq; - UINT32 sentFrames = 0; - unsigned int nextFeedId = 0; - AutoHandle wakeEvent; - unsigned int defaultDeviceChangeCount; - unsigned int deviceStateChangeCount; - bool isUsingPreferredDevice = false; -}; - -#ifdef __cplusplus -extern "C" { -#endif - - -#ifdef __cplusplus -} -#endif -#endif +#pragma once + +#ifndef WASAPI_H_ +#define WASAPI_H_ + +#if defined(_WIN32) +#ifdef WASAPI_EXPORTS +#define WASAPI_API __declspec(dllexport) +#else +#define WASAPI_API __declspec(dllimport) +#endif +#else +#define WASAPI_API +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + + +_COM_SMARTPTR_TYPEDEF(IMMDevice, IID_IMMDevice); +_COM_SMARTPTR_TYPEDEF(IMMDeviceCollection, IID_IMMDeviceCollection); +_COM_SMARTPTR_TYPEDEF(IMMDeviceEnumerator, IID_IMMDeviceEnumerator); +_COM_SMARTPTR_TYPEDEF(IAudioClient, IID_IAudioClient); +_COM_SMARTPTR_TYPEDEF(IAudioRenderClient, IID_IAudioRenderClient); +_COM_SMARTPTR_TYPEDEF(IAudioClock, IID_IAudioClock); +_COM_SMARTPTR_TYPEDEF(IAudioStreamVolume, IID_IAudioStreamVolume); +_COM_SMARTPTR_TYPEDEF(IPropertyStore, IID_IPropertyStore); + +class AutoHandle { +public: + AutoHandle() : handle(nullptr) {} + AutoHandle(HANDLE handle) : handle(handle) {} + + ~AutoHandle() { + if (handle) { + CloseHandle(handle); + } + } + + AutoHandle& operator=(HANDLE newHandle) { + if (handle) { + CloseHandle(handle); + } + handle = newHandle; + return *this; + } + + operator HANDLE() { + return handle; + } + +private: + HANDLE handle; +}; + +class NotificationClient : public IMMNotificationClient { +public: + ULONG STDMETHODCALLTYPE AddRef() override { + return InterlockedIncrement(&refCount); + } + + ULONG STDMETHODCALLTYPE Release() override { + LONG result = InterlockedDecrement(&refCount); + if (result == 0) { + delete this; + } + return result; + } + + STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) final { + if (riid == IID_IUnknown || riid == IID_IMMNotificationClient) { + AddRef(); + *ppvObject = (void*)this; + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, + LPCWSTR defaultDeviceId + ) final { + if (flow == eRender && role == eConsole) { + ++defaultDeviceChangeCount; + } + return S_OK; + } + + STDMETHODIMP OnDeviceAdded(LPCWSTR deviceId) final { + return S_OK; + } + + STDMETHODIMP OnDeviceRemoved(LPCWSTR deviceId) final { + return S_OK; + } + + STDMETHODIMP OnDeviceStateChanged(LPCWSTR deviceId, DWORD newState) final { + ++deviceStateChangeCount; + return S_OK; + } + + STDMETHODIMP OnPropertyValueChanged(LPCWSTR deviceId, + const PROPERTYKEY key + ) final { + return S_OK; + } + + unsigned int getDefaultDeviceChangeCount() { + return defaultDeviceChangeCount; + } + + unsigned int getDeviceStateChangeCount() { + return deviceStateChangeCount; + } + +private: + LONG refCount = 0; + unsigned int defaultDeviceChangeCount = 0; + unsigned int deviceStateChangeCount = 0; +}; +_COM_SMARTPTR_TYPEDEF(NotificationClient, IID_IMMNotificationClient); + +class WasapiPlayer { +public: + using ChunkCompletedCallback = void(*)(WasapiPlayer* player, unsigned int id); + + WasapiPlayer(wchar_t* deviceName, WAVEFORMATEX format, ChunkCompletedCallback callback); + HRESULT open(bool force = false); + HRESULT feed(unsigned char* data, unsigned int size, unsigned int* id); + HRESULT stop(); + HRESULT sync(); + HRESULT idle(); + HRESULT pause(); + HRESULT resume(); + HRESULT setChannelVolume(unsigned int channel, float level); + + WAVEFORMATEX format; + +private: + void maybeFireCallback(); + void completeStop(); + + UINT64 framesToMs(UINT32 frames) { + return frames * 1000 / format.nSamplesPerSec; + } + + UINT64 getPlayPos(); + void waitUntilNeeded(UINT64 maxWait = INFINITE); + HRESULT getPreferredDevice(IMMDevicePtr& preferredDevice); + bool didPreferredDeviceBecomeAvailable(); + + enum class PlayState { + stopped, + playing, + stopping, + }; + + IAudioClientPtr client; + IAudioRenderClientPtr render; + IAudioClockPtr clock; + UINT32 bufferFrames; + std::wstring deviceName; + ChunkCompletedCallback callback; + PlayState playState = PlayState::stopped; + std::vector> feedEnds; + UINT64 clockFreq; + UINT32 sentFrames = 0; + unsigned int nextFeedId = 0; + AutoHandle wakeEvent; + unsigned int defaultDeviceChangeCount; + unsigned int deviceStateChangeCount; + bool isUsingPreferredDevice = false; +}; + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/Examples/C/SRALExample.c b/Examples/C/SRALExample.c index fe44bba..ac21deb 100644 --- a/Examples/C/SRALExample.c +++ b/Examples/C/SRALExample.c @@ -1,65 +1,65 @@ -#include -#include -#define SRAL_STATIC -#include -#include -#ifdef _WIN32 -#include -#else -#include -#endif - -void sleep_ms(int milliseconds) { -#ifdef _WIN32 - Sleep(milliseconds); // Windows-specific function -#else - usleep(milliseconds * 1000); // usleep takes microseconds -#endif -} - -int main(void) { - char text[10000]; - // Initialize the SRAL library - if (!SRAL_Initialize(ENGINE_UIA)) { // So Microsoft UIAutomation provider can't speak in the terminal or none current process windows - printf("Failed to initialize SRAL library.\n"); - return 1; - } - SRAL_RegisterKeyboardHooks(); - // Speak some text - if (SRAL_GetEngineFeatures(0) & SUPPORTS_SPEECH) { - printf("Enter the text you want to be spoken:\n"); - scanf("%s", text); - SRAL_Speak(text, false); - } - - // Output text to a Braille display - if (SRAL_GetEngineFeatures(0) & SUPPORTS_BRAILLE) { - printf("Enter the text you want to be shown on braille display:\n"); - scanf("%s", text); - SRAL_Braille(text); - - } - - // Delay example - SRAL_Output("Delay example: Enter any text", false); - SRAL_Delay(5000); - SRAL_Output("Press enter to continue", false); - scanf("%s", text); - - SRAL_StopSpeech(); // Stops the delay thread - // Speech rate - if (SRAL_GetEngineFeatures(0) & SUPPORTS_SPEECH_RATE) { - - uint64_t rate = SRAL_GetRate(); - const uint64_t max_rate = rate + 10; - for (rate; rate < max_rate; rate++) { - SRAL_SetRate(rate); - SRAL_Speak(text, false); - sleep_ms(500); - } - } - // Uninitialize the SRAL library - SRAL_Uninitialize(); - - return 0; -} +#include +#include +#define SRAL_STATIC +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +void sleep_ms(int milliseconds) { +#ifdef _WIN32 + Sleep(milliseconds); // Windows-specific function +#else + usleep(milliseconds * 1000); // usleep takes microseconds +#endif +} + +int main(void) { + char text[10000]; + // Initialize the SRAL library + if (!SRAL_Initialize(ENGINE_UIA)) { // So Microsoft UIAutomation provider can't speak in the terminal or none current process windows + printf("Failed to initialize SRAL library.\n"); + return 1; + } + SRAL_RegisterKeyboardHooks(); + // Speak some text + if (SRAL_GetEngineFeatures(0) & SUPPORTS_SPEECH) { + printf("Enter the text you want to be spoken:\n"); + scanf("%s", text); + SRAL_Speak(text, false); + } + + // Output text to a Braille display + if (SRAL_GetEngineFeatures(0) & SUPPORTS_BRAILLE) { + printf("Enter the text you want to be shown on braille display:\n"); + scanf("%s", text); + SRAL_Braille(text); + + } + + // Delay example + SRAL_Output("Delay example: Enter any text", false); + SRAL_Delay(5000); + SRAL_Output("Press enter to continue", false); + scanf("%s", text); + + SRAL_StopSpeech(); // Stops the delay thread + // Speech rate + if (SRAL_GetEngineFeatures(0) & SUPPORTS_SPEECH_RATE) { + + uint64_t rate = SRAL_GetRate(); + const uint64_t max_rate = rate + 10; + for (rate; rate < max_rate; rate++) { + SRAL_SetRate(rate); + SRAL_Speak(text, false); + sleep_ms(500); + } + } + // Uninitialize the SRAL library + SRAL_Uninitialize(); + + return 0; +} diff --git a/Include/SRAL.h b/Include/SRAL.h index ed45e19..7310a4f 100644 --- a/Include/SRAL.h +++ b/Include/SRAL.h @@ -1,435 +1,435 @@ -#ifndef SRAL_H_ -#define SRAL_H_ -#pragma once -#ifdef _WIN32 -#if defined SRAL_EXPORT -#define SRAL_API __declspec(dllexport) -#elif defined (SRAL_STATIC) -#define SRAL_API -#else -#define SRAL_API __declspec(dllimport) -#endif -#else -#define SRAL_API -#endif -#ifdef __cplusplus -extern "C" { -#include -#endif -#include -#include - - /** - * @enum SRAL_Engines - * @brief Enumeration of available speech engines. - * - * This enumeration defines the identifiers for different speech engines - * supported by the SRAL library. - */ - - enum SRAL_Engines { - ENGINE_NONE = 0, - ENGINE_NVDA = 2, - ENGINE_SAPI = 4, - ENGINE_JAWS = 8, - ENGINE_SPEECH_DISPATCHER = 16, - ENGINE_UIA = 32, - ENGINE_AV_SPEECH = 64, - ENGINE_NARRATOR = 128 - }; - - /** - * @enum SRAL_SupportedFeatures - * @brief Enumeration of supported features in the engines. - * - * This enumeration defines the features supported by various speech engines. - */ - - enum SRAL_SupportedFeatures { - SUPPORTS_SPEECH = 128, - SUPPORTS_BRAILLE = 256, - SUPPORTS_SPEECH_RATE = 512, - SUPPORTS_SPEECH_VOLUME = 1024, - SUPPORTS_SELECT_VOICE = 2048, - SUPPORTS_PAUSE_SPEECH = 4096, - SUPPORTS_SSML = 8192 - }; - - /** -* @enum SRAL_EngineParams -* @brief Enumeration of engine parameters. -*/ - - enum SRAL_EngineParams { - SYMBOL_LEVEL, - SAPI_TRIM_THRESHOLD - }; - - - - /** - * @brief Speak the given text. - * @param text A pointer to the text string to be spoken. - * @param interrupt A flag indicating whether to interrupt the current speech. - * @return true if speaking was successful, false otherwise. - */ - - SRAL_API bool SRAL_Speak(const char* text, bool interrupt); - - - /** -* @brief Speak the given text using SSML tags. -* @param SSML A pointer to the valid SSML string to be spoken. -* @param interrupt A flag indicating whether to interrupt the current speech. -* @return true if speaking was successful, false otherwise. -*/ - - SRAL_API bool SRAL_SpeakSsml(const char* ssml, bool interrupt); - - - - - /** - * @brief Output text to a Braille display. - * @param text A pointer to the text string to be output in Braille. - * @return true if Braille output was successful, false otherwise. - */ - - SRAL_API bool SRAL_Braille(const char* text); - - /** - * @brief Output text using all currently supported speech engine methods. - * @param text A pointer to the text string to be output. - * @param interrupt A flag indicating whether to interrupt speech. - * @return true if output was successful, false otherwise. - */ - - SRAL_API bool SRAL_Output(const char* text, bool interrupt); - - - /** - * @brief Stop speech if it is active. - * @return true if speech was stopped successfully, false otherwise. - */ - - - SRAL_API bool SRAL_StopSpeech(void); - - /** -* @brief Pause speech if it is active and the current speech engine supports this. -* @return true if speech was paused successfully, false otherwise. -*/ - - - SRAL_API bool SRAL_PauseSpeech(void); - - - /** -* @brief Resume speech if it was active and the current speech engine supports this. -* @return true if speech was resumed successfully, false otherwise. -*/ - - - SRAL_API bool SRAL_ResumeSpeech(void); - - - - - /** - * @brief Get the current speech engine in use. - * @return The identifier of the current speech engine defined by the SRAL_Engines enumeration. - */ - - SRAL_API int SRAL_GetCurrentEngine(void); - - - /** - * @brief Get features supported by the specified engine. - * @param engine The identifier of the engine to query. Defaults to 0 (current engine). - * @return An integer representing the features supported by the engine defined by the SRAL_SupportedFeatures enumeration. - */ - - - SRAL_API int SRAL_GetEngineFeatures(int engine); - - /** -* @brief Set the parameter for the specified speech engine. -* @param engine The engine to set the param for. -* @param param The desired parameter. -* @param value The desired value. - -* @return true if the parameter was set successfully, false otherwise. -*/ - - SRAL_API bool SRAL_SetEngineParameter(int engine, int param, int value); - - - - - - /** - * @brief Initialize the library and optionally exclude certain engines. - * @param engines_exclude A bitmask specifying engines to exclude from initialization. Defaults to 0 (include all). - * @return true if initialization was successful, false otherwise. - */ - - - SRAL_API bool SRAL_Initialize(int engines_exclude); - - /** - * @brief Uninitialize the library, freeing resources. - */ - - SRAL_API void SRAL_Uninitialize(void); - - - /** - * @brief Set the speech volume level, if current speech engine supports this. - * @param value The desired volume level. - * @return true if the volume was set successfully, false otherwise. - */ - - - SRAL_API bool SRAL_SetVolume(uint64_t value); - - /** - * @brief Get the current speech volume level of the current speech engine. - * @return The current volume level. - */ - - SRAL_API uint64_t SRAL_GetVolume(void); - - - /** - * @brief Set the speech rate, if current engine supports this. - * @param value The desired speech rate. - * @return true if the speech rate was set successfully, false otherwise. - */ - - - SRAL_API bool SRAL_SetRate(uint64_t value); - - /** - * @brief Get the current speech rate of the current speech engine. - * @return The current speech rate. - */ - - SRAL_API uint64_t SRAL_GetRate(void); - - /** - * @brief Get the count of available voices supported by the current speech engine. - * @return The number of available voices. - */ - - SRAL_API uint64_t SRAL_GetVoiceCount(void); - - /** - * @brief Get the name of a voice by its index, if the current speech engine supports this. - * @param The index of a voice to get. - * @return A pointer to the name of the voice. - */ - - SRAL_API const char* SRAL_GetVoiceName(uint64_t index); - - /** - * @brief Set the currently selected voice by index, if the current speech engine supports this. - * @param index The index of a voice to set. - * @return true if the voice was set successfully, false otherwise. - */ - - SRAL_API bool SRAL_SetVoice(uint64_t index); - - - - - - /** - * Extended functions to perform operations with specific speech engines. - */ - - /** - * @brief Speak the given text with the specified engine. - * @param engine The engine to use for speaking. - * @param text A pointer to the text string to be spoken. - * @param interrupt A flag indicating whether to interrupt the current speech. - * @return true if speaking was successful, false otherwise. - */ - - SRAL_API bool SRAL_SpeakEx(int engine, const char* text, bool interrupt); - - /** - * @brief Speak the given text with the specified engine and using SSML tags. - * @param engine The engine to use for speaking. - * @param ssml A pointer to the valid SSML string to be spoken. - * @param interrupt A flag indicating whether to interrupt the current speech. - * @return true if speaking was successful, false otherwise. - */ - - SRAL_API bool SRAL_SpeakSsmlEx(int engine, const char* ssml, bool interrupt); - - - - - /** - * @brief Output text to a Braille display using the specified engine. - * @param engine The engine to use for Braille display output. - * @param text A pointer to the text string to be output in Braille display. - * @return true if Braille output was successful, false otherwise. - */ - - - SRAL_API bool SRAL_BrailleEx(int engine, const char* text); - - /** - * @brief Output text using the specified engine. - * @param engine The engine to use for output. - * @param text A pointer to the text string to be output. - * @param interrupt A flag indicating whether to interrupt the current speech. - * @return true if output was successful, false otherwise. - */ - - SRAL_API bool SRAL_OutputEx(int engine, const char* text, bool interrupt); - - - - /** - * @brief Stop speech for the specified engine. - * @param engine The engine to stop speech for. - * @return true if speech was stopped successfully, false otherwise. - */ - - - SRAL_API bool SRAL_StopSpeechEx(int engine); - - - /** -* @brief Pause speech for the specified engine. -* @param engine The engine to pause speech for. -* @return true if speech was paused successfully, false otherwise. -*/ - - - SRAL_API bool SRAL_PauseSpeechEx(int engine); - - - /** -* @brief Resume speech for the specified engine. -* @param engine The engine to resume speech for. -* @return true if speech was resumed successfully, false otherwise. -*/ - - - - SRAL_API bool SRAL_ResumeSpeechEx(int engine); - - - - /** - * @brief Set the volume level for the specified speech engine. - * @param engine The engine to set the volume for. - * @param value The desired volume level. - * @return true if the volume was set successfully, false otherwise. - */ - - SRAL_API bool SRAL_SetVolumeEx(int engine, uint64_t value); - - - /** - * @brief Get the current volume level for the specified engine. - * @param engine The engine to query. - * @return The current volume level for the engine. - */ - - - SRAL_API uint64_t SRAL_GetVolumeEx(int engine); - - /** - * @brief Set the speech rate for the specified engine. - * @param engine The engine to set the rate for. - * @param value The desired speech rate. - * @return true if the speech rate was set successfully, false otherwise. - */ - - SRAL_API bool SRAL_SetRateEx(int engine, uint64_t value); - - /** - * @brief Get the current speech rate for the specified engine. - * @param engine The engine to query. - * @return The current speech rate for the engine. - */ - - SRAL_API uint64_t SRAL_GetRateEx(int engine); - - /** - * @brief Get the count of available voices for the specified engine. - * @param engine The engine to query. - * @return The number of voices available for the speech engine. - */ - - SRAL_API uint64_t SRAL_GetVoiceCountEx(int engine); - - /** - * @brief Get the name of a voice for the specified engine by its index. - * @param engine The engine to query. - * @param index The index of the voice. - * @return A pointer to the name of the voice. - */ - - SRAL_API const char* SRAL_GetVoiceNameEx(int engine, uint64_t index); - - /** - * @brief Set the currently selected voice for the specified engine by index. - * @param engine The engine to set the voice for. - * @param index The index of the voice to set. - * @return true if the voice was set successfully, false otherwise. - */ - - SRAL_API bool SRAL_SetVoiceEx(int engine, uint64_t index); - - - - /** - * @brief Check if the library has been initialized. - * @return true if the library is initialized, false otherwise. - */ - - SRAL_API bool SRAL_IsInitialized(void); - - /** - - // *@brief Delayes the next speech or output operation by a given time. - * @param time A value in milliseconds. - - - */ - - - SRAL_API void SRAL_Delay(int time); - - - - - /** - *@brief Install speech interruption and pause keyboard hooks for speech engines other than screen readers, such as Microsoft SAPI 5 or SpeechDispatcher. - * These hooks work globally in any window. - * Ctrl - Interrupt, Shift - Pause. - * @return true if the hooks are successfully installed, false otherwise. - */ - - SRAL_API bool SRAL_RegisterKeyboardHooks(void); - - /** - *@brief Uninstall speech interruption and pause keyboard hooks. -*/ - - - SRAL_API void SRAL_UnregisterKeyboardHooks(void); - - - - -#ifdef __cplusplus -}// extern "C" -#endif - +#ifndef SRAL_H_ +#define SRAL_H_ +#pragma once +#ifdef _WIN32 +#if defined SRAL_EXPORT +#define SRAL_API __declspec(dllexport) +#elif defined (SRAL_STATIC) +#define SRAL_API +#else +#define SRAL_API __declspec(dllimport) +#endif +#else +#define SRAL_API +#endif +#ifdef __cplusplus +extern "C" { +#include +#endif +#include +#include + + /** + * @enum SRAL_Engines + * @brief Enumeration of available speech engines. + * + * This enumeration defines the identifiers for different speech engines + * supported by the SRAL library. + */ + + enum SRAL_Engines { + ENGINE_NONE = 0, + ENGINE_NVDA = 2, + ENGINE_SAPI = 4, + ENGINE_JAWS = 8, + ENGINE_SPEECH_DISPATCHER = 16, + ENGINE_UIA = 32, + ENGINE_AV_SPEECH = 64, + ENGINE_NARRATOR = 128 + }; + + /** + * @enum SRAL_SupportedFeatures + * @brief Enumeration of supported features in the engines. + * + * This enumeration defines the features supported by various speech engines. + */ + + enum SRAL_SupportedFeatures { + SUPPORTS_SPEECH = 128, + SUPPORTS_BRAILLE = 256, + SUPPORTS_SPEECH_RATE = 512, + SUPPORTS_SPEECH_VOLUME = 1024, + SUPPORTS_SELECT_VOICE = 2048, + SUPPORTS_PAUSE_SPEECH = 4096, + SUPPORTS_SSML = 8192 + }; + + /** +* @enum SRAL_EngineParams +* @brief Enumeration of engine parameters. +*/ + + enum SRAL_EngineParams { + SYMBOL_LEVEL, + SAPI_TRIM_THRESHOLD + }; + + + + /** + * @brief Speak the given text. + * @param text A pointer to the text string to be spoken. + * @param interrupt A flag indicating whether to interrupt the current speech. + * @return true if speaking was successful, false otherwise. + */ + + SRAL_API bool SRAL_Speak(const char* text, bool interrupt); + + + /** +* @brief Speak the given text using SSML tags. +* @param SSML A pointer to the valid SSML string to be spoken. +* @param interrupt A flag indicating whether to interrupt the current speech. +* @return true if speaking was successful, false otherwise. +*/ + + SRAL_API bool SRAL_SpeakSsml(const char* ssml, bool interrupt); + + + + + /** + * @brief Output text to a Braille display. + * @param text A pointer to the text string to be output in Braille. + * @return true if Braille output was successful, false otherwise. + */ + + SRAL_API bool SRAL_Braille(const char* text); + + /** + * @brief Output text using all currently supported speech engine methods. + * @param text A pointer to the text string to be output. + * @param interrupt A flag indicating whether to interrupt speech. + * @return true if output was successful, false otherwise. + */ + + SRAL_API bool SRAL_Output(const char* text, bool interrupt); + + + /** + * @brief Stop speech if it is active. + * @return true if speech was stopped successfully, false otherwise. + */ + + + SRAL_API bool SRAL_StopSpeech(void); + + /** +* @brief Pause speech if it is active and the current speech engine supports this. +* @return true if speech was paused successfully, false otherwise. +*/ + + + SRAL_API bool SRAL_PauseSpeech(void); + + + /** +* @brief Resume speech if it was active and the current speech engine supports this. +* @return true if speech was resumed successfully, false otherwise. +*/ + + + SRAL_API bool SRAL_ResumeSpeech(void); + + + + + /** + * @brief Get the current speech engine in use. + * @return The identifier of the current speech engine defined by the SRAL_Engines enumeration. + */ + + SRAL_API int SRAL_GetCurrentEngine(void); + + + /** + * @brief Get features supported by the specified engine. + * @param engine The identifier of the engine to query. Defaults to 0 (current engine). + * @return An integer representing the features supported by the engine defined by the SRAL_SupportedFeatures enumeration. + */ + + + SRAL_API int SRAL_GetEngineFeatures(int engine); + + /** +* @brief Set the parameter for the specified speech engine. +* @param engine The engine to set the param for. +* @param param The desired parameter. +* @param value The desired value. + +* @return true if the parameter was set successfully, false otherwise. +*/ + + SRAL_API bool SRAL_SetEngineParameter(int engine, int param, int value); + + + + + + /** + * @brief Initialize the library and optionally exclude certain engines. + * @param engines_exclude A bitmask specifying engines to exclude from initialization. Defaults to 0 (include all). + * @return true if initialization was successful, false otherwise. + */ + + + SRAL_API bool SRAL_Initialize(int engines_exclude); + + /** + * @brief Uninitialize the library, freeing resources. + */ + + SRAL_API void SRAL_Uninitialize(void); + + + /** + * @brief Set the speech volume level, if current speech engine supports this. + * @param value The desired volume level. + * @return true if the volume was set successfully, false otherwise. + */ + + + SRAL_API bool SRAL_SetVolume(uint64_t value); + + /** + * @brief Get the current speech volume level of the current speech engine. + * @return The current volume level. + */ + + SRAL_API uint64_t SRAL_GetVolume(void); + + + /** + * @brief Set the speech rate, if current engine supports this. + * @param value The desired speech rate. + * @return true if the speech rate was set successfully, false otherwise. + */ + + + SRAL_API bool SRAL_SetRate(uint64_t value); + + /** + * @brief Get the current speech rate of the current speech engine. + * @return The current speech rate. + */ + + SRAL_API uint64_t SRAL_GetRate(void); + + /** + * @brief Get the count of available voices supported by the current speech engine. + * @return The number of available voices. + */ + + SRAL_API uint64_t SRAL_GetVoiceCount(void); + + /** + * @brief Get the name of a voice by its index, if the current speech engine supports this. + * @param The index of a voice to get. + * @return A pointer to the name of the voice. + */ + + SRAL_API const char* SRAL_GetVoiceName(uint64_t index); + + /** + * @brief Set the currently selected voice by index, if the current speech engine supports this. + * @param index The index of a voice to set. + * @return true if the voice was set successfully, false otherwise. + */ + + SRAL_API bool SRAL_SetVoice(uint64_t index); + + + + + + /** + * Extended functions to perform operations with specific speech engines. + */ + + /** + * @brief Speak the given text with the specified engine. + * @param engine The engine to use for speaking. + * @param text A pointer to the text string to be spoken. + * @param interrupt A flag indicating whether to interrupt the current speech. + * @return true if speaking was successful, false otherwise. + */ + + SRAL_API bool SRAL_SpeakEx(int engine, const char* text, bool interrupt); + + /** + * @brief Speak the given text with the specified engine and using SSML tags. + * @param engine The engine to use for speaking. + * @param ssml A pointer to the valid SSML string to be spoken. + * @param interrupt A flag indicating whether to interrupt the current speech. + * @return true if speaking was successful, false otherwise. + */ + + SRAL_API bool SRAL_SpeakSsmlEx(int engine, const char* ssml, bool interrupt); + + + + + /** + * @brief Output text to a Braille display using the specified engine. + * @param engine The engine to use for Braille display output. + * @param text A pointer to the text string to be output in Braille display. + * @return true if Braille output was successful, false otherwise. + */ + + + SRAL_API bool SRAL_BrailleEx(int engine, const char* text); + + /** + * @brief Output text using the specified engine. + * @param engine The engine to use for output. + * @param text A pointer to the text string to be output. + * @param interrupt A flag indicating whether to interrupt the current speech. + * @return true if output was successful, false otherwise. + */ + + SRAL_API bool SRAL_OutputEx(int engine, const char* text, bool interrupt); + + + + /** + * @brief Stop speech for the specified engine. + * @param engine The engine to stop speech for. + * @return true if speech was stopped successfully, false otherwise. + */ + + + SRAL_API bool SRAL_StopSpeechEx(int engine); + + + /** +* @brief Pause speech for the specified engine. +* @param engine The engine to pause speech for. +* @return true if speech was paused successfully, false otherwise. +*/ + + + SRAL_API bool SRAL_PauseSpeechEx(int engine); + + + /** +* @brief Resume speech for the specified engine. +* @param engine The engine to resume speech for. +* @return true if speech was resumed successfully, false otherwise. +*/ + + + + SRAL_API bool SRAL_ResumeSpeechEx(int engine); + + + + /** + * @brief Set the volume level for the specified speech engine. + * @param engine The engine to set the volume for. + * @param value The desired volume level. + * @return true if the volume was set successfully, false otherwise. + */ + + SRAL_API bool SRAL_SetVolumeEx(int engine, uint64_t value); + + + /** + * @brief Get the current volume level for the specified engine. + * @param engine The engine to query. + * @return The current volume level for the engine. + */ + + + SRAL_API uint64_t SRAL_GetVolumeEx(int engine); + + /** + * @brief Set the speech rate for the specified engine. + * @param engine The engine to set the rate for. + * @param value The desired speech rate. + * @return true if the speech rate was set successfully, false otherwise. + */ + + SRAL_API bool SRAL_SetRateEx(int engine, uint64_t value); + + /** + * @brief Get the current speech rate for the specified engine. + * @param engine The engine to query. + * @return The current speech rate for the engine. + */ + + SRAL_API uint64_t SRAL_GetRateEx(int engine); + + /** + * @brief Get the count of available voices for the specified engine. + * @param engine The engine to query. + * @return The number of voices available for the speech engine. + */ + + SRAL_API uint64_t SRAL_GetVoiceCountEx(int engine); + + /** + * @brief Get the name of a voice for the specified engine by its index. + * @param engine The engine to query. + * @param index The index of the voice. + * @return A pointer to the name of the voice. + */ + + SRAL_API const char* SRAL_GetVoiceNameEx(int engine, uint64_t index); + + /** + * @brief Set the currently selected voice for the specified engine by index. + * @param engine The engine to set the voice for. + * @param index The index of the voice to set. + * @return true if the voice was set successfully, false otherwise. + */ + + SRAL_API bool SRAL_SetVoiceEx(int engine, uint64_t index); + + + + /** + * @brief Check if the library has been initialized. + * @return true if the library is initialized, false otherwise. + */ + + SRAL_API bool SRAL_IsInitialized(void); + + /** + + // *@brief Delayes the next speech or output operation by a given time. + * @param time A value in milliseconds. + + + */ + + + SRAL_API void SRAL_Delay(int time); + + + + + /** + *@brief Install speech interruption and pause keyboard hooks for speech engines other than screen readers, such as Microsoft SAPI 5 or SpeechDispatcher. + * These hooks work globally in any window. + * Ctrl - Interrupt, Shift - Pause. + * @return true if the hooks are successfully installed, false otherwise. + */ + + SRAL_API bool SRAL_RegisterKeyboardHooks(void); + + /** + *@brief Uninstall speech interruption and pause keyboard hooks. +*/ + + + SRAL_API void SRAL_UnregisterKeyboardHooks(void); + + + + +#ifdef __cplusplus +}// extern "C" +#endif + #endif // SRAL_H_ \ No newline at end of file diff --git a/LICENSE b/LICENSE index ab8ba01..a321700 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2024 M_maker - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2024 M_maker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 8fea7c2..5052e7b 100644 --- a/README.md +++ b/README.md @@ -1,163 +1,163 @@ -# SRAL -Screen Reader Abstraction Library -## Description -SRAL is a cross-platform library for output text using speech engines. - -## Platforms -SRAL is supported on Windows, MacOS and Linux platforms. - -## Header -See how to use SRAL in Include/SRAL.h - -## Compilation -SRAL can build using CMake into two libraries, static and dynamic. -Run these commands -``` -cmake . -B build -cmake --build build --config Release -``` - -You will also have an executable test to test the SRAL. - - -# Warning -To build on Linux you need to install libspeechd-dev and libx11-dev - - -## Usage - -To use the SRAL API in a C/C++ project, you need a statically linked or dynamically imported SRAL library, as well as a SRAL.h file with function declarations. -If you use SRAL as a static library for Windows, you need to define SRAL_STATIC in the SRAL.h before the include -``` -#define SRAL_STATIC -#include -``` - -## Bindings -SRAL also has Bindings, which is updated with new wrappers for programming languages. - -## Example -## C -``` -#include -#include -#define SRAL_STATIC -#include -#include -#ifdef _WIN32 -#include -#else -#include -#endif - -void sleep_ms(int milliseconds) { -#ifdef _WIN32 - Sleep(milliseconds); // Windows-specific function -#else - usleep(milliseconds * 1000); // usleep takes microseconds -#endif -} - -int main(void) { - char text[10000]; - // Initialize the SRAL library - if (!SRAL_Initialize(ENGINE_UIA)) { // So Microsoft UIAutomation provider can't speak in the terminal or none current process windows - printf("Failed to initialize SRAL library.\n"); - return 1; - } - SRAL_RegisterKeyboardHooks(); - // Speak some text - if (SRAL_GetEngineFeatures(0) & SUPPORTS_SPEECH) { - printf("Enter the text you want to be spoken:\n"); - scanf("%s", text); - SRAL_Speak(text, false); - } - - // Output text to a Braille display - if (SRAL_GetEngineFeatures(0) & SUPPORTS_BRAILLE) { - printf("Enter the text you want to be shown on braille display:\n"); - scanf("%s", text); - SRAL_Braille(text); - - } - - // Delay example - SRAL_Output("Delay example: Enter any text", false); - SRAL_Delay(5000); - SRAL_Output("Press enter to continue", false); - scanf("%s", text); - - SRAL_StopSpeech(); // Stops the delay thread - // Speech rate - if (SRAL_GetEngineFeatures(0) & SUPPORTS_SPEECH_RATE) { - - uint64_t rate = SRAL_GetRate(); - const uint64_t max_rate = rate + 10; - for (rate; rate < max_rate; rate++) { - SRAL_SetRate(rate); - SRAL_Speak(text, false); - sleep_ms(500); - } - } - // Uninitialize the SRAL library - SRAL_Uninitialize(); - - return 0; -} - -``` - -## Python -``` -import time - -import sral - -def sleep_ms(milliseconds): - time.sleep(milliseconds / 1000.0) # Convert milliseconds to seconds - -def main(): - text = "" - # Initialize the SRAL library - instance = sral.Sral(32) - - instance.register_keyboard_hooks() - - # Speak some text - if instance.get_engine_features(0) & 128: - text = input("Enter the text you want to be spoken:\n") - instance.speak(text, False) - - # Output text to a Braille display - if instance.get_engine_features(0) & 256: - text = input("Enter the text you want to be shown on braille display:\n") - instance.braille(text) - - # Delay example - instance.output("Delay example: Enter any text", False) - instance.delay(5000) - instance.output("Press enter to continue", False) - input() # Wait for user to press enter - - instance.stop_speech() # Stops the delay thread - - # Speech rate - if instance.get_engine_features(0) & 512: - rate = instance.get_rate() - max_rate = rate + 10 - for rate in range(rate, max_rate): - instance.set_rate(rate) - instance.speak(text, False) - sleep_ms(500) - - # Uninitialize the SRAL library - instance = None -if __name__ == "__main__": - main() # invoke_main() - -``` - - -## Additional info -For [NVDA](https://github.com/nvaccess/nvda) screen reader, you need to download the [Controller Client](https://www.nvaccess.org/files/nvda/releases/stable/). We don't support old client V 1. - +# SRAL +Screen Reader Abstraction Library +## Description +SRAL is a cross-platform library for output text using speech engines. + +## Platforms +SRAL is supported on Windows, MacOS and Linux platforms. + +## Header +See how to use SRAL in Include/SRAL.h + +## Compilation +SRAL can build using CMake into two libraries, static and dynamic. +Run these commands +``` +cmake . -B build +cmake --build build --config Release +``` + +You will also have an executable test to test the SRAL. + + +# Warning +To build on Linux you need to install libspeechd-dev and libx11-dev + + +## Usage + +To use the SRAL API in a C/C++ project, you need a statically linked or dynamically imported SRAL library, as well as a SRAL.h file with function declarations. +If you use SRAL as a static library for Windows, you need to define SRAL_STATIC in the SRAL.h before the include +``` +#define SRAL_STATIC +#include +``` + +## Bindings +SRAL also has Bindings, which is updated with new wrappers for programming languages. + +## Example +## C +``` +#include +#include +#define SRAL_STATIC +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +void sleep_ms(int milliseconds) { +#ifdef _WIN32 + Sleep(milliseconds); // Windows-specific function +#else + usleep(milliseconds * 1000); // usleep takes microseconds +#endif +} + +int main(void) { + char text[10000]; + // Initialize the SRAL library + if (!SRAL_Initialize(ENGINE_UIA)) { // So Microsoft UIAutomation provider can't speak in the terminal or none current process windows + printf("Failed to initialize SRAL library.\n"); + return 1; + } + SRAL_RegisterKeyboardHooks(); + // Speak some text + if (SRAL_GetEngineFeatures(0) & SUPPORTS_SPEECH) { + printf("Enter the text you want to be spoken:\n"); + scanf("%s", text); + SRAL_Speak(text, false); + } + + // Output text to a Braille display + if (SRAL_GetEngineFeatures(0) & SUPPORTS_BRAILLE) { + printf("Enter the text you want to be shown on braille display:\n"); + scanf("%s", text); + SRAL_Braille(text); + + } + + // Delay example + SRAL_Output("Delay example: Enter any text", false); + SRAL_Delay(5000); + SRAL_Output("Press enter to continue", false); + scanf("%s", text); + + SRAL_StopSpeech(); // Stops the delay thread + // Speech rate + if (SRAL_GetEngineFeatures(0) & SUPPORTS_SPEECH_RATE) { + + uint64_t rate = SRAL_GetRate(); + const uint64_t max_rate = rate + 10; + for (rate; rate < max_rate; rate++) { + SRAL_SetRate(rate); + SRAL_Speak(text, false); + sleep_ms(500); + } + } + // Uninitialize the SRAL library + SRAL_Uninitialize(); + + return 0; +} + +``` + +## Python +``` +import time + +import sral + +def sleep_ms(milliseconds): + time.sleep(milliseconds / 1000.0) # Convert milliseconds to seconds + +def main(): + text = "" + # Initialize the SRAL library + instance = sral.Sral(32) + + instance.register_keyboard_hooks() + + # Speak some text + if instance.get_engine_features(0) & 128: + text = input("Enter the text you want to be spoken:\n") + instance.speak(text, False) + + # Output text to a Braille display + if instance.get_engine_features(0) & 256: + text = input("Enter the text you want to be shown on braille display:\n") + instance.braille(text) + + # Delay example + instance.output("Delay example: Enter any text", False) + instance.delay(5000) + instance.output("Press enter to continue", False) + input() # Wait for user to press enter + + instance.stop_speech() # Stops the delay thread + + # Speech rate + if instance.get_engine_features(0) & 512: + rate = instance.get_rate() + max_rate = rate + 10 + for rate in range(rate, max_rate): + instance.set_rate(rate) + instance.speak(text, False) + sleep_ms(500) + + # Uninitialize the SRAL library + instance = None +if __name__ == "__main__": + main() # invoke_main() + +``` + + +## Additional info +For [NVDA](https://github.com/nvaccess/nvda) screen reader, you need to download the [Controller Client](https://www.nvaccess.org/files/nvda/releases/stable/). We don't support old client V 1. + diff --git a/SRC/AVSpeech.h b/SRC/AVSpeech.h index 5ca2112..9798363 100644 --- a/SRC/AVSpeech.h +++ b/SRC/AVSpeech.h @@ -1,47 +1,47 @@ -/* -Thanks Gruia for implementing AVSpeech. -*/ -#pragma once -#include "../Include/SRAL.h" -#include "Engine.h" -#include -class AVSpeechSynthesizerWrapper; - -class AVSpeech : public Engine { -public: - bool Speak(const char* text, bool interrupt)override; - bool SpeakSsml(const char* ssml, bool interrupt)override { - return false; - } - bool SetParameter(int param, int value)override { - return false; - } - - - bool Braille(const char* text)override { return false; } - bool StopSpeech()override; - bool PauseSpeech()override { return false; } - bool ResumeSpeech()override { return false; } - int GetNumber()override { - return ENGINE_AV_SPEECH; - } - bool GetActive()override; - bool Initialize()override; - bool Uninitialize()override; - int GetFeatures()override { - return SUPPORTS_SPEECH | SUPPORTS_SPEECH_RATE | SUPPORTS_SPEECH_VOLUME | SUPPORTS_SELECT_VOICE; - } - void SetVolume(uint64_t value)override; - uint64_t GetVolume()override; - void SetRate(uint64_t value)override; - uint64_t GetRate()override; - uint64_t GetVoiceCount()override; - const char* GetVoiceName(uint64_t index)override; - bool SetVoice(uint64_t index)override; - int GetKeyFlags()override { - return HANDLE_NONE; - } - -private: - AVSpeechSynthesizerWrapper* obj = nullptr; +/* +Thanks Gruia for implementing AVSpeech. +*/ +#pragma once +#include "../Include/SRAL.h" +#include "Engine.h" +#include +class AVSpeechSynthesizerWrapper; + +class AVSpeech : public Engine { +public: + bool Speak(const char* text, bool interrupt)override; + bool SpeakSsml(const char* ssml, bool interrupt)override { + return false; + } + bool SetParameter(int param, int value)override { + return false; + } + + + bool Braille(const char* text)override { return false; } + bool StopSpeech()override; + bool PauseSpeech()override { return false; } + bool ResumeSpeech()override { return false; } + int GetNumber()override { + return ENGINE_AV_SPEECH; + } + bool GetActive()override; + bool Initialize()override; + bool Uninitialize()override; + int GetFeatures()override { + return SUPPORTS_SPEECH | SUPPORTS_SPEECH_RATE | SUPPORTS_SPEECH_VOLUME | SUPPORTS_SELECT_VOICE; + } + void SetVolume(uint64_t value)override; + uint64_t GetVolume()override; + void SetRate(uint64_t value)override; + uint64_t GetRate()override; + uint64_t GetVoiceCount()override; + const char* GetVoiceName(uint64_t index)override; + bool SetVoice(uint64_t index)override; + int GetKeyFlags()override { + return HANDLE_NONE; + } + +private: + AVSpeechSynthesizerWrapper* obj = nullptr; }; \ No newline at end of file diff --git a/SRC/AVSpeech.mm b/SRC/AVSpeech.mm index a27debe..dc4675d 100644 --- a/SRC/AVSpeech.mm +++ b/SRC/AVSpeech.mm @@ -1,141 +1,141 @@ -#include "AVSpeech.h" -#include -#import -#import - -class AVSpeechSynthesizerWrapper { -public: - float rate; - float volume; - AVSpeechSynthesizer* synth; - AVSpeechSynthesisVoice* currentVoice; - AVSpeechUtterance* utterance; - - AVSpeechSynthesizerWrapper() : rate(0), volume(1), synth(nullptr), currentVoice(nullptr), utterance(nullptr) {} -AVSpeechSynthesisVoice* getVoiceObject(NSString* name){ - NSArray* voices = [AVSpeechSynthesisVoice speechVoices]; - for (AVSpeechSynthesisVoice* v in voices) { - if ([v.name isEqualToString : name]) return v; - } - return nil; -} - -bool Initialize() { - currentVoice = [AVSpeechSynthesisVoice voiceWithLanguage:@"en-US"]; //choosing english as a default language - utterance = [[AVSpeechUtterance alloc] initWithString:@""]; - rate = utterance.rate; - volume = utterance.volume; - synth = [[AVSpeechSynthesizer alloc] init]; - return true; -} -bool Uninitialize(){ - return true; -} -bool Speak(const char* text, bool interrupt){ - if (interrupt && synth.isSpeaking)[synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; - NSString *nstext = [NSString stringWithUTF8String:text]; - AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:nstext]; - utterance.rate = rate; - utterance.volume = volume; - utterance.voice = currentVoice; - this->utterance = utterance; - [synth speakUtterance:this->utterance]; - return synth.isSpeaking; -} -bool StopSpeech(){ - if (synth.isSpeaking) return [synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; - return false; -} -bool GetActive(){ - return synth != nil; -} -void SetVolume(uint64_t value){ - this->volume = value; -} -uint64_t GetVolume(){ - return this->volume; -} -void SetRate(uint64_t value){ - this->rate = value; -} -uint64_t GetRate(){ - return this->rate; -} -uint64_t GetVoiceCount(){ - NSArray *voices = [AVSpeechSynthesisVoice speechVoices]; - return voices.count; -} -const char* GetVoiceName(uint64_t index){ - NSArray *voices = [AVSpeechSynthesisVoice speechVoices]; - @try { - return [[voices objectAtIndex:index].name UTF8String]; - } @catch (NSException *exception) { - return ""; - } -} -bool SetVoice(uint64_t index){ - NSArray *voices = [AVSpeechSynthesisVoice speechVoices]; - AVSpeechSynthesisVoice *oldVoice = currentVoice; - @try { - currentVoice = [voices objectAtIndex:index]; - return true; - } @catch (NSException *exception) { - currentVoice = oldVoice; - return false; - } -} -}; - -bool AVSpeech::Initialize() { - obj = new AVSpeechSynthesizerWrapper(); - return obj->Initialize(); -} - -bool AVSpeech::Uninitialize() { - if (obj == nullptr) return false; // Check for nullptr - delete obj; - obj = nullptr; // Set to nullptr after deletion - return true; // Return true to indicate successful uninitialization -} - -bool AVSpeech::GetActive() { - return obj != nullptr && obj->GetActive(); -} - -bool AVSpeech::Speak(const char* text, bool interrupt) { - return obj->Speak(text, interrupt); -} - -bool AVSpeech::StopSpeech() { - return obj->StopSpeech(); -} - -void AVSpeech::SetVolume(uint64_t value) { - obj->SetVolume(value); -} - -uint64_t AVSpeech::GetVolume() { - return obj->GetVolume(); -} - -void AVSpeech::SetRate(uint64_t value) { - obj->SetRate(value); -} - -uint64_t AVSpeech::GetRate() { - return obj->GetRate(); -} - -uint64_t AVSpeech::GetVoiceCount() { - return obj->GetVoiceCount(); -} - -const char* AVSpeech::GetVoiceName(uint64_t index) { - return obj->GetVoiceName(index); -} - -bool AVSpeech::SetVoice(uint64_t index) { - return obj->SetVoice(index); -} - - +#include "AVSpeech.h" +#include +#import +#import + +class AVSpeechSynthesizerWrapper { +public: + float rate; + float volume; + AVSpeechSynthesizer* synth; + AVSpeechSynthesisVoice* currentVoice; + AVSpeechUtterance* utterance; + + AVSpeechSynthesizerWrapper() : rate(0), volume(1), synth(nullptr), currentVoice(nullptr), utterance(nullptr) {} +AVSpeechSynthesisVoice* getVoiceObject(NSString* name){ + NSArray* voices = [AVSpeechSynthesisVoice speechVoices]; + for (AVSpeechSynthesisVoice* v in voices) { + if ([v.name isEqualToString : name]) return v; + } + return nil; +} + +bool Initialize() { + currentVoice = [AVSpeechSynthesisVoice voiceWithLanguage:@"en-US"]; //choosing english as a default language + utterance = [[AVSpeechUtterance alloc] initWithString:@""]; + rate = utterance.rate; + volume = utterance.volume; + synth = [[AVSpeechSynthesizer alloc] init]; + return true; +} +bool Uninitialize(){ + return true; +} +bool Speak(const char* text, bool interrupt){ + if (interrupt && synth.isSpeaking)[synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + NSString *nstext = [NSString stringWithUTF8String:text]; + AVSpeechUtterance *utterance = [[AVSpeechUtterance alloc] initWithString:nstext]; + utterance.rate = rate; + utterance.volume = volume; + utterance.voice = currentVoice; + this->utterance = utterance; + [synth speakUtterance:this->utterance]; + return synth.isSpeaking; +} +bool StopSpeech(){ + if (synth.isSpeaking) return [synth stopSpeakingAtBoundary:AVSpeechBoundaryImmediate]; + return false; +} +bool GetActive(){ + return synth != nil; +} +void SetVolume(uint64_t value){ + this->volume = value; +} +uint64_t GetVolume(){ + return this->volume; +} +void SetRate(uint64_t value){ + this->rate = value; +} +uint64_t GetRate(){ + return this->rate; +} +uint64_t GetVoiceCount(){ + NSArray *voices = [AVSpeechSynthesisVoice speechVoices]; + return voices.count; +} +const char* GetVoiceName(uint64_t index){ + NSArray *voices = [AVSpeechSynthesisVoice speechVoices]; + @try { + return [[voices objectAtIndex:index].name UTF8String]; + } @catch (NSException *exception) { + return ""; + } +} +bool SetVoice(uint64_t index){ + NSArray *voices = [AVSpeechSynthesisVoice speechVoices]; + AVSpeechSynthesisVoice *oldVoice = currentVoice; + @try { + currentVoice = [voices objectAtIndex:index]; + return true; + } @catch (NSException *exception) { + currentVoice = oldVoice; + return false; + } +} +}; + +bool AVSpeech::Initialize() { + obj = new AVSpeechSynthesizerWrapper(); + return obj->Initialize(); +} + +bool AVSpeech::Uninitialize() { + if (obj == nullptr) return false; // Check for nullptr + delete obj; + obj = nullptr; // Set to nullptr after deletion + return true; // Return true to indicate successful uninitialization +} + +bool AVSpeech::GetActive() { + return obj != nullptr && obj->GetActive(); +} + +bool AVSpeech::Speak(const char* text, bool interrupt) { + return obj->Speak(text, interrupt); +} + +bool AVSpeech::StopSpeech() { + return obj->StopSpeech(); +} + +void AVSpeech::SetVolume(uint64_t value) { + obj->SetVolume(value); +} + +uint64_t AVSpeech::GetVolume() { + return obj->GetVolume(); +} + +void AVSpeech::SetRate(uint64_t value) { + obj->SetRate(value); +} + +uint64_t AVSpeech::GetRate() { + return obj->GetRate(); +} + +uint64_t AVSpeech::GetVoiceCount() { + return obj->GetVoiceCount(); +} + +const char* AVSpeech::GetVoiceName(uint64_t index) { + return obj->GetVoiceName(index); +} + +bool AVSpeech::SetVoice(uint64_t index) { + return obj->SetVoice(index); +} + + diff --git a/SRC/Encoding.cpp b/SRC/Encoding.cpp index 41a8eeb..8a855e7 100644 --- a/SRC/Encoding.cpp +++ b/SRC/Encoding.cpp @@ -1,68 +1,68 @@ -#include "Encoding.h" -#include -#ifdef _WIN32 -#include -#endif - -bool UnicodeConvert(const std::string& input, std::wstring& output) { -#ifdef _WIN32 - int size_needed = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, NULL, 0); - if (size_needed == 0) { - return false; - } - std::vector wide_string(size_needed); - if (MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, &wide_string[0], size_needed) == 0) { - return false; - } - output.assign(wide_string.begin(), wide_string.end() - 1); // Remove null terminator - return true; -#endif -} - -bool UnicodeConvert(const std::wstring& input, std::string& output) { -#ifdef _WIN32 - int size_needed = WideCharToMultiByte(CP_UTF8, 0, input.c_str(), -1, NULL, 0, NULL, NULL); - if (size_needed == 0) { - return false; - } - std::vector multi_byte_string(size_needed); - if (WideCharToMultiByte(CP_UTF8, 0, input.c_str(), -1, &multi_byte_string[0], size_needed, NULL, NULL) == 0) { - return false; - } - output.assign(multi_byte_string.begin(), multi_byte_string.end() - 1); // Remove null terminator - return true; -#endif -} - - - -void XmlEncode(std::string& data) { - std::string encoded; - encoded.reserve(data.size()); // Reserve space for efficiency - - for (char c : data) { - switch (c) { - case '&': - encoded += "&"; - break; - case '<': - encoded += "<"; - break; - case '>': - encoded += ">"; - break; - case '"': - encoded += """; - break; - case '\'': - encoded += "'"; - break; - default: - encoded += c; // Copy the character as is - break; - } - } - - data = encoded; // Update the original string with the encoded version -} - +#include "Encoding.h" +#include +#ifdef _WIN32 +#include +#endif + +bool UnicodeConvert(const std::string& input, std::wstring& output) { +#ifdef _WIN32 + int size_needed = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, NULL, 0); + if (size_needed == 0) { + return false; + } + std::vector wide_string(size_needed); + if (MultiByteToWideChar(CP_UTF8, 0, input.c_str(), -1, &wide_string[0], size_needed) == 0) { + return false; + } + output.assign(wide_string.begin(), wide_string.end() - 1); // Remove null terminator + return true; +#endif +} + +bool UnicodeConvert(const std::wstring& input, std::string& output) { +#ifdef _WIN32 + int size_needed = WideCharToMultiByte(CP_UTF8, 0, input.c_str(), -1, NULL, 0, NULL, NULL); + if (size_needed == 0) { + return false; + } + std::vector multi_byte_string(size_needed); + if (WideCharToMultiByte(CP_UTF8, 0, input.c_str(), -1, &multi_byte_string[0], size_needed, NULL, NULL) == 0) { + return false; + } + output.assign(multi_byte_string.begin(), multi_byte_string.end() - 1); // Remove null terminator + return true; +#endif +} + + + +void XmlEncode(std::string& data) { + std::string encoded; + encoded.reserve(data.size()); // Reserve space for efficiency + + for (char c : data) { + switch (c) { + case '&': + encoded += "&"; + break; + case '<': + encoded += "<"; + break; + case '>': + encoded += ">"; + break; + case '"': + encoded += """; + break; + case '\'': + encoded += "'"; + break; + default: + encoded += c; // Copy the character as is + break; + } + } + + data = encoded; // Update the original string with the encoded version +} + diff --git a/SRC/Encoding.h b/SRC/Encoding.h index 2d9e2c0..ff57169 100644 --- a/SRC/Encoding.h +++ b/SRC/Encoding.h @@ -1,8 +1,8 @@ -#ifndef ENCODING_H_ -#define ENCODING_H_ -#pragma once -#include -bool UnicodeConvert(const std::string& input, std::wstring& output); -bool UnicodeConvert(const std::wstring& input, std::string& output); -void XmlEncode(std::string& data); +#ifndef ENCODING_H_ +#define ENCODING_H_ +#pragma once +#include +bool UnicodeConvert(const std::string& input, std::wstring& output); +bool UnicodeConvert(const std::wstring& input, std::string& output); +void XmlEncode(std::string& data); #endif // ENCODING_H \ No newline at end of file diff --git a/SRC/Engine.h b/SRC/Engine.h index c2c3aa1..b838ea6 100644 --- a/SRC/Engine.h +++ b/SRC/Engine.h @@ -1,36 +1,36 @@ -#ifndef SCREENREADER_H_ -#define SCREENREADER_H_ -#pragma once -#include -enum KeyboardFlags { - HANDLE_NONE = 0, - HANDLE_INTERRUPT = 2, - HANDLE_PAUSE_RESUME = 4 -}; - -class Engine { -public: - virtual bool Speak(const char* text, bool interrupt) = 0; - virtual bool SpeakSsml(const char* ssml, bool interrupt) = 0; - - virtual bool Braille(const char* text) = 0; - virtual bool StopSpeech() = 0; - virtual bool PauseSpeech() = 0; - virtual bool ResumeSpeech() = 0; - virtual int GetNumber() = 0; - virtual bool GetActive() = 0; - virtual int GetFeatures() = 0; - virtual bool Initialize() = 0; - virtual bool Uninitialize() = 0; - virtual void SetVolume(uint64_t value) = 0; - virtual uint64_t GetVolume() = 0; - virtual void SetRate(uint64_t value) = 0; - virtual uint64_t GetRate() = 0; - virtual uint64_t GetVoiceCount() = 0; - virtual const char* GetVoiceName(uint64_t index) = 0; - virtual bool SetVoice(uint64_t index) = 0; - virtual int GetKeyFlags() = 0; - virtual bool SetParameter(int param, int value) = 0; - bool paused; -}; +#ifndef SCREENREADER_H_ +#define SCREENREADER_H_ +#pragma once +#include +enum KeyboardFlags { + HANDLE_NONE = 0, + HANDLE_INTERRUPT = 2, + HANDLE_PAUSE_RESUME = 4 +}; + +class Engine { +public: + virtual bool Speak(const char* text, bool interrupt) = 0; + virtual bool SpeakSsml(const char* ssml, bool interrupt) = 0; + + virtual bool Braille(const char* text) = 0; + virtual bool StopSpeech() = 0; + virtual bool PauseSpeech() = 0; + virtual bool ResumeSpeech() = 0; + virtual int GetNumber() = 0; + virtual bool GetActive() = 0; + virtual int GetFeatures() = 0; + virtual bool Initialize() = 0; + virtual bool Uninitialize() = 0; + virtual void SetVolume(uint64_t value) = 0; + virtual uint64_t GetVolume() = 0; + virtual void SetRate(uint64_t value) = 0; + virtual uint64_t GetRate() = 0; + virtual uint64_t GetVoiceCount() = 0; + virtual const char* GetVoiceName(uint64_t index) = 0; + virtual bool SetVoice(uint64_t index) = 0; + virtual int GetKeyFlags() = 0; + virtual bool SetParameter(int param, int value) = 0; + bool paused; +}; #endif \ No newline at end of file diff --git a/SRC/Jaws.cpp b/SRC/Jaws.cpp index f2d2d1c..e824eb4 100644 --- a/SRC/Jaws.cpp +++ b/SRC/Jaws.cpp @@ -1,56 +1,56 @@ -#ifdef _WIN32 -#include "Encoding.h" -#include "Jaws.h" -#include -bool Jaws::Initialize() { - HRESULT hr = CoCreateInstance(CLSID_JawsApi, NULL, CLSCTX_INPROC_SERVER, IID_IJawsApi, (void**)&JawsAPI); - if (JawsAPI || FAILED(hr)) { - return false; - } - return true; -} -bool Jaws::Uninitialize() { - if (JawsAPI != nullptr) { - JawsAPI->Release(); - JawsAPI = NULL; - } - return true; -} -bool Jaws::GetActive() { - return (!!FindWindowW(L"JFWUI2", NULL)); -} -bool Jaws::Speak(const char* text, bool interrupt) { - if (!GetActive() || !JawsAPI)return false; - if (interrupt)JawsAPI->StopSpeech(); - std::wstring str; - UnicodeConvert(text, str); - const BSTR bstr = SysAllocString(str.c_str()); - VARIANT_BOOL result = VARIANT_FALSE; - const VARIANT_BOOL flush = interrupt ? VARIANT_TRUE : VARIANT_FALSE; - const bool succeeded = SUCCEEDED(JawsAPI->SayString(bstr, flush, &result)); - SysFreeString(bstr); - return (succeeded && result == VARIANT_TRUE); -} -bool Jaws::Braille(const char* text) { - if (!GetActive() || !JawsAPI)return false; - std::wstring wstr; - UnicodeConvert(text, wstr); - std::wstring::size_type i = wstr.find_first_of(L"\""); - while (i != std::wstring::npos) { - wstr[i] = L'\''; - i = wstr.find_first_of(L"\"", i + 1); - } - wstr.insert(0, L"BrailleString(\""); - wstr.append(L"\")"); - const BSTR bstr = SysAllocString(wstr.c_str()); - VARIANT_BOOL result = VARIANT_FALSE; - SysFreeString(bstr); - const bool succeeded = SUCCEEDED(JawsAPI->RunFunction(bstr, &result)); - return (succeeded && result == VARIANT_TRUE); -} - -bool Jaws::StopSpeech() { - if (!GetActive() || !JawsAPI)return false; - return SUCCEEDED(JawsAPI->StopSpeech()); -} +#ifdef _WIN32 +#include "Encoding.h" +#include "Jaws.h" +#include +bool Jaws::Initialize() { + HRESULT hr = CoCreateInstance(CLSID_JawsApi, NULL, CLSCTX_INPROC_SERVER, IID_IJawsApi, (void**)&JawsAPI); + if (JawsAPI || FAILED(hr)) { + return false; + } + return true; +} +bool Jaws::Uninitialize() { + if (JawsAPI != nullptr) { + JawsAPI->Release(); + JawsAPI = NULL; + } + return true; +} +bool Jaws::GetActive() { + return (!!FindWindowW(L"JFWUI2", NULL)); +} +bool Jaws::Speak(const char* text, bool interrupt) { + if (!GetActive() || !JawsAPI)return false; + if (interrupt)JawsAPI->StopSpeech(); + std::wstring str; + UnicodeConvert(text, str); + const BSTR bstr = SysAllocString(str.c_str()); + VARIANT_BOOL result = VARIANT_FALSE; + const VARIANT_BOOL flush = interrupt ? VARIANT_TRUE : VARIANT_FALSE; + const bool succeeded = SUCCEEDED(JawsAPI->SayString(bstr, flush, &result)); + SysFreeString(bstr); + return (succeeded && result == VARIANT_TRUE); +} +bool Jaws::Braille(const char* text) { + if (!GetActive() || !JawsAPI)return false; + std::wstring wstr; + UnicodeConvert(text, wstr); + std::wstring::size_type i = wstr.find_first_of(L"\""); + while (i != std::wstring::npos) { + wstr[i] = L'\''; + i = wstr.find_first_of(L"\"", i + 1); + } + wstr.insert(0, L"BrailleString(\""); + wstr.append(L"\")"); + const BSTR bstr = SysAllocString(wstr.c_str()); + VARIANT_BOOL result = VARIANT_FALSE; + SysFreeString(bstr); + const bool succeeded = SUCCEEDED(JawsAPI->RunFunction(bstr, &result)); + return (succeeded && result == VARIANT_TRUE); +} + +bool Jaws::StopSpeech() { + if (!GetActive() || !JawsAPI)return false; + return SUCCEEDED(JawsAPI->StopSpeech()); +} #endif \ No newline at end of file diff --git a/SRC/Jaws.h b/SRC/Jaws.h index adbc732..35aa56d 100644 --- a/SRC/Jaws.h +++ b/SRC/Jaws.h @@ -1,51 +1,51 @@ -#ifndef JAWS_H_ -#define JAWS_H_ -#pragma once -#include "../Dep/fsapi.h" -#include "../Include/SRAL.h" -#include "Engine.h" -#include -class Jaws : public Engine { -public: - bool Speak(const char* text, bool interrupt)override; - bool SpeakSsml(const char* ssml, bool interrupt)override { - return false; - } - bool SetParameter(int param, int value)override { - return false; - } - - bool Braille(const char* text)override; - bool StopSpeech()override; - bool PauseSpeech()override { return false; } - bool ResumeSpeech()override { return false; } - int GetNumber()override { - return ENGINE_JAWS; - } - bool GetActive()override; - bool Initialize()override; - bool Uninitialize()override; - int GetFeatures()override { - return SUPPORTS_SPEECH | SUPPORTS_BRAILLE; - } - void SetVolume(uint64_t)override { return; } - uint64_t GetVolume()override { return 0; } - void SetRate(uint64_t)override { return; } - uint64_t GetRate()override { return 0; } - uint64_t GetVoiceCount()override { - return 0; - } - const char* GetVoiceName(uint64_t index)override { - return nullptr; - } - bool SetVoice(uint64_t index)override { - return false; - } - int GetKeyFlags()override { - return HANDLE_NONE; - } - -private: - IJawsApi* JawsAPI = nullptr; -}; +#ifndef JAWS_H_ +#define JAWS_H_ +#pragma once +#include "../Dep/fsapi.h" +#include "../Include/SRAL.h" +#include "Engine.h" +#include +class Jaws : public Engine { +public: + bool Speak(const char* text, bool interrupt)override; + bool SpeakSsml(const char* ssml, bool interrupt)override { + return false; + } + bool SetParameter(int param, int value)override { + return false; + } + + bool Braille(const char* text)override; + bool StopSpeech()override; + bool PauseSpeech()override { return false; } + bool ResumeSpeech()override { return false; } + int GetNumber()override { + return ENGINE_JAWS; + } + bool GetActive()override; + bool Initialize()override; + bool Uninitialize()override; + int GetFeatures()override { + return SUPPORTS_SPEECH | SUPPORTS_BRAILLE; + } + void SetVolume(uint64_t)override { return; } + uint64_t GetVolume()override { return 0; } + void SetRate(uint64_t)override { return; } + uint64_t GetRate()override { return 0; } + uint64_t GetVoiceCount()override { + return 0; + } + const char* GetVoiceName(uint64_t index)override { + return nullptr; + } + bool SetVoice(uint64_t index)override { + return false; + } + int GetKeyFlags()override { + return HANDLE_NONE; + } + +private: + IJawsApi* JawsAPI = nullptr; +}; #endif \ No newline at end of file diff --git a/SRC/NVDA.cpp b/SRC/NVDA.cpp index 0ce60e2..9d78921 100644 --- a/SRC/NVDA.cpp +++ b/SRC/NVDA.cpp @@ -1,101 +1,101 @@ -#include "Encoding.h" -#include "NVDA.h" -#include - -bool NVDA::Initialize() { - lib = LoadLibraryW(L"nvdaControllerClient.dll"); - if (lib == nullptr)return false; - nvdaController_speakText = (NVDAController_speakText)GetProcAddress(lib, "nvdaController_speakText"); - nvdaController_brailleMessage = (NVDAController_brailleMessage)GetProcAddress(lib, "nvdaController_brailleMessage"); - nvdaController_cancelSpeech = (NVDAController_cancelSpeech)GetProcAddress(lib, "nvdaController_cancelSpeech"); - nvdaController_testIfRunning = (NVDAController_testIfRunning)GetProcAddress(lib, "nvdaController_testIfRunning"); - nvdaController_speakSsml = (NVDAController_speakSsml)GetProcAddress(lib, "nvdaController_speakSsml"); - return true; -} -bool NVDA::Uninitialize() { - if (lib == nullptr)return false; - FreeLibrary(lib); - nvdaController_speakText = nullptr; - nvdaController_brailleMessage = nullptr; - nvdaController_cancelSpeech = nullptr; - nvdaController_testIfRunning = nullptr; - nvdaController_speakSsml = nullptr; - - return true; -} -bool NVDA::GetActive() { - if (lib == nullptr) return false; - if (nvdaController_testIfRunning) return (!!FindWindowW(L"wxWindowClassNR", L"NVDA") && nvdaController_testIfRunning() == 0); - return false; -} -bool NVDA::Speak(const char* text, bool interrupt) { - if (!GetActive())return false; - if (interrupt) - nvdaController_cancelSpeech(); - std::string text_str(text); - XmlEncode(text_str); - std::string final = "" + text_str + ""; - - std::wstring out_ssml; - UnicodeConvert(final, out_ssml); - error_status_t result = nvdaController_speakSsml(out_ssml.c_str(), this->symbolLevel, 0, true); - if (result == 1717) { - std::wstring out; - UnicodeConvert(text, out); - return nvdaController_speakText(out.c_str()) == 0; - } - else { - return result == 0; - } - return false; -} -bool NVDA::SpeakSsml(const char* ssml, bool interrupt) { - if (!GetActive())return false; - if (interrupt) - nvdaController_cancelSpeech(); - std::string text_str(ssml); - std::wstring out; - UnicodeConvert(ssml, out); - return nvdaController_speakSsml(out.c_str(), this->symbolLevel, 0, true) == 0; -} - -bool NVDA::SetParameter(int param, int value) { - switch (param) { - case SYMBOL_LEVEL: - this->symbolLevel = value; - break; - default: - return false; - } - return true; -} - -bool NVDA::Braille(const char* text) { - if (!GetActive())return false; - std::wstring out; - UnicodeConvert(text, out); - return nvdaController_brailleMessage(out.c_str()) == 0; -} -bool NVDA::StopSpeech() { - if (!GetActive())return false; - return nvdaController_cancelSpeech() == 0; -} -bool NVDA::PauseSpeech() { - if (!GetActive())return false; - INPUT input[2] = {}; - - input[0].type = INPUT_KEYBOARD; - input[0].ki.wVk = VK_SHIFT; - input[0].ki.dwFlags = 0; - - input[1].type = INPUT_KEYBOARD; - input[1].ki.wVk = VK_SHIFT; - input[1].ki.dwFlags = KEYEVENTF_KEYUP; - - SendInput(2, input, sizeof(INPUT)); - - return true; -} -bool NVDA::ResumeSpeech() { - return PauseSpeech(); // Don't know how to do it -} +#include "Encoding.h" +#include "NVDA.h" +#include + +bool NVDA::Initialize() { + lib = LoadLibraryW(L"nvdaControllerClient.dll"); + if (lib == nullptr)return false; + nvdaController_speakText = (NVDAController_speakText)GetProcAddress(lib, "nvdaController_speakText"); + nvdaController_brailleMessage = (NVDAController_brailleMessage)GetProcAddress(lib, "nvdaController_brailleMessage"); + nvdaController_cancelSpeech = (NVDAController_cancelSpeech)GetProcAddress(lib, "nvdaController_cancelSpeech"); + nvdaController_testIfRunning = (NVDAController_testIfRunning)GetProcAddress(lib, "nvdaController_testIfRunning"); + nvdaController_speakSsml = (NVDAController_speakSsml)GetProcAddress(lib, "nvdaController_speakSsml"); + return true; +} +bool NVDA::Uninitialize() { + if (lib == nullptr)return false; + FreeLibrary(lib); + nvdaController_speakText = nullptr; + nvdaController_brailleMessage = nullptr; + nvdaController_cancelSpeech = nullptr; + nvdaController_testIfRunning = nullptr; + nvdaController_speakSsml = nullptr; + + return true; +} +bool NVDA::GetActive() { + if (lib == nullptr) return false; + if (nvdaController_testIfRunning) return (!!FindWindowW(L"wxWindowClassNR", L"NVDA") && nvdaController_testIfRunning() == 0); + return false; +} +bool NVDA::Speak(const char* text, bool interrupt) { + if (!GetActive())return false; + if (interrupt) + nvdaController_cancelSpeech(); + std::string text_str(text); + XmlEncode(text_str); + std::string final = "" + text_str + ""; + + std::wstring out_ssml; + UnicodeConvert(final, out_ssml); + error_status_t result = nvdaController_speakSsml(out_ssml.c_str(), this->symbolLevel, 0, true); + if (result == 1717) { + std::wstring out; + UnicodeConvert(text, out); + return nvdaController_speakText(out.c_str()) == 0; + } + else { + return result == 0; + } + return false; +} +bool NVDA::SpeakSsml(const char* ssml, bool interrupt) { + if (!GetActive())return false; + if (interrupt) + nvdaController_cancelSpeech(); + std::string text_str(ssml); + std::wstring out; + UnicodeConvert(ssml, out); + return nvdaController_speakSsml(out.c_str(), this->symbolLevel, 0, true) == 0; +} + +bool NVDA::SetParameter(int param, int value) { + switch (param) { + case SYMBOL_LEVEL: + this->symbolLevel = value; + break; + default: + return false; + } + return true; +} + +bool NVDA::Braille(const char* text) { + if (!GetActive())return false; + std::wstring out; + UnicodeConvert(text, out); + return nvdaController_brailleMessage(out.c_str()) == 0; +} +bool NVDA::StopSpeech() { + if (!GetActive())return false; + return nvdaController_cancelSpeech() == 0; +} +bool NVDA::PauseSpeech() { + if (!GetActive())return false; + INPUT input[2] = {}; + + input[0].type = INPUT_KEYBOARD; + input[0].ki.wVk = VK_SHIFT; + input[0].ki.dwFlags = 0; + + input[1].type = INPUT_KEYBOARD; + input[1].ki.wVk = VK_SHIFT; + input[1].ki.dwFlags = KEYEVENTF_KEYUP; + + SendInput(2, input, sizeof(INPUT)); + + return true; +} +bool NVDA::ResumeSpeech() { + return PauseSpeech(); // Don't know how to do it +} diff --git a/SRC/NVDA.h b/SRC/NVDA.h index e01e027..9d627e4 100644 --- a/SRC/NVDA.h +++ b/SRC/NVDA.h @@ -1,62 +1,62 @@ -#ifndef NVDA_H_ -#define NVDA_H_ -#ifdef _WIN32 -#pragma once -#include "../Include/SRAL.h" -#include "Engine.h" -#include -class NVDA : public Engine { -public: - bool Speak(const char* text, bool interrupt)override; - bool SpeakSsml(const char* ssml, bool interrupt)override; - bool SetParameter(int param, int value)override; - bool Braille(const char* text)override; - bool StopSpeech()override; - bool PauseSpeech()override; - bool ResumeSpeech()override; - int GetNumber()override { - return ENGINE_NVDA; - } - bool GetActive()override; - bool Initialize()override; - bool Uninitialize()override; - int GetFeatures()override { - return SUPPORTS_SPEECH | SUPPORTS_BRAILLE | SUPPORTS_PAUSE_SPEECH; - } - void SetVolume(uint64_t)override { return; } - uint64_t GetVolume()override { return 0; } - void SetRate(uint64_t)override { return; } - uint64_t GetRate()override { return 0; } - uint64_t GetVoiceCount()override { - return 0; - } - const char* GetVoiceName(uint64_t index)override { - return nullptr; - } - bool SetVoice(uint64_t index)override { - return false; - } - - int GetKeyFlags()override { - return HANDLE_NONE; - } - -private: - HINSTANCE lib = nullptr; - typedef error_status_t(__stdcall* NVDAController_speakText)(const wchar_t*); - typedef error_status_t(__stdcall* NVDAController_brailleMessage)(const wchar_t*); - typedef error_status_t(__stdcall* NVDAController_cancelSpeech)(); - typedef error_status_t(__stdcall* NVDAController_testIfRunning)(); - typedef error_status_t(__stdcall* NVDAController_speakSsml)(const wchar_t*, int, int, int); - - NVDAController_speakText nvdaController_speakText = nullptr; - NVDAController_brailleMessage nvdaController_brailleMessage = nullptr; - NVDAController_cancelSpeech nvdaController_cancelSpeech = nullptr; - NVDAController_testIfRunning nvdaController_testIfRunning = nullptr; - NVDAController_speakSsml nvdaController_speakSsml = nullptr; - int symbolLevel = -1; - - -}; -#endif +#ifndef NVDA_H_ +#define NVDA_H_ +#ifdef _WIN32 +#pragma once +#include "../Include/SRAL.h" +#include "Engine.h" +#include +class NVDA : public Engine { +public: + bool Speak(const char* text, bool interrupt)override; + bool SpeakSsml(const char* ssml, bool interrupt)override; + bool SetParameter(int param, int value)override; + bool Braille(const char* text)override; + bool StopSpeech()override; + bool PauseSpeech()override; + bool ResumeSpeech()override; + int GetNumber()override { + return ENGINE_NVDA; + } + bool GetActive()override; + bool Initialize()override; + bool Uninitialize()override; + int GetFeatures()override { + return SUPPORTS_SPEECH | SUPPORTS_BRAILLE | SUPPORTS_PAUSE_SPEECH; + } + void SetVolume(uint64_t)override { return; } + uint64_t GetVolume()override { return 0; } + void SetRate(uint64_t)override { return; } + uint64_t GetRate()override { return 0; } + uint64_t GetVoiceCount()override { + return 0; + } + const char* GetVoiceName(uint64_t index)override { + return nullptr; + } + bool SetVoice(uint64_t index)override { + return false; + } + + int GetKeyFlags()override { + return HANDLE_NONE; + } + +private: + HINSTANCE lib = nullptr; + typedef error_status_t(__stdcall* NVDAController_speakText)(const wchar_t*); + typedef error_status_t(__stdcall* NVDAController_brailleMessage)(const wchar_t*); + typedef error_status_t(__stdcall* NVDAController_cancelSpeech)(); + typedef error_status_t(__stdcall* NVDAController_testIfRunning)(); + typedef error_status_t(__stdcall* NVDAController_speakSsml)(const wchar_t*, int, int, int); + + NVDAController_speakText nvdaController_speakText = nullptr; + NVDAController_brailleMessage nvdaController_brailleMessage = nullptr; + NVDAController_cancelSpeech nvdaController_cancelSpeech = nullptr; + NVDAController_testIfRunning nvdaController_testIfRunning = nullptr; + NVDAController_speakSsml nvdaController_speakSsml = nullptr; + int symbolLevel = -1; + + +}; +#endif #endif \ No newline at end of file diff --git a/SRC/SAPI.cpp b/SRC/SAPI.cpp index bd3d71c..937fa8a 100644 --- a/SRC/SAPI.cpp +++ b/SRC/SAPI.cpp @@ -1,215 +1,215 @@ -#ifdef _WIN32 -#include "SAPI.h" -#include -#include -#include - - -static char* trim(char* data, unsigned long* size, WAVEFORMATEX* wfx, int threshold) { - int channels = wfx->nChannels; - int bytesPerSample = wfx->wBitsPerSample / 8; - int samplesPerFrame = channels * bytesPerSample; - int numSamples = *size / samplesPerFrame; - int startIndex = 0; - int endIndex = numSamples - 1; - - for (int i = 0; i < numSamples; i++) { - int maxAbsValue = 0; - for (int j = 0; j < channels; j++) { - int absValue = abs(static_cast(data[i * samplesPerFrame + j])); - if (absValue > maxAbsValue) { - maxAbsValue = absValue; - } - } - if (maxAbsValue >= threshold) { - startIndex = i; - break; - } - } - - for (int i = numSamples - 1; i >= 0; i--) { - int maxAbsValue = 0; - for (int j = 0; j < channels; j++) { - int absValue = abs(static_cast(data[i * samplesPerFrame + j])); - if (absValue > maxAbsValue) { - maxAbsValue = absValue; - } - } - if (maxAbsValue >= threshold) { - endIndex = i; - break; - } - } - - int trimmedSize = (endIndex - startIndex + 1) * samplesPerFrame; - char* trimmedData = new char[trimmedSize]; - memcpy(trimmedData, data + startIndex * samplesPerFrame, trimmedSize); - *size = trimmedSize; - return trimmedData; -} - - -struct PCMData { - unsigned char* data; - unsigned long size; -}; - -std::vector g_dataQueue; -bool g_threadStarted = false; -static void sapi_thread(WasapiPlayer* player) { - if (player == nullptr) { - g_threadStarted = false; - } - HRESULT hr; - while (g_threadStarted) { - Sleep(1); - for (uint64_t i = 0; i < g_dataQueue.size(); ++i) { - - hr = player->feed(g_dataQueue[i].data, g_dataQueue[i].size, nullptr); - if (FAILED(hr))continue; - hr = player->sync(); - if (FAILED(hr))continue; - } - if (g_dataQueue.size() > 0) g_dataQueue.clear(); - } -} -bool SAPI::Initialize() { - instance = new blastspeak; - - if (blastspeak_initialize(instance) == 0) { - delete instance; - instance = nullptr; - return false; - } - CoInitialize(nullptr); - wchar_t device[] = L""; - WasapiPlayer::ChunkCompletedCallback callback = nullptr; - - wfx.wFormatTag = WAVE_FORMAT_PCM; - wfx.nChannels = instance->channels; - wfx.nSamplesPerSec = instance->sample_rate; - wfx.wBitsPerSample = instance->bits_per_sample; - wfx.nBlockAlign = (wfx.nChannels * wfx.wBitsPerSample) / 8; - wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; - wfx.cbSize = 0; - player = new WasapiPlayer(device, wfx, callback); - HRESULT hr = player->open(); - if (FAILED(hr)) { - delete player; - player = nullptr; - return false; - } - g_threadStarted = true; - std::thread t(sapi_thread, player); - t.detach(); - return true; -} -bool SAPI::Uninitialize() { - if (instance == nullptr || player == nullptr)return false; - g_threadStarted = false; - blastspeak_destroy(instance); - delete instance; - instance = nullptr; - StopSpeech(); - delete player; - player = nullptr; - return true; -} -bool SAPI::GetActive() { - return instance != nullptr; -} -bool SAPI::Speak(const char* text, bool interrupt) { - if (instance == nullptr || player == nullptr) - return false; - if (interrupt) { - StopSpeech(); - - } - if (wfx.nChannels != instance->channels || wfx.nSamplesPerSec != instance->sample_rate || wfx.wBitsPerSample != instance->bits_per_sample) { - - this->Uninitialize(); - this->Initialize(); - } - std::string text_str(text); - unsigned long bytes; - char* audio_ptr = blastspeak_speak_to_memory(instance, &bytes, text_str.c_str()); - if (audio_ptr == nullptr) - return false; - - char* final = trim(audio_ptr, &bytes, &wfx, this->trimThreshold); - if (final == nullptr) - return false; - PCMData dat = { 0, 0 }; - dat.data = (unsigned char*)final; - dat.size = bytes; - if (this->paused) { - this->paused = false; - if (!interrupt) - player->resume(); - } - g_dataQueue.push_back(dat); - return true; -} -bool SAPI::SetParameter(int param, int value) { - switch (param) { - case SAPI_TRIM_THRESHOLD: - this->trimThreshold = value; - break; - default: - return false; - } - return true; -} - - -bool SAPI::StopSpeech() { - if (player == nullptr)return false; - g_dataQueue.clear(); - player->stop(); - this->paused = false; - return true; -} -bool SAPI::PauseSpeech() { - paused = true; - return SUCCEEDED(player->pause()); -} -bool SAPI::ResumeSpeech() { - paused = false; - return SUCCEEDED(player->resume()); -} -void SAPI::SetVolume(uint64_t value) { - if (instance == nullptr)return; - blastspeak_set_voice_volume(instance, value); -} -uint64_t SAPI::GetVolume() { - if (instance == nullptr)return 0; - long value; - blastspeak_get_voice_volume(instance, &value); - return value; -} -void SAPI::SetRate(uint64_t value) { - if (instance == nullptr)return; - blastspeak_set_voice_rate(instance, value); -} -uint64_t SAPI::GetRate() { - if (instance == nullptr)return 0; - long value; - blastspeak_get_voice_rate(instance, &value); - return value; -} -uint64_t SAPI::GetVoiceCount() { - if (instance == nullptr)return 0; - return instance->voice_count; -} -const char* SAPI::GetVoiceName(uint64_t index) { - if (instance == nullptr)return nullptr; - return blastspeak_get_voice_description(instance, index); -} -bool SAPI::SetVoice(uint64_t index) { - if (instance == nullptr)return false; - return blastspeak_set_voice(instance, index); -} - - - +#ifdef _WIN32 +#include "SAPI.h" +#include +#include +#include + + +static char* trim(char* data, unsigned long* size, WAVEFORMATEX* wfx, int threshold) { + int channels = wfx->nChannels; + int bytesPerSample = wfx->wBitsPerSample / 8; + int samplesPerFrame = channels * bytesPerSample; + int numSamples = *size / samplesPerFrame; + int startIndex = 0; + int endIndex = numSamples - 1; + + for (int i = 0; i < numSamples; i++) { + int maxAbsValue = 0; + for (int j = 0; j < channels; j++) { + int absValue = abs(static_cast(data[i * samplesPerFrame + j])); + if (absValue > maxAbsValue) { + maxAbsValue = absValue; + } + } + if (maxAbsValue >= threshold) { + startIndex = i; + break; + } + } + + for (int i = numSamples - 1; i >= 0; i--) { + int maxAbsValue = 0; + for (int j = 0; j < channels; j++) { + int absValue = abs(static_cast(data[i * samplesPerFrame + j])); + if (absValue > maxAbsValue) { + maxAbsValue = absValue; + } + } + if (maxAbsValue >= threshold) { + endIndex = i; + break; + } + } + + int trimmedSize = (endIndex - startIndex + 1) * samplesPerFrame; + char* trimmedData = new char[trimmedSize]; + memcpy(trimmedData, data + startIndex * samplesPerFrame, trimmedSize); + *size = trimmedSize; + return trimmedData; +} + + +struct PCMData { + unsigned char* data; + unsigned long size; +}; + +std::vector g_dataQueue; +bool g_threadStarted = false; +static void sapi_thread(WasapiPlayer* player) { + if (player == nullptr) { + g_threadStarted = false; + } + HRESULT hr; + while (g_threadStarted) { + Sleep(1); + for (uint64_t i = 0; i < g_dataQueue.size(); ++i) { + + hr = player->feed(g_dataQueue[i].data, g_dataQueue[i].size, nullptr); + if (FAILED(hr))continue; + hr = player->sync(); + if (FAILED(hr))continue; + } + if (g_dataQueue.size() > 0) g_dataQueue.clear(); + } +} +bool SAPI::Initialize() { + instance = new blastspeak; + + if (blastspeak_initialize(instance) == 0) { + delete instance; + instance = nullptr; + return false; + } + CoInitialize(nullptr); + wchar_t device[] = L""; + WasapiPlayer::ChunkCompletedCallback callback = nullptr; + + wfx.wFormatTag = WAVE_FORMAT_PCM; + wfx.nChannels = instance->channels; + wfx.nSamplesPerSec = instance->sample_rate; + wfx.wBitsPerSample = instance->bits_per_sample; + wfx.nBlockAlign = (wfx.nChannels * wfx.wBitsPerSample) / 8; + wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; + wfx.cbSize = 0; + player = new WasapiPlayer(device, wfx, callback); + HRESULT hr = player->open(); + if (FAILED(hr)) { + delete player; + player = nullptr; + return false; + } + g_threadStarted = true; + std::thread t(sapi_thread, player); + t.detach(); + return true; +} +bool SAPI::Uninitialize() { + if (instance == nullptr || player == nullptr)return false; + g_threadStarted = false; + blastspeak_destroy(instance); + delete instance; + instance = nullptr; + StopSpeech(); + delete player; + player = nullptr; + return true; +} +bool SAPI::GetActive() { + return instance != nullptr; +} +bool SAPI::Speak(const char* text, bool interrupt) { + if (instance == nullptr || player == nullptr) + return false; + if (interrupt) { + StopSpeech(); + + } + if (wfx.nChannels != instance->channels || wfx.nSamplesPerSec != instance->sample_rate || wfx.wBitsPerSample != instance->bits_per_sample) { + + this->Uninitialize(); + this->Initialize(); + } + std::string text_str(text); + unsigned long bytes; + char* audio_ptr = blastspeak_speak_to_memory(instance, &bytes, text_str.c_str()); + if (audio_ptr == nullptr) + return false; + + char* final = trim(audio_ptr, &bytes, &wfx, this->trimThreshold); + if (final == nullptr) + return false; + PCMData dat = { 0, 0 }; + dat.data = (unsigned char*)final; + dat.size = bytes; + if (this->paused) { + this->paused = false; + if (!interrupt) + player->resume(); + } + g_dataQueue.push_back(dat); + return true; +} +bool SAPI::SetParameter(int param, int value) { + switch (param) { + case SAPI_TRIM_THRESHOLD: + this->trimThreshold = value; + break; + default: + return false; + } + return true; +} + + +bool SAPI::StopSpeech() { + if (player == nullptr)return false; + g_dataQueue.clear(); + player->stop(); + this->paused = false; + return true; +} +bool SAPI::PauseSpeech() { + paused = true; + return SUCCEEDED(player->pause()); +} +bool SAPI::ResumeSpeech() { + paused = false; + return SUCCEEDED(player->resume()); +} +void SAPI::SetVolume(uint64_t value) { + if (instance == nullptr)return; + blastspeak_set_voice_volume(instance, value); +} +uint64_t SAPI::GetVolume() { + if (instance == nullptr)return 0; + long value; + blastspeak_get_voice_volume(instance, &value); + return value; +} +void SAPI::SetRate(uint64_t value) { + if (instance == nullptr)return; + blastspeak_set_voice_rate(instance, value); +} +uint64_t SAPI::GetRate() { + if (instance == nullptr)return 0; + long value; + blastspeak_get_voice_rate(instance, &value); + return value; +} +uint64_t SAPI::GetVoiceCount() { + if (instance == nullptr)return 0; + return instance->voice_count; +} +const char* SAPI::GetVoiceName(uint64_t index) { + if (instance == nullptr)return nullptr; + return blastspeak_get_voice_description(instance, index); +} +bool SAPI::SetVoice(uint64_t index) { + if (instance == nullptr)return false; + return blastspeak_set_voice(instance, index); +} + + + #endif \ No newline at end of file diff --git a/SRC/SAPI.h b/SRC/SAPI.h index 112723c..d2402da 100644 --- a/SRC/SAPI.h +++ b/SRC/SAPI.h @@ -1,47 +1,47 @@ -#ifndef SAPI_H_ -#define SAPI_H_ -#pragma once -#define BLASTSPEAK_IMPLEMENTATION -#include "../Dep/blastspeak.h" -#include "../Dep/wasapi.h" -#include "../Include/SRAL.h" -#include "Engine.h" -class SAPI : public Engine { -public: - bool Speak(const char* text, bool interrupt)override; - bool SpeakSsml(const char* ssml, bool interrupt)override { - return false; - } - bool SetParameter(int param, int value)override; - - bool Braille(const char* text)override { return false; } - bool StopSpeech()override; - bool PauseSpeech()override; - bool ResumeSpeech()override; - int GetNumber()override { - return ENGINE_SAPI; - } - bool GetActive()override; - bool Initialize()override; - bool Uninitialize()override; - int GetFeatures()override { - return SUPPORTS_SPEECH | SUPPORTS_SPEECH_RATE | SUPPORTS_SPEECH_VOLUME | SUPPORTS_SELECT_VOICE | SUPPORTS_PAUSE_SPEECH; - } - void SetVolume(uint64_t value)override; - uint64_t GetVolume()override; - void SetRate(uint64_t value)override; - uint64_t GetRate()override; - uint64_t GetVoiceCount()override; - const char* GetVoiceName(uint64_t index)override; - bool SetVoice(uint64_t index)override; - int GetKeyFlags()override { - return HANDLE_INTERRUPT | HANDLE_PAUSE_RESUME; - } - -private: - blastspeak* instance = nullptr; - WasapiPlayer* player = nullptr; - WAVEFORMATEX wfx; - int trimThreshold = 20; -}; +#ifndef SAPI_H_ +#define SAPI_H_ +#pragma once +#define BLASTSPEAK_IMPLEMENTATION +#include "../Dep/blastspeak.h" +#include "../Dep/wasapi.h" +#include "../Include/SRAL.h" +#include "Engine.h" +class SAPI : public Engine { +public: + bool Speak(const char* text, bool interrupt)override; + bool SpeakSsml(const char* ssml, bool interrupt)override { + return false; + } + bool SetParameter(int param, int value)override; + + bool Braille(const char* text)override { return false; } + bool StopSpeech()override; + bool PauseSpeech()override; + bool ResumeSpeech()override; + int GetNumber()override { + return ENGINE_SAPI; + } + bool GetActive()override; + bool Initialize()override; + bool Uninitialize()override; + int GetFeatures()override { + return SUPPORTS_SPEECH | SUPPORTS_SPEECH_RATE | SUPPORTS_SPEECH_VOLUME | SUPPORTS_SELECT_VOICE | SUPPORTS_PAUSE_SPEECH; + } + void SetVolume(uint64_t value)override; + uint64_t GetVolume()override; + void SetRate(uint64_t value)override; + uint64_t GetRate()override; + uint64_t GetVoiceCount()override; + const char* GetVoiceName(uint64_t index)override; + bool SetVoice(uint64_t index)override; + int GetKeyFlags()override { + return HANDLE_INTERRUPT | HANDLE_PAUSE_RESUME; + } + +private: + blastspeak* instance = nullptr; + WasapiPlayer* player = nullptr; + WAVEFORMATEX wfx; + int trimThreshold = 20; +}; #endif \ No newline at end of file diff --git a/SRC/SRAL.cpp b/SRC/SRAL.cpp index cbca02e..0c6ca93 100644 --- a/SRC/SRAL.cpp +++ b/SRC/SRAL.cpp @@ -1,635 +1,635 @@ -#define SRAL_EXPORT -#include "../Include/SRAL.h" -#include "Engine.h" -#if defined(_WIN32) -#define UNICODE -#include "NVDA.h" -#include "SAPI.h" -#include "Jaws.h" -#include "UIA.h" -#include -#include -#elif defined(__APPLE__) -#include "AVSpeech.h" -#else -#include "SpeechDispatcher.h" -#include -#include -#include -#endif -#include -#include -#include -#include - -class Timer { -public: - Timer() { - restart(); - } - - inline uint64_t elapsed() { - auto now = std::chrono::high_resolution_clock::now(); - return std::chrono::duration_cast(now - start_time).count(); - } - - inline void restart() { - start_time = std::chrono::high_resolution_clock::now(); - } - -private: - std::chrono::time_point start_time; -}; - - - -Engine* g_currentEngine = nullptr; -std::vector g_engines; -int g_excludes = 0; -bool g_initialized = false; - -struct QueuedOutput { - const char* text; - bool interrupt; - bool braille; - bool speak; - bool ssml; - int time; - Engine* engine; -}; -std::vector g_delayedOutputs; -bool g_delayOperation = false; -bool g_outputThreadRunning = false; - - -uint64_t g_lastDelayTime = 0; - - -static void output_thread() { - g_outputThreadRunning = true; - Timer t; - while (g_delayOperation && g_delayedOutputs.size() != 0) { - for (uint64_t i = 0; i < g_delayedOutputs.size(); ++i) { - t.restart(); - while (t.elapsed() < g_delayedOutputs[i].time && g_delayOperation) { - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } - if (g_delayedOutputs[i].speak) { - if (g_delayedOutputs[i].ssml) - g_delayedOutputs[i].engine->SpeakSsml(g_delayedOutputs[i].text, g_delayedOutputs[i].interrupt); - else - g_delayedOutputs[i].engine->Speak(g_delayedOutputs[i].text, g_delayedOutputs[i].interrupt); - - } - else if (g_delayedOutputs[i].braille) - g_delayedOutputs[i].engine->Braille(g_delayedOutputs[i].text); - } - if (g_delayedOutputs.size() != 0) { - g_delayedOutputs.clear(); - g_delayOperation = false; - break; - } - } - g_outputThreadRunning = false; -} - - - -bool g_keyboardHookThread = false; -bool g_shiftPressed = false; - -#if defined(_WIN32) -static HHOOK g_keyboardHook; -static LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) { - if (nCode >= 0) { - KBDLLHOOKSTRUCT* pKeyInfo = (KBDLLHOOKSTRUCT*)lParam; - for (uint64_t i = 0; i < g_engines.size(); ++i) { - if (g_engines[i] == nullptr || !g_engines[i]->GetActive()) continue; - - if (wParam == WM_KEYDOWN) { - if ((pKeyInfo->vkCode == VK_LCONTROL || pKeyInfo->vkCode == VK_RCONTROL) && g_engines[i]->GetKeyFlags() & HANDLE_INTERRUPT) { - g_engines[i]->StopSpeech(); - } - else if ((pKeyInfo->vkCode == VK_LSHIFT || pKeyInfo->vkCode == VK_RSHIFT) && g_engines[i]->GetKeyFlags() & HANDLE_PAUSE_RESUME && g_shiftPressed == false) { - if (g_engines[i]->paused) - g_engines[i]->ResumeSpeech(); - else - g_engines[i]->PauseSpeech(); - g_shiftPressed = true; - } - } - else if (wParam == WM_KEYUP) { - if (pKeyInfo->vkCode == VK_LSHIFT || pKeyInfo->vkCode == VK_RSHIFT) { - g_shiftPressed = false; - } - } - } - } - return CallNextHookEx(g_keyboardHook, nCode, wParam, lParam); -} -static void hook_thread() { - g_keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, GetModuleHandle(NULL), 0); - if (g_keyboardHook == nullptr)return; - MSG msg; - - while (g_keyboardHookThread) { - if (GetMessageW(&msg, nullptr, 0, 0)) { - DispatchMessageW(&msg); - TranslateMessage(&msg); - } - } - UnhookWindowsHookEx(g_keyboardHook); -} -extern "C" SRAL_API bool SRAL_RegisterKeyboardHooks(void) { - if (g_keyboardHookThread)return g_keyboardHookThread; - g_keyboardHookThread = true; - std::thread t(hook_thread); - t.detach(); - Timer timer; - while (timer.elapsed() < 3000) { - Sleep(5); - if (g_keyboardHook != nullptr) { - return true; - } - } - return false; // Timeout: Hook is not set -} - -extern "C" SRAL_API void SRAL_UnregisterKeyboardHooks(void) { - PostMessage(0, WM_KEYUP, 0, 0); - g_keyboardHookThread = false; -} -#elif defined(__APPLE__) -extern "C" SRAL_API bool SRAL_RegisterKeyboardHooks(void) { - return false; -} -extern "C" SRAL_API void SRAL_UnregisterKeyboardHooks(void) { - return; -} -#else -Display* g_display = nullptr; -static void hook_thread() { - - g_display = XOpenDisplay(nullptr); - - if (g_display == nullptr)return; - XGrabKeyboard(g_display, DefaultRootWindow(g_display), True, GrabModeAsync, GrabModeAsync, CurrentTime); - XEvent event; - - while (g_keyboardHookThread) { - if (XPending(g_display)) { - XNextEvent(g_display, &event); - } - for (uint64_t i = 0; i < g_engines.size(); ++i) { - if (g_engines[i] == nullptr || !g_engines[i]->GetActive()) continue; - - if (event.type == KeyPress) { - KeySym key = XLookupKeysym(&event.xkey, 0); - - if ((key == XK_Control_L || key == XK_Control_R) && g_engines[i]->GetKeyFlags() & HANDLE_INTERRUPT) { - g_engines[i]->StopSpeech(); - } - else if ((key == XK_Shift_L || key == XK_Shift_R) && g_engines[i]->GetKeyFlags() & HANDLE_PAUSE_RESUME && g_shiftPressed == false) { - if (g_engines[i]->paused) - g_engines[i]->ResumeSpeech(); - else - g_engines[i]->PauseSpeech(); - g_shiftPressed = true; - } - } - else if (event.type == KeyRelease) { - KeySym key = XLookupKeysym(&event.xkey, 0); - - if ((key == XK_Shift_L || key == XK_Shift_R) && g_shiftPressed) { - g_shiftPressed = false; - } - } - } - } - XCloseDisplay(g_display); -} -extern "C" SRAL_API bool SRAL_RegisterKeyboardHooks(void) { - if (g_keyboardHookThread)return g_keyboardHookThread; - g_keyboardHookThread = true; - std::thread t(hook_thread); - t.detach(); - Timer timer; - while (timer.elapsed() < 3000) { - usleep(5000); - if (g_display != nullptr) { - return true; - } - } - return false; // Timeout: Hook is not set -} - -extern "C" SRAL_API void SRAL_UnregisterKeyboardHooks(void) { - g_keyboardHookThread = false; -} - -#endif - - - -extern "C" SRAL_API bool SRAL_Initialize(int engines_exclude) { - if (g_initialized)return true; -#if defined(_WIN32) - g_engines.push_back(new NVDA); - g_engines.push_back(new SAPI); - g_engines.push_back(new Jaws); - g_engines.push_back(new UIA); -#elif defined(__APPLE__) - g_engines.push_back(new AVSpeech); -#else - g_engines.push_back(new SpeechDispatcher); -#endif - bool found = false; - for (uint64_t i = 0; i < g_engines.size(); ++i) { - g_engines[i]->Initialize(); - if (g_engines[i]->GetActive() && !found && !(engines_exclude & g_engines[i]->GetNumber())) { - g_currentEngine = g_engines[i]; - found = true; - } - } - if (g_currentEngine == nullptr)return false; - g_excludes = engines_exclude; - g_initialized = found; - return found; -} -extern "C" SRAL_API void SRAL_Uninitialize(void) { - if (!g_initialized)return; - g_currentEngine->Uninitialize(); - delete g_currentEngine; - g_currentEngine = nullptr; - g_engines.clear(); - g_excludes = 0; - if (g_keyboardHookThread) { - SRAL_UnregisterKeyboardHooks(); - // Just for sure - g_keyboardHookThread = false; - } - g_initialized = false; -} -#ifdef _WIN32 -// This is used for find the Windows Narrator process -BOOL FindProcess(const wchar_t* name) { - HANDLE hProcessSnap; - PROCESSENTRY32 pe32; - - // Take a snapshot of all processes in the system. - hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hProcessSnap == INVALID_HANDLE_VALUE) { - return FALSE; // Snapshot failed - } - - pe32.dwSize = sizeof(PROCESSENTRY32); - - // Retrieve information about the first process. - if (!Process32First(hProcessSnap, &pe32)) { - CloseHandle(hProcessSnap); // Clean up the snapshot object - return FALSE; // Unable to retrieve process information - } - - // Now walk the snapshot of processes - do { - // Compare the process name with the input name - if (_wcsicmp(pe32.szExeFile, name) == 0) { - CloseHandle(hProcessSnap); // Clean up the snapshot object - return TRUE; // Process found - } - } while (Process32Next(hProcessSnap, &pe32)); - - CloseHandle(hProcessSnap); // Clean up the snapshot object - return FALSE; // Process not found -} - - -#endif -static Engine* get_engine(int); -static void speech_engine_update() { - if (!g_currentEngine->GetActive() || g_currentEngine->GetNumber() == ENGINE_SAPI || g_currentEngine->GetNumber() == ENGINE_UIA) { -#ifdef _WIN32 - if (FindProcess(L"narrator.exe") == TRUE) { - g_currentEngine = get_engine(ENGINE_UIA); - return; - } - else { -#endif - for (uint64_t i = 0; i < g_engines.size(); ++i) { - if (g_engines[i]->GetActive() && !(g_excludes & g_engines[i]->GetNumber())) { - g_currentEngine = g_engines[i]; - break; - } - } - } -#ifdef _WIN32 - } -#endif -} -static Engine* get_engine(int engine) { -#ifdef _WIN32 - if (engine == ENGINE_NARRATOR)return get_engine(ENGINE_UIA); -#endif - bool found = false; - uint64_t i; - for (i = 0; i < g_engines.size(); ++i) { - if (g_engines[i]->GetNumber() == engine) { - found = true; - break; - } - } - if (!found)return nullptr; - return g_engines[i]; -} -extern "C" SRAL_API bool SRAL_Speak(const char* text, bool interrupt) { - if (g_currentEngine == nullptr) return false; - speech_engine_update(); - if (!g_delayOperation) - return g_currentEngine->Speak(text, interrupt); - else { - QueuedOutput qout; - qout.text = text; - qout.interrupt = interrupt; - qout.braille = false; - qout.speak = true; - qout.ssml = false; - qout.engine = g_currentEngine; - qout.time = g_lastDelayTime; - g_delayedOutputs.push_back(qout); - if (!g_outputThreadRunning) { - std::thread t(output_thread); - t.detach(); - } - return true; - } - return false; -} -extern "C" SRAL_API bool SRAL_SpeakSsml(const char* ssml, bool interrupt) { - if (g_currentEngine == nullptr) return false; - speech_engine_update(); - if (!g_delayOperation) - return g_currentEngine->SpeakSsml(ssml, interrupt); - else { - QueuedOutput qout; - qout.text = ssml; - qout.interrupt = interrupt; - qout.braille = false; - qout.speak = true; - qout.ssml = true; - qout.engine = g_currentEngine; - qout.time = g_lastDelayTime; - g_delayedOutputs.push_back(qout); - if (!g_outputThreadRunning) { - std::thread t(output_thread); - t.detach(); - } - return true; - } - return false; -} - -extern "C" SRAL_API bool SRAL_Braille(const char* text) { - if (g_currentEngine == nullptr)return false; - speech_engine_update(); - return g_currentEngine->Braille(text); -} -extern "C" SRAL_API bool SRAL_Output(const char* text, bool interrupt) { - if (g_currentEngine == nullptr)return false; - speech_engine_update(); - const bool speech = SRAL_Speak(text, interrupt); - const bool braille = SRAL_Braille(text); - return speech || braille; -} -extern "C" SRAL_API bool SRAL_StopSpeech(void) { - if (g_currentEngine == nullptr)return false; - speech_engine_update(); - if (g_delayOperation) { - g_delayedOutputs.clear(); - g_delayOperation = false; - } - return g_currentEngine->StopSpeech(); -} -extern "C" SRAL_API bool SRAL_PauseSpeech(void) { - if (g_currentEngine == nullptr)return false; - speech_engine_update(); - if (g_delayOperation) { - // Just stop the thread. Don't clear the queue - g_delayOperation = false; - } - return g_currentEngine->PauseSpeech(); -} -extern "C" SRAL_API bool SRAL_ResumeSpeech(void) { - if (g_currentEngine == nullptr)return false; - speech_engine_update(); - if (g_delayedOutputs.size() != 0) { - g_delayOperation = true; - if (!g_outputThreadRunning) { - std::thread t(output_thread); - t.detach(); - } - } - return g_currentEngine->ResumeSpeech(); -} - -extern "C" SRAL_API int SRAL_GetCurrentEngine(void) { - if (g_currentEngine == nullptr)return ENGINE_NONE; - return g_currentEngine->GetNumber(); -} -extern "C" SRAL_API int SRAL_GetEngineFeatures(int engine) { - if (engine == 0) { - if (g_currentEngine == nullptr)return -1; - return g_currentEngine->GetFeatures(); - } - else { - Engine* e = get_engine(engine); - if (e == nullptr)return -1; - return e->GetFeatures(); - } - return -1; -} -extern "C" SRAL_API bool SRAL_SetVolume(uint64_t value) { - if (g_currentEngine == nullptr)return false; - g_currentEngine->SetVolume(value); - return true; -} -extern "C" SRAL_API uint64_t SRAL_GetVolume(void) { - if (g_currentEngine == nullptr)return false; - return g_currentEngine->GetVolume(); -} -extern "C" SRAL_API bool SRAL_SetRate(uint64_t value) { - if (g_currentEngine == nullptr)return false; - g_currentEngine->SetRate(value); - return true; -} -extern "C" SRAL_API uint64_t SRAL_GetRate(void) { - if (g_currentEngine == nullptr)return false; - return g_currentEngine->GetRate(); -} - -extern "C" SRAL_API uint64_t SRAL_GetVoiceCount(void) { - if (g_currentEngine == nullptr)return false; - return g_currentEngine->GetVoiceCount(); -} -extern "C" SRAL_API const char* SRAL_GetVoiceName(uint64_t index) { - if (g_currentEngine == nullptr)return nullptr; - return g_currentEngine->GetVoiceName(index); -} -extern "C" SRAL_API bool SRAL_SetVoice(uint64_t index) { - if (g_currentEngine == nullptr)return false; - return g_currentEngine->SetVoice(index); -} - -extern "C" SRAL_API bool SRAL_SetVolumeEx(int engine, uint64_t value) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - e->SetVolume(value); - return true; -} -extern "C" SRAL_API uint64_t SRAL_GetVolumeEx(int engine) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - return e->GetVolume(); -} -extern "C" SRAL_API bool SRAL_SetRateEx(int engine, uint64_t value) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - e->SetRate(value); - return true; -} -extern "C" SRAL_API uint64_t SRAL_GetRateEx(int engine) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - return e->GetRate(); -} - -extern "C" SRAL_API uint64_t SRAL_GetVoiceCountEx(int engine) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - return e->GetVoiceCount(); -} -extern "C" SRAL_API const char* SRAL_GetVoiceNameEx(int engine, uint64_t index) { - Engine* e = get_engine(engine); - if (e == nullptr)return nullptr; - return e->GetVoiceName(index); -} -extern "C" SRAL_API bool SRAL_SetVoiceEx(int engine, uint64_t index) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - return e->SetVoice(index); -} - - - -extern "C" SRAL_API bool SRAL_SetEngineParameter(int engine, int param, int value) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - return e->SetParameter(param, value); -} - - - -extern "C" SRAL_API bool SRAL_SpeakEx(int engine, const char* text, bool interrupt) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - if (!g_delayOperation) - return e->Speak(text, interrupt); - else { - QueuedOutput qout; - qout.text = text; - qout.interrupt = interrupt; - qout.braille = false; - qout.speak = true; - qout.ssml = false; - qout.engine = e; - qout.time = g_lastDelayTime; - g_delayedOutputs.push_back(qout); - if (!g_outputThreadRunning) { - std::thread t(output_thread); - t.detach(); - } - return true; - } - return false; -} -extern "C" SRAL_API bool SRAL_SpeakSsmlEx(int engine, const char* ssml, bool interrupt) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - if (!g_delayOperation) - return e->SpeakSsml(ssml, interrupt); - else { - QueuedOutput qout; - qout.text = ssml; - qout.interrupt = interrupt; - qout.braille = false; - qout.speak = true; - qout.ssml = true; - qout.engine = e; - qout.time = g_lastDelayTime; - g_delayedOutputs.push_back(qout); - if (!g_outputThreadRunning) { - std::thread t(output_thread); - t.detach(); - } - return true; - } - return false; -} - -extern "C" SRAL_API bool SRAL_BrailleEx(int engine, const char* text) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - return e->Braille(text); -} -extern "C" SRAL_API bool SRAL_OutputEx(int engine, const char* text, bool interrupt) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - const bool speech = e->Speak(text, interrupt); - const bool braille = e->Braille(text); - return speech || braille; -} -extern "C" SRAL_API bool SRAL_StopSpeechEx(int engine) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - if (g_delayOperation) { - g_delayedOutputs.clear(); - g_delayOperation = false; - } - return e->StopSpeech(); -} - - -extern "C" SRAL_API bool SRAL_PauseSpeechEx(int engine) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - if (g_delayOperation) { - g_delayOperation = false; - } - return e->PauseSpeech(); -} - - - -extern "C" SRAL_API bool SRAL_ResumeSpeechEx(int engine) { - Engine* e = get_engine(engine); - if (e == nullptr)return false; - if (g_delayedOutputs.size() != 0) { - g_delayOperation = true; - if (!g_outputThreadRunning) { - std::thread t(output_thread); - t.detach(); - } - } - return e->ResumeSpeech(); -} - - -extern "C" SRAL_API bool SRAL_IsInitialized(void) { - return g_initialized; -} - - - -extern "C" SRAL_API void SRAL_Delay(int time) { - g_lastDelayTime = time; - g_delayOperation = true; -} +#define SRAL_EXPORT +#include "../Include/SRAL.h" +#include "Engine.h" +#if defined(_WIN32) +#define UNICODE +#include "NVDA.h" +#include "SAPI.h" +#include "Jaws.h" +#include "UIA.h" +#include +#include +#elif defined(__APPLE__) +#include "AVSpeech.h" +#else +#include "SpeechDispatcher.h" +#include +#include +#include +#endif +#include +#include +#include +#include + +class Timer { +public: + Timer() { + restart(); + } + + inline uint64_t elapsed() { + auto now = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(now - start_time).count(); + } + + inline void restart() { + start_time = std::chrono::high_resolution_clock::now(); + } + +private: + std::chrono::time_point start_time; +}; + + + +Engine* g_currentEngine = nullptr; +std::vector g_engines; +int g_excludes = 0; +bool g_initialized = false; + +struct QueuedOutput { + const char* text; + bool interrupt; + bool braille; + bool speak; + bool ssml; + int time; + Engine* engine; +}; +std::vector g_delayedOutputs; +bool g_delayOperation = false; +bool g_outputThreadRunning = false; + + +uint64_t g_lastDelayTime = 0; + + +static void output_thread() { + g_outputThreadRunning = true; + Timer t; + while (g_delayOperation && g_delayedOutputs.size() != 0) { + for (uint64_t i = 0; i < g_delayedOutputs.size(); ++i) { + t.restart(); + while (t.elapsed() < g_delayedOutputs[i].time && g_delayOperation) { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + if (g_delayedOutputs[i].speak) { + if (g_delayedOutputs[i].ssml) + g_delayedOutputs[i].engine->SpeakSsml(g_delayedOutputs[i].text, g_delayedOutputs[i].interrupt); + else + g_delayedOutputs[i].engine->Speak(g_delayedOutputs[i].text, g_delayedOutputs[i].interrupt); + + } + else if (g_delayedOutputs[i].braille) + g_delayedOutputs[i].engine->Braille(g_delayedOutputs[i].text); + } + if (g_delayedOutputs.size() != 0) { + g_delayedOutputs.clear(); + g_delayOperation = false; + break; + } + } + g_outputThreadRunning = false; +} + + + +bool g_keyboardHookThread = false; +bool g_shiftPressed = false; + +#if defined(_WIN32) +static HHOOK g_keyboardHook; +static LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) { + if (nCode >= 0) { + KBDLLHOOKSTRUCT* pKeyInfo = (KBDLLHOOKSTRUCT*)lParam; + for (uint64_t i = 0; i < g_engines.size(); ++i) { + if (g_engines[i] == nullptr || !g_engines[i]->GetActive()) continue; + + if (wParam == WM_KEYDOWN) { + if ((pKeyInfo->vkCode == VK_LCONTROL || pKeyInfo->vkCode == VK_RCONTROL) && g_engines[i]->GetKeyFlags() & HANDLE_INTERRUPT) { + g_engines[i]->StopSpeech(); + } + else if ((pKeyInfo->vkCode == VK_LSHIFT || pKeyInfo->vkCode == VK_RSHIFT) && g_engines[i]->GetKeyFlags() & HANDLE_PAUSE_RESUME && g_shiftPressed == false) { + if (g_engines[i]->paused) + g_engines[i]->ResumeSpeech(); + else + g_engines[i]->PauseSpeech(); + g_shiftPressed = true; + } + } + else if (wParam == WM_KEYUP) { + if (pKeyInfo->vkCode == VK_LSHIFT || pKeyInfo->vkCode == VK_RSHIFT) { + g_shiftPressed = false; + } + } + } + } + return CallNextHookEx(g_keyboardHook, nCode, wParam, lParam); +} +static void hook_thread() { + g_keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, GetModuleHandle(NULL), 0); + if (g_keyboardHook == nullptr)return; + MSG msg; + + while (g_keyboardHookThread) { + if (GetMessageW(&msg, nullptr, 0, 0)) { + DispatchMessageW(&msg); + TranslateMessage(&msg); + } + } + UnhookWindowsHookEx(g_keyboardHook); +} +extern "C" SRAL_API bool SRAL_RegisterKeyboardHooks(void) { + if (g_keyboardHookThread)return g_keyboardHookThread; + g_keyboardHookThread = true; + std::thread t(hook_thread); + t.detach(); + Timer timer; + while (timer.elapsed() < 3000) { + Sleep(5); + if (g_keyboardHook != nullptr) { + return true; + } + } + return false; // Timeout: Hook is not set +} + +extern "C" SRAL_API void SRAL_UnregisterKeyboardHooks(void) { + PostMessage(0, WM_KEYUP, 0, 0); + g_keyboardHookThread = false; +} +#elif defined(__APPLE__) +extern "C" SRAL_API bool SRAL_RegisterKeyboardHooks(void) { + return false; +} +extern "C" SRAL_API void SRAL_UnregisterKeyboardHooks(void) { + return; +} +#else +Display* g_display = nullptr; +static void hook_thread() { + + g_display = XOpenDisplay(nullptr); + + if (g_display == nullptr)return; + XGrabKeyboard(g_display, DefaultRootWindow(g_display), True, GrabModeAsync, GrabModeAsync, CurrentTime); + XEvent event; + + while (g_keyboardHookThread) { + if (XPending(g_display)) { + XNextEvent(g_display, &event); + } + for (uint64_t i = 0; i < g_engines.size(); ++i) { + if (g_engines[i] == nullptr || !g_engines[i]->GetActive()) continue; + + if (event.type == KeyPress) { + KeySym key = XLookupKeysym(&event.xkey, 0); + + if ((key == XK_Control_L || key == XK_Control_R) && g_engines[i]->GetKeyFlags() & HANDLE_INTERRUPT) { + g_engines[i]->StopSpeech(); + } + else if ((key == XK_Shift_L || key == XK_Shift_R) && g_engines[i]->GetKeyFlags() & HANDLE_PAUSE_RESUME && g_shiftPressed == false) { + if (g_engines[i]->paused) + g_engines[i]->ResumeSpeech(); + else + g_engines[i]->PauseSpeech(); + g_shiftPressed = true; + } + } + else if (event.type == KeyRelease) { + KeySym key = XLookupKeysym(&event.xkey, 0); + + if ((key == XK_Shift_L || key == XK_Shift_R) && g_shiftPressed) { + g_shiftPressed = false; + } + } + } + } + XCloseDisplay(g_display); +} +extern "C" SRAL_API bool SRAL_RegisterKeyboardHooks(void) { + if (g_keyboardHookThread)return g_keyboardHookThread; + g_keyboardHookThread = true; + std::thread t(hook_thread); + t.detach(); + Timer timer; + while (timer.elapsed() < 3000) { + usleep(5000); + if (g_display != nullptr) { + return true; + } + } + return false; // Timeout: Hook is not set +} + +extern "C" SRAL_API void SRAL_UnregisterKeyboardHooks(void) { + g_keyboardHookThread = false; +} + +#endif + + + +extern "C" SRAL_API bool SRAL_Initialize(int engines_exclude) { + if (g_initialized)return true; +#if defined(_WIN32) + g_engines.push_back(new NVDA); + g_engines.push_back(new SAPI); + g_engines.push_back(new Jaws); + g_engines.push_back(new UIA); +#elif defined(__APPLE__) + g_engines.push_back(new AVSpeech); +#else + g_engines.push_back(new SpeechDispatcher); +#endif + bool found = false; + for (uint64_t i = 0; i < g_engines.size(); ++i) { + g_engines[i]->Initialize(); + if (g_engines[i]->GetActive() && !found && !(engines_exclude & g_engines[i]->GetNumber())) { + g_currentEngine = g_engines[i]; + found = true; + } + } + if (g_currentEngine == nullptr)return false; + g_excludes = engines_exclude; + g_initialized = found; + return found; +} +extern "C" SRAL_API void SRAL_Uninitialize(void) { + if (!g_initialized)return; + g_currentEngine->Uninitialize(); + delete g_currentEngine; + g_currentEngine = nullptr; + g_engines.clear(); + g_excludes = 0; + if (g_keyboardHookThread) { + SRAL_UnregisterKeyboardHooks(); + // Just for sure + g_keyboardHookThread = false; + } + g_initialized = false; +} +#ifdef _WIN32 +// This is used for find the Windows Narrator process +BOOL FindProcess(const wchar_t* name) { + HANDLE hProcessSnap; + PROCESSENTRY32 pe32; + + // Take a snapshot of all processes in the system. + hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hProcessSnap == INVALID_HANDLE_VALUE) { + return FALSE; // Snapshot failed + } + + pe32.dwSize = sizeof(PROCESSENTRY32); + + // Retrieve information about the first process. + if (!Process32First(hProcessSnap, &pe32)) { + CloseHandle(hProcessSnap); // Clean up the snapshot object + return FALSE; // Unable to retrieve process information + } + + // Now walk the snapshot of processes + do { + // Compare the process name with the input name + if (_wcsicmp(pe32.szExeFile, name) == 0) { + CloseHandle(hProcessSnap); // Clean up the snapshot object + return TRUE; // Process found + } + } while (Process32Next(hProcessSnap, &pe32)); + + CloseHandle(hProcessSnap); // Clean up the snapshot object + return FALSE; // Process not found +} + + +#endif +static Engine* get_engine(int); +static void speech_engine_update() { + if (!g_currentEngine->GetActive() || g_currentEngine->GetNumber() == ENGINE_SAPI || g_currentEngine->GetNumber() == ENGINE_UIA) { +#ifdef _WIN32 + if (FindProcess(L"narrator.exe") == TRUE) { + g_currentEngine = get_engine(ENGINE_UIA); + return; + } + else { +#endif + for (uint64_t i = 0; i < g_engines.size(); ++i) { + if (g_engines[i]->GetActive() && !(g_excludes & g_engines[i]->GetNumber())) { + g_currentEngine = g_engines[i]; + break; + } + } + } +#ifdef _WIN32 + } +#endif +} +static Engine* get_engine(int engine) { +#ifdef _WIN32 + if (engine == ENGINE_NARRATOR)return get_engine(ENGINE_UIA); +#endif + bool found = false; + uint64_t i; + for (i = 0; i < g_engines.size(); ++i) { + if (g_engines[i]->GetNumber() == engine) { + found = true; + break; + } + } + if (!found)return nullptr; + return g_engines[i]; +} +extern "C" SRAL_API bool SRAL_Speak(const char* text, bool interrupt) { + if (g_currentEngine == nullptr) return false; + speech_engine_update(); + if (!g_delayOperation) + return g_currentEngine->Speak(text, interrupt); + else { + QueuedOutput qout; + qout.text = text; + qout.interrupt = interrupt; + qout.braille = false; + qout.speak = true; + qout.ssml = false; + qout.engine = g_currentEngine; + qout.time = g_lastDelayTime; + g_delayedOutputs.push_back(qout); + if (!g_outputThreadRunning) { + std::thread t(output_thread); + t.detach(); + } + return true; + } + return false; +} +extern "C" SRAL_API bool SRAL_SpeakSsml(const char* ssml, bool interrupt) { + if (g_currentEngine == nullptr) return false; + speech_engine_update(); + if (!g_delayOperation) + return g_currentEngine->SpeakSsml(ssml, interrupt); + else { + QueuedOutput qout; + qout.text = ssml; + qout.interrupt = interrupt; + qout.braille = false; + qout.speak = true; + qout.ssml = true; + qout.engine = g_currentEngine; + qout.time = g_lastDelayTime; + g_delayedOutputs.push_back(qout); + if (!g_outputThreadRunning) { + std::thread t(output_thread); + t.detach(); + } + return true; + } + return false; +} + +extern "C" SRAL_API bool SRAL_Braille(const char* text) { + if (g_currentEngine == nullptr)return false; + speech_engine_update(); + return g_currentEngine->Braille(text); +} +extern "C" SRAL_API bool SRAL_Output(const char* text, bool interrupt) { + if (g_currentEngine == nullptr)return false; + speech_engine_update(); + const bool speech = SRAL_Speak(text, interrupt); + const bool braille = SRAL_Braille(text); + return speech || braille; +} +extern "C" SRAL_API bool SRAL_StopSpeech(void) { + if (g_currentEngine == nullptr)return false; + speech_engine_update(); + if (g_delayOperation) { + g_delayedOutputs.clear(); + g_delayOperation = false; + } + return g_currentEngine->StopSpeech(); +} +extern "C" SRAL_API bool SRAL_PauseSpeech(void) { + if (g_currentEngine == nullptr)return false; + speech_engine_update(); + if (g_delayOperation) { + // Just stop the thread. Don't clear the queue + g_delayOperation = false; + } + return g_currentEngine->PauseSpeech(); +} +extern "C" SRAL_API bool SRAL_ResumeSpeech(void) { + if (g_currentEngine == nullptr)return false; + speech_engine_update(); + if (g_delayedOutputs.size() != 0) { + g_delayOperation = true; + if (!g_outputThreadRunning) { + std::thread t(output_thread); + t.detach(); + } + } + return g_currentEngine->ResumeSpeech(); +} + +extern "C" SRAL_API int SRAL_GetCurrentEngine(void) { + if (g_currentEngine == nullptr)return ENGINE_NONE; + return g_currentEngine->GetNumber(); +} +extern "C" SRAL_API int SRAL_GetEngineFeatures(int engine) { + if (engine == 0) { + if (g_currentEngine == nullptr)return -1; + return g_currentEngine->GetFeatures(); + } + else { + Engine* e = get_engine(engine); + if (e == nullptr)return -1; + return e->GetFeatures(); + } + return -1; +} +extern "C" SRAL_API bool SRAL_SetVolume(uint64_t value) { + if (g_currentEngine == nullptr)return false; + g_currentEngine->SetVolume(value); + return true; +} +extern "C" SRAL_API uint64_t SRAL_GetVolume(void) { + if (g_currentEngine == nullptr)return false; + return g_currentEngine->GetVolume(); +} +extern "C" SRAL_API bool SRAL_SetRate(uint64_t value) { + if (g_currentEngine == nullptr)return false; + g_currentEngine->SetRate(value); + return true; +} +extern "C" SRAL_API uint64_t SRAL_GetRate(void) { + if (g_currentEngine == nullptr)return false; + return g_currentEngine->GetRate(); +} + +extern "C" SRAL_API uint64_t SRAL_GetVoiceCount(void) { + if (g_currentEngine == nullptr)return false; + return g_currentEngine->GetVoiceCount(); +} +extern "C" SRAL_API const char* SRAL_GetVoiceName(uint64_t index) { + if (g_currentEngine == nullptr)return nullptr; + return g_currentEngine->GetVoiceName(index); +} +extern "C" SRAL_API bool SRAL_SetVoice(uint64_t index) { + if (g_currentEngine == nullptr)return false; + return g_currentEngine->SetVoice(index); +} + +extern "C" SRAL_API bool SRAL_SetVolumeEx(int engine, uint64_t value) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + e->SetVolume(value); + return true; +} +extern "C" SRAL_API uint64_t SRAL_GetVolumeEx(int engine) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + return e->GetVolume(); +} +extern "C" SRAL_API bool SRAL_SetRateEx(int engine, uint64_t value) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + e->SetRate(value); + return true; +} +extern "C" SRAL_API uint64_t SRAL_GetRateEx(int engine) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + return e->GetRate(); +} + +extern "C" SRAL_API uint64_t SRAL_GetVoiceCountEx(int engine) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + return e->GetVoiceCount(); +} +extern "C" SRAL_API const char* SRAL_GetVoiceNameEx(int engine, uint64_t index) { + Engine* e = get_engine(engine); + if (e == nullptr)return nullptr; + return e->GetVoiceName(index); +} +extern "C" SRAL_API bool SRAL_SetVoiceEx(int engine, uint64_t index) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + return e->SetVoice(index); +} + + + +extern "C" SRAL_API bool SRAL_SetEngineParameter(int engine, int param, int value) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + return e->SetParameter(param, value); +} + + + +extern "C" SRAL_API bool SRAL_SpeakEx(int engine, const char* text, bool interrupt) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + if (!g_delayOperation) + return e->Speak(text, interrupt); + else { + QueuedOutput qout; + qout.text = text; + qout.interrupt = interrupt; + qout.braille = false; + qout.speak = true; + qout.ssml = false; + qout.engine = e; + qout.time = g_lastDelayTime; + g_delayedOutputs.push_back(qout); + if (!g_outputThreadRunning) { + std::thread t(output_thread); + t.detach(); + } + return true; + } + return false; +} +extern "C" SRAL_API bool SRAL_SpeakSsmlEx(int engine, const char* ssml, bool interrupt) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + if (!g_delayOperation) + return e->SpeakSsml(ssml, interrupt); + else { + QueuedOutput qout; + qout.text = ssml; + qout.interrupt = interrupt; + qout.braille = false; + qout.speak = true; + qout.ssml = true; + qout.engine = e; + qout.time = g_lastDelayTime; + g_delayedOutputs.push_back(qout); + if (!g_outputThreadRunning) { + std::thread t(output_thread); + t.detach(); + } + return true; + } + return false; +} + +extern "C" SRAL_API bool SRAL_BrailleEx(int engine, const char* text) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + return e->Braille(text); +} +extern "C" SRAL_API bool SRAL_OutputEx(int engine, const char* text, bool interrupt) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + const bool speech = e->Speak(text, interrupt); + const bool braille = e->Braille(text); + return speech || braille; +} +extern "C" SRAL_API bool SRAL_StopSpeechEx(int engine) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + if (g_delayOperation) { + g_delayedOutputs.clear(); + g_delayOperation = false; + } + return e->StopSpeech(); +} + + +extern "C" SRAL_API bool SRAL_PauseSpeechEx(int engine) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + if (g_delayOperation) { + g_delayOperation = false; + } + return e->PauseSpeech(); +} + + + +extern "C" SRAL_API bool SRAL_ResumeSpeechEx(int engine) { + Engine* e = get_engine(engine); + if (e == nullptr)return false; + if (g_delayedOutputs.size() != 0) { + g_delayOperation = true; + if (!g_outputThreadRunning) { + std::thread t(output_thread); + t.detach(); + } + } + return e->ResumeSpeech(); +} + + +extern "C" SRAL_API bool SRAL_IsInitialized(void) { + return g_initialized; +} + + + +extern "C" SRAL_API void SRAL_Delay(int time) { + g_lastDelayTime = time; + g_delayOperation = true; +} diff --git a/SRC/SpeechDispatcher.cpp b/SRC/SpeechDispatcher.cpp index bc7a629..7c68c89 100644 --- a/SRC/SpeechDispatcher.cpp +++ b/SRC/SpeechDispatcher.cpp @@ -1,69 +1,81 @@ -#include "SpeechDispatcher.h" -bool SpeechDispatcher::Initialize() { - const auto* address = spd_get_default_address(nullptr); - if (address == nullptr) { - return false; - } - Speech = spd_open2("SRAL", nullptr, nullptr, SPD_MODE_THREADED, address, true, nullptr); - if (Speech == nullptr) { - return false; - } - - return true; -} -bool SpeechDispatcher::GetActive() { - return Speech != nullptr; -} -bool SpeechDispatcher::Uninitialize() { - if (Speech == nullptr)return false; - spd_close(Speech); - Speech = nullptr; - return true; -} -bool SpeechDispatcher::Speak(const char* text, bool interrupt) { - if (Speech == nullptr)return false; - if (interrupt) { - spd_stop(Speech); - spd_cancel(Speech); - } - if (this->paused) { - this->ResumeSpeech(); - this->paused = false; - - } - return spd_say(Speech, SPD_IMPORTANT, text); -} -bool SpeechDispatcher::StopSpeech() { - if (Speech == nullptr)return false; - spd_stop(Speech); - spd_cancel(Speech); - return true; -} -bool SpeechDispatcher::PauseSpeech() { - if (!GetActive())return false; - this->paused = true; - return spd_pause_all(Speech) == 0; -} -bool SpeechDispatcher::ResumeSpeech() { - if (!GetActive())return false; - this->paused = false; - return spd_resume_all(Speech) == 0; -} - - -void SpeechDispatcher::SetVolume(uint64_t value) { - if (!GetActive())return; - spd_set_volume(Speech, value); -} -uint64_t SpeechDispatcher::GetVolume() { - if (!GetActive())return 0; - return spd_get_volume(Speech); -} -void SpeechDispatcher::SetRate(uint64_t value) { - if (!GetActive())return; - spd_set_voice_rate(Speech, value); -} -uint64_t SpeechDispatcher::GetRate() { - if (!GetActive())return 0; - return spd_get_voice_rate(Speech); -} +#include "SpeechDispatcher.h" +bool SpeechDispatcher::Initialize() { + const auto* address = spd_get_default_address(nullptr); + if (address == nullptr) { + return false; + } + Speech = spd_open2("SRAL", nullptr, nullptr, SPD_MODE_THREADED, address, true, nullptr); + if (Speech == nullptr) { + return false; + } + + return true; +} +bool SpeechDispatcher::GetActive() { + return Speech != nullptr; +} +bool SpeechDispatcher::Uninitialize() { + if (Speech == nullptr)return false; + spd_close(Speech); + Speech = nullptr; + return true; +} +bool SpeechDispatcher::Speak(const char* text, bool interrupt) { + if (Speech == nullptr)return false; + if (interrupt) { + spd_stop(Speech); + spd_cancel(Speech); + } + if (this->paused) { + this->ResumeSpeech(); + this->paused = false; + + } + return spd_say(Speech, SPD_IMPORTANT, text); +} +bool SpeechDispatcher::SetParameter(int param, int value) { + switch (param) { + case SYMBOL_LEVEL: + spd_set_punctuation(Speech, (SPDPunctuation)value); + break; + default: + return false; + } + return true; +} + + +bool SpeechDispatcher::StopSpeech() { + if (Speech == nullptr)return false; + spd_stop(Speech); + spd_cancel(Speech); + return true; +} +bool SpeechDispatcher::PauseSpeech() { + if (!GetActive())return false; + this->paused = true; + return spd_pause_all(Speech) == 0; +} +bool SpeechDispatcher::ResumeSpeech() { + if (!GetActive())return false; + this->paused = false; + return spd_resume_all(Speech) == 0; +} + + +void SpeechDispatcher::SetVolume(uint64_t value) { + if (!GetActive())return; + spd_set_volume(Speech, value); +} +uint64_t SpeechDispatcher::GetVolume() { + if (!GetActive())return 0; + return spd_get_volume(Speech); +} +void SpeechDispatcher::SetRate(uint64_t value) { + if (!GetActive())return; + spd_set_voice_rate(Speech, value); +} +uint64_t SpeechDispatcher::GetRate() { + if (!GetActive())return 0; + return spd_get_voice_rate(Speech); +} diff --git a/SRC/SpeechDispatcher.h b/SRC/SpeechDispatcher.h index 57d4f3a..f336db8 100644 --- a/SRC/SpeechDispatcher.h +++ b/SRC/SpeechDispatcher.h @@ -1,54 +1,52 @@ -#ifndef SPEECHDISPATCHER_H_ -#define SPEECHDISPATCHER_H_ -#include "../Include/SRAL.h" -#include "Engine.h" -#include - - -class SpeechDispatcher : public Engine { -public: - bool Speak(const char* text, bool interrupt)override; - bool SpeakSsml(const char* ssml, bool interrupt)override { - return false; - } - bool SetParameter(int param, int value)override { - return false; - } - - - bool Braille(const char* text)override { return false; } - bool StopSpeech()override; - bool PauseSpeech()override; - bool ResumeSpeech()override; - - int GetNumber()override { - return ENGINE_SPEECH_DISPATCHER; - } - bool GetActive()override; - bool Initialize()override; - bool Uninitialize()override; - int GetFeatures()override { - return SUPPORTS_SPEECH | SUPPORTS_SPEECH_RATE | SUPPORTS_SPEECH_VOLUME | SUPPORTS_PAUSE_SPEECH; - } - void SetVolume(uint64_t)override; - uint64_t GetVolume()override; - void SetRate(uint64_t)override; - uint64_t GetRate()override; - uint64_t GetVoiceCount()override { - return 0; - } - const char* GetVoiceName(uint64_t index)override { - return nullptr; - } - bool SetVoice(uint64_t index)override { - return false; - } - int GetKeyFlags()override { - return HANDLE_INTERRUPT | HANDLE_PAUSE_RESUME; - } - -private: - SPDConnection* Speech = nullptr; -}; - +#ifndef SPEECHDISPATCHER_H_ +#define SPEECHDISPATCHER_H_ +#include "../Include/SRAL.h" +#include "Engine.h" +#include + + +class SpeechDispatcher : public Engine { +public: + bool Speak(const char* text, bool interrupt)override; + bool SpeakSsml(const char* ssml, bool interrupt)override { + return false; + } + bool SetParameter(int param, int value)override; + + + bool Braille(const char* text)override { return false; } + bool StopSpeech()override; + bool PauseSpeech()override; + bool ResumeSpeech()override; + + int GetNumber()override { + return ENGINE_SPEECH_DISPATCHER; + } + bool GetActive()override; + bool Initialize()override; + bool Uninitialize()override; + int GetFeatures()override { + return SUPPORTS_SPEECH | SUPPORTS_SPEECH_RATE | SUPPORTS_SPEECH_VOLUME | SUPPORTS_PAUSE_SPEECH; + } + void SetVolume(uint64_t)override; + uint64_t GetVolume()override; + void SetRate(uint64_t)override; + uint64_t GetRate()override; + uint64_t GetVoiceCount()override { + return 0; + } + const char* GetVoiceName(uint64_t index)override { + return nullptr; + } + bool SetVoice(uint64_t index)override { + return false; + } + int GetKeyFlags()override { + return HANDLE_INTERRUPT | HANDLE_PAUSE_RESUME; + } + +private: + SPDConnection* Speech = nullptr; +}; + #endif \ No newline at end of file diff --git a/SRC/UIA.cpp b/SRC/UIA.cpp index 784626c..26a4687 100644 --- a/SRC/UIA.cpp +++ b/SRC/UIA.cpp @@ -1,59 +1,59 @@ -#include "Encoding.h" -#include "UIA.h" -#include -#include - - -bool UIA::Initialize() { - HRESULT hr = CoInitialize(NULL); - hr = CoCreateInstance(CLSID_CUIAutomation, NULL, CLSCTX_INPROC_SERVER, IID_IUIAutomation, (void**)&pAutomation); - if (FAILED(hr)) { - return false; - } - - varName.vt = VT_BSTR; - varName.bstrVal = _bstr_t(L""); - hr = pAutomation->CreatePropertyConditionEx(UIA_NamePropertyId, varName, PropertyConditionFlags_None, &pCondition); - if (FAILED(hr)) { - return false; - } - return true; -} - -bool UIA::Uninitialize() { - pProvider->Release(); - - pCondition->Release(); - - pAutomation->Release(); - - return true; -} -bool UIA::Speak(const char* text, bool interrupt) { - NotificationProcessing flags = NotificationProcessing_ImportantAll; - if (interrupt) - flags = NotificationProcessing_ImportantMostRecent; - std::wstring str; - UnicodeConvert(text, str); - pProvider = new Provider(GetForegroundWindow()); - printf("Test"); - - HRESULT hr = pAutomation->ElementFromHandle(GetForegroundWindow(), &pElement); - - if (FAILED(hr)) { - return false; - } - hr = UiaRaiseNotificationEvent(pProvider, NotificationKind_ActionCompleted, flags, _bstr_t(str.c_str()), _bstr_t(L"")); - if (FAILED(hr)) { - return false; - } - - return true; - -} -bool UIA::StopSpeech() { - return Speak("", true); -} -bool UIA::GetActive() { - return true; +#include "Encoding.h" +#include "UIA.h" +#include +#include + + +bool UIA::Initialize() { + HRESULT hr = CoInitialize(NULL); + hr = CoCreateInstance(CLSID_CUIAutomation, NULL, CLSCTX_INPROC_SERVER, IID_IUIAutomation, (void**)&pAutomation); + if (FAILED(hr)) { + return false; + } + + varName.vt = VT_BSTR; + varName.bstrVal = _bstr_t(L""); + hr = pAutomation->CreatePropertyConditionEx(UIA_NamePropertyId, varName, PropertyConditionFlags_None, &pCondition); + if (FAILED(hr)) { + return false; + } + return true; +} + +bool UIA::Uninitialize() { + pProvider->Release(); + + pCondition->Release(); + + pAutomation->Release(); + + return true; +} +bool UIA::Speak(const char* text, bool interrupt) { + NotificationProcessing flags = NotificationProcessing_ImportantAll; + if (interrupt) + flags = NotificationProcessing_ImportantMostRecent; + std::wstring str; + UnicodeConvert(text, str); + pProvider = new Provider(GetForegroundWindow()); + printf("Test"); + + HRESULT hr = pAutomation->ElementFromHandle(GetForegroundWindow(), &pElement); + + if (FAILED(hr)) { + return false; + } + hr = UiaRaiseNotificationEvent(pProvider, NotificationKind_ActionCompleted, flags, _bstr_t(str.c_str()), _bstr_t(L"")); + if (FAILED(hr)) { + return false; + } + + return true; + +} +bool UIA::StopSpeech() { + return Speak("", true); +} +bool UIA::GetActive() { + return true; } \ No newline at end of file diff --git a/SRC/UIA.h b/SRC/UIA.h index b6bc196..b7ef6f4 100644 --- a/SRC/UIA.h +++ b/SRC/UIA.h @@ -1,58 +1,58 @@ -#ifndef UIA_H_ -#define UIA_H_ -#pragma once -#include "../Dep/UIAProvider.h" -#include "../Include/SRAL.h" -#include "Engine.h" - - -class UIA : public Engine { -public: - bool Speak(const char* text, bool interrupt)override; - bool SpeakSsml(const char* ssml, bool interrupt)override { - return false; - } - bool SetParameter(int param, int value)override { - return false; - } - - - bool Braille(const char* text)override { return false; } - bool StopSpeech()override; - bool PauseSpeech()override { return false; } - bool ResumeSpeech()override { return false; } - - int GetNumber()override { - return ENGINE_UIA; - } - bool GetActive()override; - bool Initialize()override; - bool Uninitialize()override; - int GetFeatures()override { - return SUPPORTS_SPEECH; - } - void SetVolume(uint64_t)override { return; } - uint64_t GetVolume()override { return 0; } - void SetRate(uint64_t)override { return; } - uint64_t GetRate()override { return 0; } - uint64_t GetVoiceCount()override { - return 0; - } - const char* GetVoiceName(uint64_t index)override { - return nullptr; - } - bool SetVoice(uint64_t index)override { - return false; - } - int GetKeyFlags()override { - return HANDLE_NONE; - } - -private: - IUIAutomation* pAutomation = nullptr; - IUIAutomationCondition* pCondition = nullptr; - VARIANT varName; - Provider* pProvider = nullptr; - IUIAutomationElement* pElement = nullptr; -}; +#ifndef UIA_H_ +#define UIA_H_ +#pragma once +#include "../Dep/UIAProvider.h" +#include "../Include/SRAL.h" +#include "Engine.h" + + +class UIA : public Engine { +public: + bool Speak(const char* text, bool interrupt)override; + bool SpeakSsml(const char* ssml, bool interrupt)override { + return false; + } + bool SetParameter(int param, int value)override { + return false; + } + + + bool Braille(const char* text)override { return false; } + bool StopSpeech()override; + bool PauseSpeech()override { return false; } + bool ResumeSpeech()override { return false; } + + int GetNumber()override { + return ENGINE_UIA; + } + bool GetActive()override; + bool Initialize()override; + bool Uninitialize()override; + int GetFeatures()override { + return SUPPORTS_SPEECH; + } + void SetVolume(uint64_t)override { return; } + uint64_t GetVolume()override { return 0; } + void SetRate(uint64_t)override { return; } + uint64_t GetRate()override { return 0; } + uint64_t GetVoiceCount()override { + return 0; + } + const char* GetVoiceName(uint64_t index)override { + return nullptr; + } + bool SetVoice(uint64_t index)override { + return false; + } + int GetKeyFlags()override { + return HANDLE_NONE; + } + +private: + IUIAutomation* pAutomation = nullptr; + IUIAutomationCondition* pCondition = nullptr; + VARIANT varName; + Provider* pProvider = nullptr; + IUIAutomationElement* pElement = nullptr; +}; #endif \ No newline at end of file