#if (UNITY_WSA && !UNITY_EDITOR) && ENABLE_WINMD_SUPPORT //|| CT_DEVELOP using UnityEngine; using System.Collections; using System.Linq; namespace Crosstales.RTVoice.Provider { /// WSA (UWP) voice provider. public class VoiceProviderWSA : BaseVoiceProvider { #region Variables private static VoiceProviderWSA instance; private static RTVoiceUWPBridge ttsHandler; private bool isLoading; #endregion #region Properties /// Returns the singleton instance of this class. /// Singleton instance of this class. public static VoiceProviderWSA Instance { get { if (instance == null) instance = new VoiceProviderWSA(); return instance; } } public override string AudioFileExtension => ".wav"; public override AudioType AudioFileType => AudioType.WAV; public override string DefaultVoiceName => "Microsoft David"; public override bool isWorkingInEditor => false; public override bool isWorkingInPlaymode => false; public override int MaxTextLength => 64000; public override bool isSpeakNativeSupported => false; public override bool isSpeakSupported => true; public override bool isPlatformSupported => Util.Helper.isWSABasedPlatform; public override bool isSSMLSupported => true; public override bool isOnlineService => false; public override bool hasCoRoutines => true; public override bool isIL2CPPSupported => true; public override bool hasVoicesInEditor => false; #endregion #region Constructor /// Constructor for VoiceProviderWSA. private VoiceProviderWSA() { //Util.Config.DEBUG = true; //only for tests Load(); } #endregion #region Implemented methods public override void Load(bool forceReload = false) { if (cachedVoices?.Count == 0 || forceReload) { if (!isLoading) { isLoading = true; if (ttsHandler == null) { if (Util.Constants.DEV_DEBUG) Debug.Log("Initializing TTS..."); ttsHandler = new RTVoiceUWPBridge(); ttsHandler.DEBUG = Util.Config.DEBUG; } Speaker.Instance.StartCoroutine(getVoices()); } } else { onVoicesReady(); } } public override IEnumerator SpeakNative(Model.Wrapper wrapper) { yield return speak(wrapper, true); } public override IEnumerator Speak(Model.Wrapper wrapper) { yield return speak(wrapper, false); } public override IEnumerator Generate(Model.Wrapper wrapper) { if (wrapper == null) { Debug.LogWarning("'wrapper' is null!"); } else { if (string.IsNullOrEmpty(wrapper.Text)) { Debug.LogWarning("'wrapper.Text' is null or empty: " + wrapper); yield return null; } else { yield return null; //return to the main process (uid) ttsHandler.isBusy = true; string voiceName = getVoiceName(wrapper); string outputFile = getOutputFile(wrapper.Uid, true); string path = Application.persistentDataPath.Replace('/', '\\'); //ttsHandler.SynthesizeToFile(prepareText(wrapper), Application.persistentDataPath.Replace('/', '\\'), Util.Constants.AUDIOFILE_PREFIX + wrapper.Uid + AudioFileExtension, voiceName); UnityEngine.WSA.Application.InvokeOnUIThread(() => { ttsHandler.SynthesizeToFile(prepareText(wrapper), path, Util.Constants.AUDIOFILE_PREFIX + wrapper.Uid + AudioFileExtension, voiceName); }, false); //UnityEngine.WSA.Application.InvokeOnAppThread(() => { ttsHandler.SynthesizeToFile(prepareText(wrapper), path, Util.Constants.AUDIOFILE_PREFIX + wrapper.Uid + AudioFileExtension, voiceName); }, false); silence = false; onSpeakAudioGenerationStart(wrapper); do { yield return null; } while (!silence && ttsHandler.isBusy); //Debug.Log("FILE: " + "file://" + outputFile + "/" + wrapper.Uid + extension); processAudioFile(wrapper, outputFile); } } } public override void Silence() { UnityEngine.WSA.Application.InvokeOnUIThread(() => { ttsHandler.StopNative(); }, false); //UnityEngine.WSA.Application.InvokeOnAppThread(() => { ttsHandler.StopNative(); }, false); base.Silence(); } #endregion #region Private methods private IEnumerator getVoices() { try { System.Collections.Generic.List voices = new System.Collections.Generic.List(70); string[] myStringVoices = ttsHandler.Voices; string name; foreach (string voice in myStringVoices) { string[] currentVoiceData = voice.Split(';'); name = currentVoiceData[0]; Model.Voice newVoice = new Model.Voice(name, "UWP voice: " + voice, Util.Helper.WSAVoiceNameToGender(name), "unknown", currentVoiceData[1]); voices.Add(newVoice); } cachedVoices = voices.OrderBy(s => s.Name).ToList(); if (Util.Constants.DEV_DEBUG) Debug.Log("Voices read: " + cachedVoices.CTDump()); } catch (System.Exception ex) { string errorMessage = "Could not get any voices!" + System.Environment.NewLine + ex; Debug.LogError(errorMessage); onErrorInfo(null, errorMessage); } yield return null; isLoading = false; onVoicesReady(); } private IEnumerator speak(Model.Wrapper wrapper, bool isNative) { if (wrapper == null) { Debug.LogWarning("'wrapper' is null!"); } else { if (string.IsNullOrEmpty(wrapper.Text)) { Debug.LogWarning("'wrapper.Text' is null or empty: " + wrapper); } else { if (wrapper.Source == null) { Debug.LogWarning("'wrapper.Source' is null: " + wrapper); } else { yield return null; //return to the main process (uid) ttsHandler.isBusy = true; string voiceName = getVoiceName(wrapper); string outputFile = getOutputFile(wrapper.Uid, true); string path = Application.persistentDataPath.Replace('/', '\\'); //ttsHandler.SynthesizeToFile(prepareText(wrapper), path, Util.Constants.AUDIOFILE_PREFIX + wrapper.Uid + AudioFileExtension, voiceName); UnityEngine.WSA.Application.InvokeOnUIThread(() => { ttsHandler.SynthesizeToFile(prepareText(wrapper), path, Util.Constants.AUDIOFILE_PREFIX + wrapper.Uid + AudioFileExtension, voiceName); }, false); //UnityEngine.WSA.Application.InvokeOnAppThread(() => { ttsHandler.SynthesizeToFile(prepareText(wrapper), path, Util.Constants.AUDIOFILE_PREFIX + wrapper.Uid + AudioFileExtension, voiceName); }, false); silence = false; if (!isNative) { onSpeakAudioGenerationStart(wrapper); } do { yield return null; } while (!silence && ttsHandler.isBusy); yield return playAudioFile(wrapper, Util.Constants.PREFIX_FILE + outputFile, outputFile, AudioType.WAV, isNative); } } } } private static string prepareText(Model.Wrapper wrapper) { //TEST //wrapper.ForceSSML = false; if (wrapper.ForceSSML && !Speaker.Instance.AutoClearTags) { System.Text.StringBuilder sbXML = new System.Text.StringBuilder(); sbXML.Append(""); sbXML.Append(""); if (Mathf.Abs(wrapper.Rate - 1f) > Common.Util.BaseConstants.FLOAT_TOLERANCE || Mathf.Abs(wrapper.Pitch - 1f) > Common.Util.BaseConstants.FLOAT_TOLERANCE || Mathf.Abs(wrapper.Volume - 1f) > Common.Util.BaseConstants.FLOAT_TOLERANCE) { sbXML.Append(" Common.Util.BaseConstants.FLOAT_TOLERANCE) { float _rate = wrapper.Rate > 1 ? (wrapper.Rate - 1f) * 0.5f : wrapper.Rate - 1f; sbXML.Append(" rate=\""); sbXML.Append(_rate >= 0f ? _rate.ToString("+#0%", Util.Helper.BaseCulture) : _rate.ToString("#0%", Util.Helper.BaseCulture)); sbXML.Append("\""); } if (Mathf.Abs(wrapper.Pitch - 1f) > Common.Util.BaseConstants.FLOAT_TOLERANCE) { float _pitch = wrapper.Pitch - 1f; sbXML.Append(" pitch=\""); sbXML.Append(_pitch >= 0f ? _pitch.ToString("+#0%", Util.Helper.BaseCulture) : _pitch.ToString("#0%", Util.Helper.BaseCulture)); sbXML.Append("\""); } if (Mathf.Abs(wrapper.Volume - 1f) > Common.Util.BaseConstants.FLOAT_TOLERANCE) { sbXML.Append(" volume=\""); sbXML.Append((100 * wrapper.Volume).ToString("#0", Util.Helper.BaseCulture)); sbXML.Append("\""); } sbXML.Append(">"); sbXML.Append(wrapper.Text); sbXML.Append(""); } else { sbXML.Append(wrapper.Text); } sbXML.Append(""); return getValidXML(sbXML.ToString()); } return wrapper.Text; } #endregion #region Editor-only methods #if UNITY_EDITOR public override void GenerateInEditor(Model.Wrapper wrapper) { Debug.LogError("'GenerateInEditor' is not supported for UWP (WSA)!"); } public override void SpeakNativeInEditor(Model.Wrapper wrapper) { Debug.LogError("'SpeakNativeInEditor' is not supported for UWP (WSA)!"); } #endif #endregion } } #endif // © 2016-2020 crosstales LLC (https://www.crosstales.com)