diff --git a/Assets/UAP/NativePlugins~/WindowsTTS/WindowsTTS.h b/Assets/UAP/NativePlugins~/WindowsTTS/WindowsTTS.h index 4263ce4..c041056 100644 --- a/Assets/UAP/NativePlugins~/WindowsTTS/WindowsTTS.h +++ b/Assets/UAP/NativePlugins~/WindowsTTS/WindowsTTS.h @@ -5,6 +5,7 @@ #endif #include +#include #include #include @@ -22,6 +23,7 @@ namespace WindowsVoice { } std::mutex theMutex; + std::condition_variable cv; std::list theSpeechQueue; std::thread* theSpeechThread; bool stopSpeech; diff --git a/Assets/UAP/NativePlugins~/WindowsTTS/dllmain.cpp b/Assets/UAP/NativePlugins~/WindowsTTS/dllmain.cpp index eadb2e4..8687352 100644 --- a/Assets/UAP/NativePlugins~/WindowsTTS/dllmain.cpp +++ b/Assets/UAP/NativePlugins~/WindowsTTS/dllmain.cpp @@ -10,28 +10,30 @@ #include -namespace WindowsVoice +namespace WindowsVoice { int Volume = 100; int Rate = 0; bool IsSpeaking = false; - ISpVoice * pVoice = NULL; + ISpVoice* pVoice = NULL; void SpeechThreadFunc() { + theMutex.lock(); //SpeechSynthesizer* synth = new SpeechSynthesizer(); - SPVOICESTATUS *pStatus = new SPVOICESTATUS(); + SPVOICESTATUS* pStatus = new SPVOICESTATUS(); if (FAILED(::CoInitializeEx(NULL, COINITBASE_MULTITHREADED))) { //std::cout<<"Failed to initialize COM for Voice.\n"; + theMutex.unlock(); return; } - HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void **)&pVoice); + HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)&pVoice); pVoice->SetRate(Rate); pVoice->SetVolume(Volume); if (!SUCCEEDED(hr)) @@ -39,12 +41,14 @@ namespace WindowsVoice LPVOID pText = 0; ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&pText, 0, NULL); + NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&pText, 0, NULL); //std::cout<<"Failed to create voice instance. Error: "<Speak(wText, SPF_IS_NOT_XML | SPF_ASYNC, NULL); - // Sleep(250); - delete[] priorText; - priorText = wText; + pVoice->Speak(wText, SPF_IS_NOT_XML | SPF_ASYNC, NULL); + // Sleep(250); + delete[] priorText; + priorText = wText; } } pVoice->Release(); @@ -103,10 +107,11 @@ namespace WindowsVoice if (text) { int len = strlen(text) + 1; - wchar_t *wText = new wchar_t[len]; + wchar_t* wText = new wchar_t[len]; memset(wText, 0, len); - ::MultiByteToWideChar(CP_ACP, NULL, text, -1, wText, len); + // Use CP_UTF8 for proper unicode support + ::MultiByteToWideChar(CP_UTF8, NULL, text, -1, wText, len); theMutex.lock(); theSpeechQueue.push_back(wText); @@ -122,7 +127,9 @@ namespace WindowsVoice shouldTerminate = false; stopSpeech = false; + std::unique_lock lk(theMutex); theSpeechThread = new std::thread(WindowsVoice::SpeechThreadFunc); + while (pVoice == NULL) cv.wait(lk); } ////////////////////////////////////////////////////////////////////////// @@ -159,6 +166,7 @@ namespace WindowsVoice { theMutex.lock(); Rate = rate; + pVoice->SetRate(Rate); theMutex.unlock(); } @@ -182,7 +190,7 @@ namespace WindowsVoice { // convert char to WCHAR int len = strlen(voiceDescription) + 1; - wchar_t *wText = new wchar_t[len]; + wchar_t* wText = new wchar_t[len]; memset(wText, 0, len); ::MultiByteToWideChar(CP_ACP, NULL, voiceDescription, -1, wText, len); @@ -190,12 +198,14 @@ namespace WindowsVoice ISpObjectToken* cpToken(NULL); SpFindBestToken(SPCAT_VOICES, wText, L"", &cpToken); - // Set the voice - theMutex.lock(); - pVoice->SetVoice(cpToken); - theMutex.unlock(); - - cpToken->Release(); + //if there's a valid token + if (cpToken) { + // Set the voice + theMutex.lock(); + pVoice->SetVoice(cpToken); + theMutex.unlock(); + cpToken->Release(); + } } } diff --git a/Assets/UAP/NativePlugins~/WindowsTTS/framework.h b/Assets/UAP/NativePlugins~/WindowsTTS/framework.h new file mode 100644 index 0000000..54b83e9 --- /dev/null +++ b/Assets/UAP/NativePlugins~/WindowsTTS/framework.h @@ -0,0 +1,5 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include diff --git a/Assets/UAP/NativePlugins~/WindowsTTS/pch.cpp b/Assets/UAP/NativePlugins~/WindowsTTS/pch.cpp new file mode 100644 index 0000000..64b7eef --- /dev/null +++ b/Assets/UAP/NativePlugins~/WindowsTTS/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/Assets/UAP/NativePlugins~/WindowsTTS/pch.h b/Assets/UAP/NativePlugins~/WindowsTTS/pch.h new file mode 100644 index 0000000..885d5d6 --- /dev/null +++ b/Assets/UAP/NativePlugins~/WindowsTTS/pch.h @@ -0,0 +1,13 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +// add headers that you want to pre-compile here +#include "framework.h" + +#endif //PCH_H diff --git a/Assets/UAP/Plugins/MacOS/MacOSTTS.cs b/Assets/UAP/Plugins/MacOS/MacOSTTS.cs index 7b9b374..7ab4008 100644 --- a/Assets/UAP/Plugins/MacOS/MacOSTTS.cs +++ b/Assets/UAP/Plugins/MacOS/MacOSTTS.cs @@ -11,6 +11,7 @@ public class MacOSTTS : MonoBehaviour { public static MacOSTTS instance = null; bool m_IsSpeaking = false; + private static string m_VoiceName = null; #if (UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX) Process m_VoiceProcess = null; @@ -57,6 +58,10 @@ IEnumerator SpeakText(string textToSpeak) textToSpeak = textToSpeak.Replace('"', '\''); int speechRate = (int)((UAP_AccessibilityManager.GetSpeechRate() / 100.0f) * 175 * 2); string parameters = "-r " + speechRate + " " + '"' + textToSpeak + '"'; + if (m_VoiceName != null) + { + parameters = "-v " + m_VoiceName + " " + parameters; + } m_VoiceProcess = new System.Diagnostics.Process(); m_VoiceProcess.StartInfo.FileName = "say"; @@ -137,4 +142,15 @@ void OnDestroy() ////////////////////////////////////////////////////////////////////////// + /// + /// Sets the voice used by the say command + /// + /// + /// Name of the voice to be used. + /// List of available voices can be found by using the terminal command "say -v '?'". + /// + public static void SetVoice(string name) + { + m_VoiceName = name; + } } diff --git a/Assets/UAP/Plugins/x86/WindowsTTS.dll b/Assets/UAP/Plugins/x86/WindowsTTS.dll index 7f21339..4163265 100644 Binary files a/Assets/UAP/Plugins/x86/WindowsTTS.dll and b/Assets/UAP/Plugins/x86/WindowsTTS.dll differ diff --git a/Assets/UAP/Plugins/x86_64/WindowsTTS.cs b/Assets/UAP/Plugins/x86_64/WindowsTTS.cs index 732a375..ef67a6c 100644 --- a/Assets/UAP/Plugins/x86_64/WindowsTTS.cs +++ b/Assets/UAP/Plugins/x86_64/WindowsTTS.cs @@ -1,6 +1,7 @@ using UnityEngine; using System.Collections; using System.Runtime.InteropServices; +using System.Collections.Generic; public class WindowsTTS : MonoBehaviour { @@ -15,10 +16,12 @@ public class WindowsTTS : MonoBehaviour public static extern void AddToSpeechQueue(string s); //[DllImport("WindowsTTS")] //public static extern void SetVolume(int volume); - //[DllImport("WindowsTTS")] - //public static extern void SetRate(int rate); + [DllImport("WindowsTTS")] + public static extern void SetRate(int rate); [DllImport("WindowsTTS")] public static extern bool IsVoiceSpeaking(); + [DllImport("WindowsTTS")] + public static extern void SetVoiceSAPI(string s); [DllImport("nvdaControllerClient")] internal static extern int nvdaController_testIfRunning(); @@ -78,7 +81,7 @@ void Start() } ////////////////////////////////////////////////////////////////////////// - + public static void Speak(string msg) { if (m_UseNVDA) @@ -94,6 +97,20 @@ public static void Speak(string msg) ////////////////////////////////////////////////////////////////////////// + /// + /// Sets the voice used by the SAPI API + /// + /// Name of the voice to be used. Can be found under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\[voice]\Attributes. + public static void SetVoice(string name) + { + if (!m_UseNVDA) + { + SetVoiceSAPI("Name=" + name); + } + } + + ////////////////////////////////////////////////////////////////////////// + public static void Stop() { if (m_UseNVDA) @@ -133,22 +150,30 @@ void Update() m_NVDAIsSpeakingTimer -= Time.unscaledDeltaTime; } -/* - ////////////////////////////////////////////////////////////////////////// + /* + ////////////////////////////////////////////////////////////////////////// - public static void SetSpeechVolume(int volume) - { - SetVolume(volume); - } + public static void SetSpeechVolume(int volume) + { + SetVolume(volume); + } + + */ ////////////////////////////////////////////////////////////////////////// public static void SetSpeechRate(int rate) { + //if using SAPI + if (!m_UseNVDA) + { + //normalize the standard 1-100 rate to SAPI -10 to 10 range + rate = -10 + (int)((rate - 1) * 0.202); + } SetRate(rate); } -*/ - + + ////////////////////////////////////////////////////////////////////////// void OnDestroy() diff --git a/Assets/UAP/Plugins/x86_64/WindowsTTS.dll b/Assets/UAP/Plugins/x86_64/WindowsTTS.dll index 2452e01..7d85a7c 100644 Binary files a/Assets/UAP/Plugins/x86_64/WindowsTTS.dll and b/Assets/UAP/Plugins/x86_64/WindowsTTS.dll differ diff --git a/Assets/UAP/Scripts/Core/UAP_AccessibilityManager.cs b/Assets/UAP/Scripts/Core/UAP_AccessibilityManager.cs index 9c61b0f..5e98ae8 100644 --- a/Assets/UAP/Scripts/Core/UAP_AccessibilityManager.cs +++ b/Assets/UAP/Scripts/Core/UAP_AccessibilityManager.cs @@ -1348,7 +1348,8 @@ void ReadType() //if (!m_CurrentItem.m_Object.IsInteractable()) // SayAudio(m_DisabledAsAudio, "Disabled", UAP_AudioQueue.EAudioType.Element_Hint); break; - }; + } + ; } ////////////////////////////////////////////////////////////////////////// @@ -1420,7 +1421,8 @@ void ReadHint() else SayAudio(null, Localize_Internal("Desktop_HintSlider"), UAP_AudioQueue.EAudioType.Element_Hint, m_CurrentItem.m_Object.m_AllowVoiceOver); break; - }; + } + ; } } @@ -1730,7 +1732,7 @@ private void ActivateContainer_Internal(AccessibleUIGroupRoot container, bool ac if (m_ActiveContainers[0] == container && container.m_AutoRead) { - ReadFromTop(); + ReadFromTop(); } else { @@ -1745,7 +1747,7 @@ private void ActivateContainer_Internal(AccessibleUIGroupRoot container, bool ac if (m_ActiveContainers[m_ActiveContainerIndex] == container && container.m_AutoRead) { - ReadFromTop(); + ReadFromTop(); } else { @@ -4545,7 +4547,7 @@ static public bool MakeActiveContainer(AccessibleUIGroupRoot container, bool for if (!instance.m_ActiveContainers.Contains(container)) { - if(instance.m_DebugOutput && m_IsEnabled) + if (instance.m_DebugOutput && m_IsEnabled) Debug.LogWarning("[Accessibility] Trying to select an item in a container that is inactive. Ignoring call."); return false; } @@ -4706,6 +4708,22 @@ static public void StopSpeaking() ////////////////////////////////////////////////////////////////////////// + /// + /// Set the voice used by the screen reader. + /// Works with Windows SAPI TTS and Mac OS TTS, but the available voices are different for each. + /// For windows, available voices can be found in the registry under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech\Voices\Tokens\[voice]\Attributes\Name + /// For mac, available voices can be found by running the command "say -v '?'". + /// + /// Name of the voice to be used + /// + static public string SetVoice(string voice) + { + Initialize(); + return instance.m_AudioQueue.SetVoice(voice); + } + + ////////////////////////////////////////////////////////////////////////// + /// /// This function is called from the Accessible UI component if the Is Localization Key checkbox is ticked. /// @@ -4771,7 +4789,7 @@ static public string FormatNumberToCurrentLocale(ulong intNumber) //Debug.Log(CultureInfo.CurrentCulture.ToString() + " " + formattedNumber); return formattedNumber; } - + static public string FormatNumberToCurrentLocale(double floatNumber) { string formattedNumber;// = string.Format(CultureInfo.CurrentCulture, "{0:n0}", intNumber); diff --git a/Assets/UAP/Scripts/Core/UAP_AudioQueue.cs b/Assets/UAP/Scripts/Core/UAP_AudioQueue.cs index 6859258..daa1500 100644 --- a/Assets/UAP/Scripts/Core/UAP_AudioQueue.cs +++ b/Assets/UAP/Scripts/Core/UAP_AudioQueue.cs @@ -1,4 +1,5 @@ using UnityEngine; +using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; @@ -28,6 +29,11 @@ public class UAP_AudioQueue : MonoBehaviour /// private int m_SpeechRate = 65; + /// + /// The name of the current screen reader voice + /// + private string m_SpeechVoice = ""; + private AudioSource m_AudioPlayer = null; private Queue m_AudioQueue = new Queue(); private SAudioEntry m_ActiveEntry = null; @@ -622,6 +628,14 @@ public int SetSpeechRate(int speechRate) if (m_SpeechRate > 100) m_SpeechRate = 100; + #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN + if (UAP_AccessibilityManager.UseWindowsTTS() && WindowsTTS.instance != null) + { + //use WindowsTTS to set the rate + WindowsTTS.SetSpeechRate(m_SpeechRate); + } + #endif + PlayerPrefs.SetInt("Accessibility_Speech_Rate", m_SpeechRate); PlayerPrefs.Save(); @@ -630,6 +644,30 @@ public int SetSpeechRate(int speechRate) ////////////////////////////////////////////////////////////////////////// + public string SetVoice(string newVoice) + { + m_SpeechVoice = newVoice; + + #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN + if (UAP_AccessibilityManager.UseWindowsTTS() && WindowsTTS.instance != null) + { + //use WindowsTTS to set the voice + WindowsTTS.SetVoice(m_SpeechVoice); + } + #elif UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX + if (UAP_AccessibilityManager.UseMacOSTTS() && MacOSTTS.instance != null) + { + //use MacOSTTS to set the voice + MacOSTTS.SetVoice(m_SpeechVoice); + } + #endif + + return m_SpeechVoice; + } + + ////////////////////////////////////////////////////////////////////////// + + /// /// This function is used to activate third-party custom Text-to-Speech engines. /// @@ -645,4 +683,3 @@ private void InitializeCustomTTS() ////////////////////////////////////////////////////////////////////////// } -