using System; using System.Buffers; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Concentus; using Concentus.Enums; using Concentus.Oggfile; using GameNetcodeStuff; using HarmonyLib; using LethalCompanyInputUtils.Api; using LethalConfig; using LethalConfig.ConfigItems; using LethalNetworkAPI; using LethalNetworkAPI.Utils; using TTSCompany.Components; using TTSCompany.Components.Constants; using TTSCompany.Components.Enums; using TTSCompany.Components.Helpers; using TTSCompany.Components.Managers; using TTSCompany.Components.Managers.Components; using TTSCompany.Components.Networking; using TTSCompany.Components.Networking.Components; using TTSCompany.Components.Networking.Components.Structs; using TTSCompany.Components.Server.Components; using TTSCompany.Debug; using TTSCompany.Debug.Inputs; using TTSCompany.Patches; using TTS_Company.Components.Networking.Components.Structs; using Unity.Netcode; using UnityEngine; using UnityEngine.Audio; using UnityEngine.InputSystem; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("TTS-Company")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("PixelIndieDev")] [assembly: AssemblyProduct("TTS-Company")] [assembly: AssemblyCopyright("Copyright © 2026 PixelIndieDev")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("11e971ee-3d73-490e-bf46-c90ae5be06a2")] [assembly: NeutralResourcesLanguage("en")] [assembly: AssemblyFileVersion("1.2.1.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace TTS_Company.Components.Networking.Components.Structs { internal readonly struct StopSpeakingTTS_NET { [SerializeField] internal readonly NetworkObjectReference _networkObjectRefOfSpeaker; [SerializeField] internal readonly ulong _callingAssemblyHash; internal StopSpeakingTTS_NET(NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _networkObjectRefOfSpeaker = networkObjectRefOfSpeaker; _callingAssemblyHash = callingAssemblyHash; } } } namespace TTSCompany { internal static class ModInfo { internal const string modGUID = "PixelIndieDev_TTSCompany"; internal const string modName = "TTS Company"; internal const string modVersion = "1.2.1.0"; } [BepInPlugin("PixelIndieDev_TTSCompany", "TTS Company", "1.2.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] internal sealed class TTSCompanyPlugin : BaseUnityPlugin { private readonly Harmony harmony = new Harmony("PixelIndieDev_TTSCompany"); internal static TTSGenerator _tts; internal static TTSCompanyDebugInputs inputActionsInstance; internal static ConfigEntry configEntryPriority; internal static ConfigEntry configEntryTimeoutBuffer; internal static ConfigEntry configEntryClearCacheOnExit; internal static ConfigEntry configEntryClearCacheManually; private bool _isDeletingCache; private bool _isShuttingDown; private bool _shutdownComplete; internal static TTSCompanyPlugin instance { get; private set; } internal static bool IsInputUtilsPresent { get; private set; } internal static TTSPlaybackManager _ttsPlaybackManagerObject { get; private set; } private void Awake() { //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Expected O, but got Unknown //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Expected O, but got Unknown //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Expected O, but got Unknown ((Object)((Component)this).gameObject).hideFlags = (HideFlags)61; if ((Object)(object)instance == (Object)null) { instance = this; } Application.wantsToQuit += OnWantsToQuit; IsInputUtilsPresent = Chainloader.PluginInfos.ContainsKey("com.rune580.LethalCompanyInputUtils"); if (IsInputUtilsPresent) { inputActionsInstance = new TTSCompanyDebugInputs(); inputActionsInstance.PixelIndieDev_TestTTS_01.performed += DebugManualTrigger.TriggerTestTTS01; inputActionsInstance.PixelIndieDev_TestTTS_02.performed += DebugManualTrigger.TriggerTestTTS02; inputActionsInstance.PixelIndieDev_TestTTS_03.performed += DebugManualTrigger.TriggerTestTTS03; } _tts = new TTSGenerator(); configEntryPriority = ((BaseUnityPlugin)this).Config.Bind("TTS Generation", "TTS generation priority", TTSGenPriority.Normal, "Adjusts the CPU priority for generating TTS. Higher priority may increase the TTS generation speed at the cost of performance."); configEntryPriority.SettingChanged += delegate { _tts.SetMaxConcurrentRequests(configEntryPriority.Value); }; LethalConfigManager.AddConfigItem((BaseConfigItem)(object)new EnumDropDownConfigItem(configEntryPriority, false)); configEntryTimeoutBuffer = ((BaseUnityPlugin)this).Config.Bind("TTS Generation", "TTS timeout scaling", TimeoutBufferScaling.Normal, "Controls how long the mod waits for a voice line to generate. Increase this if your TTS audio keeps getting cut off, or decrease it if you want the mod to give up faster when experiencing delays. \n\nControlled by the host."); configEntryTimeoutBuffer.SettingChanged += delegate { TTSConstants.UpdateTimeoutBuffers(); }; LethalConfigManager.AddConfigItem((BaseConfigItem)(object)new EnumDropDownConfigItem(configEntryTimeoutBuffer, false)); configEntryClearCacheOnExit = ((BaseUnityPlugin)this).Config.Bind("Cache", "Clear TTS cache on exit", false, "When enabled, automatically deletes saved TTS cache when you close Lethal Company. Disabling this saves disk space but requires files to be regenerate next session."); LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(configEntryClearCacheOnExit, false)); configEntryClearCacheManually = ((BaseUnityPlugin)this).Config.Bind("Cache", "Clear TTS cache now", false, "Check this checkbox to delete all saved local TTS cache. \n\nBest done in the main menu."); configEntryClearCacheManually.SettingChanged += delegate { if (!_isDeletingCache) { ClearTTSCache(); } configEntryClearCacheManually.Value = false; }; LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(configEntryClearCacheManually, false)); _tts.SetMaxConcurrentRequests(configEntryPriority.Value); TTSConstants.UpdateTimeoutBuffers(); GameObject val = new GameObject("TTSPlaybackManager"); _ttsPlaybackManagerObject = val.AddComponent(); ((Object)val).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)val); harmony.PatchAll(typeof(StartOfRoundPatch)); harmony.PatchAll(typeof(GameNetworkManagerPatch)); TTSCompanyNetworking.Initialize(); OnAwake(); LogConstants.PLUGIN_LOADED.Log("TTSCompanyPlugin", "TTS Company", "1.2.1.0"); } private async void OnAwake() { if (!(await _tts.InitializeAsync())) { LogConstants.PLUGIN_TTS_COULD_NOT_BE_INITIALIZED.Log("TTSCompanyPlugin", "new TTSGenerator()"); } await TTSCompanyAPI.PreloadTTSVoiceModelInMemory("en_US-hfc_female-medium"); await TTSCompanyAPI.PreloadTTSVoiceModelInMemory("en_US-hfc_male-medium"); await TTSCompanyAPI.PreloadTTSVoiceModelInMemory("en_US-ryan-medium"); await TTSCompanyAPI.PreloadTTSVoiceModelInMemory("en_US-sam-medium"); } private void ClearTTSCache() { _isDeletingCache = true; if (Directory.Exists(TTSConstants.TTS_VOICE_CACHE_SOUNDCLIPS_PATH)) { try { Directory.Delete(TTSConstants.TTS_VOICE_CACHE_SOUNDCLIPS_PATH, recursive: true); } catch (IOException) { LogConstants.CODE_GENERIC_CATCH.Log("TTSCompanyPlugin", "ClearTTSCache"); } } _isDeletingCache = false; } private bool OnWantsToQuit() { if (_shutdownComplete) { return true; } if (_isShuttingDown) { return false; } _isShuttingDown = true; ExecuteAsyncShutdown(); return false; } private async void ExecuteAsyncShutdown() { LogConstants.PLUGIN_ON_QUIT.Log("TTSCompanyPlugin", "TTS Company", "1.2.1.0"); if (configEntryClearCacheOnExit.Value) { ClearTTSCache(); } try { if (_tts != null) { LogConstants.CODE_TRIGGERED.Log("TTSCompanyPlugin", "ExecuteAsyncShutdown"); await _tts.ShutdownAsync(); } } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyPlugin", "ExecuteAsyncShutdown", ex.Message); } finally { _shutdownComplete = true; if (_tts != null) { _tts.Dispose(); } Application.Quit(); } } private void OnDisable() { if (!_isShuttingDown && _tts != null) { _tts.Dispose(); } } private void OnDestroy() { if (!_isShuttingDown && _tts != null) { _tts.Dispose(); } } } public static class TTSCompanyAPI { [MethodImpl(MethodImplOptions.NoInlining)] public static async Task<(bool Success, string Error)> PreloadTTSVoiceModelInMemory(string voiceModelName) { voiceModelName = VoiceHelper.CleanupVoiceModelname(voiceModelName); LogConstants.API_TRIGGER_PRELOAD_VOICE_MODEL.Log("TTSCompanyAPI", voiceModelName); ulong callingAssemblyHash = HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly()); return await TTSCompanyPlugin._tts.PreloadVoiceAsync(voiceModelName, callingAssemblyHash); } [MethodImpl(MethodImplOptions.NoInlining)] public static async Task<(bool Success, string Error)> UnloadTTSVoiceModelInMemory(string voiceModelName) { voiceModelName = VoiceHelper.CleanupVoiceModelname(voiceModelName); LogConstants.API_TRIGGER_UNLOAD_VOICE_MODEL.Log("TTSCompanyAPI", voiceModelName); ulong callingAssemblyHash = HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly()); return await TTSCompanyPlugin._tts.UnloadVoiceAsync(voiceModelName, callingAssemblyHash); } [MethodImpl(MethodImplOptions.NoInlining)] public static void AddTTSAudioSourceOnNetworkObject(GameObject objectRefOfSpeaker, bool useGlobalAudioSource = true, TTSAudioSourceSettings audioSourceSettings = null) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject)) { TTSCompanyAPI.AddTTSAudioSourceOnNetworkObject(new NetworkObjectReference(networkObject), useGlobalAudioSource, audioSourceSettings); } } [MethodImpl(MethodImplOptions.NoInlining)] public static void AddTTSAudioSourceOnNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, bool useGlobalAudioSource = true, TTSAudioSourceSettings audioSourceSettings = null) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) audioSourceSettings = audioSourceSettings ?? TTSCompanyUtils.DefaultTTSAudioSourceSettings; ulong callingAssemblyHash = (useGlobalAudioSource ? HashHelper.GlobalCallerHash : HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly())); NetworkObject val = default(NetworkObject); if (LNetworkUtils.IsConnected) { TTSCompanyNetworking.Request_Server_SpawnTTSSource(new SpawnTTSAudioSource_NET(networkObjectRefOfSpeaker, callingAssemblyHash, audioSourceSettings)); } else if (!((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null)) { LogConstants.API_NETWORK_OBJECT_NOT_FOUND.Log("TTSCompanyAPI", networkObjectRefOfSpeaker); } else { TTSAudioSourceManager.AddPermanentTTSAudioSource(((Component)val).gameObject, callingAssemblyHash, audioSourceSettings); } } [MethodImpl(MethodImplOptions.NoInlining)] public static void RemoveTTSAudioSourceOnNetworkObject(GameObject objectRefOfSpeaker) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject)) { TTSCompanyAPI.RemoveTTSAudioSourceOnNetworkObject(new NetworkObjectReference(networkObject)); } } [MethodImpl(MethodImplOptions.NoInlining)] public static void RemoveTTSAudioSourceOnNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) ulong callingAssemblyHash = HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly()); NetworkObject val = default(NetworkObject); if (LNetworkUtils.IsConnected) { TTSCompanyNetworking.Request_Server_DespawnTTSSource(new DespawnTTSAudioSource_NET(networkObjectRefOfSpeaker, callingAssemblyHash)); } else if (!((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null)) { LogConstants.API_NETWORK_OBJECT_NOT_FOUND.Log("TTSCompanyAPI", networkObjectRefOfSpeaker); } else { TTSAudioSourceManager.RemovePermanentTTSAudioSource(((Component)val).gameObject, callingAssemblyHash); } } [MethodImpl(MethodImplOptions.NoInlining)] public static void UpdateTTSAudioSourceSettingsOnNetworkObject(GameObject objectRefOfSpeaker, TTSAudioSourceSettings audioSourceSettings, bool useGlobalAudioSource = true) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject)) { TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(new NetworkObjectReference(networkObject), audioSourceSettings, useGlobalAudioSource); } } [MethodImpl(MethodImplOptions.NoInlining)] public static void UpdateTTSAudioSourceSettingsOnNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, TTSAudioSourceSettings audioSourceSettings, bool useGlobalAudioSource = true) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) audioSourceSettings = audioSourceSettings ?? TTSCompanyUtils.DefaultTTSAudioSourceSettings; ulong callingAssemblyHash = (useGlobalAudioSource ? HashHelper.GlobalCallerHash : HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly())); NetworkObject val = default(NetworkObject); if (LNetworkUtils.IsConnected) { TTSCompanyNetworking.Request_Server_UpdateTTSAudioSourceSettings(new UpdateTTSAudioSourceSettings_NET(networkObjectRefOfSpeaker, callingAssemblyHash, audioSourceSettings)); } else if (!((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null)) { LogConstants.API_NETWORK_OBJECT_NOT_FOUND.Log("TTSCompanyAPI", networkObjectRefOfSpeaker); } else { TTSAudioSourceManager.UpdateTTSAudioSourceSettings(((Component)val).gameObject, callingAssemblyHash, audioSourceSettings); } } [MethodImpl(MethodImplOptions.NoInlining)] public static void SpeakTTSAtNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, string[] textsToSpeak, bool useGlobalAudioSource = true, PiperVoiceSettings voiceSettings = null, float noiseRangeMultiplier = 0f) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) LogConstants.CODE_TRIGGERED.Log("TTSCompanyAPI", "SpeakTTSAtNetworkObject"); if (textsToSpeak != null && textsToSpeak.Length != 0) { voiceSettings = voiceSettings ?? TTSCompanyUtils.DefaultVoiceSettings; TTSCompanyUtils.GetAudioHashes(networkObjectRefOfSpeaker, useGlobalAudioSource, out var callingAHash, out var trackingKeyHash); TTSCompanyNetworking.Request_Server_SpeakTTS(new TTSSpeakTTS_NET(networkObjectRefOfSpeaker, callingAHash, textsToSpeak, voiceSettings, trackingKeyHash, noiseRangeMultiplier)); } } [MethodImpl(MethodImplOptions.NoInlining)] public static void SpeakTTSAtNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, string textToSpeak, bool useGlobalAudioSource = true, PiperVoiceSettings voiceSettings = null, float noiseRangeMultiplier = 0f) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) SpeakTTSAtNetworkObject(networkObjectRefOfSpeaker, TTSCompanyUtils.SplitTextToSpeak(textToSpeak), useGlobalAudioSource, voiceSettings, noiseRangeMultiplier); } [MethodImpl(MethodImplOptions.NoInlining)] public static void SpeakTTSAtNetworkObject(GameObject objectRefOfSpeaker, string textToSpeak, bool useGlobalAudioSource = true, PiperVoiceSettings voiceSettings = null, float noiseRangeMultiplier = 0f) { SpeakTTSAtNetworkObject(objectRefOfSpeaker, TTSCompanyUtils.SplitTextToSpeak(textToSpeak), useGlobalAudioSource, voiceSettings, noiseRangeMultiplier); } [MethodImpl(MethodImplOptions.NoInlining)] public static void SpeakTTSAtNetworkObject(GameObject objectRefOfSpeaker, string[] textsToSpeak, bool useGlobalAudioSource = true, PiperVoiceSettings voiceSettings = null, float noiseRangeMultiplier = 0f) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject)) { TTSCompanyAPI.SpeakTTSAtNetworkObject(new NetworkObjectReference(networkObject), textsToSpeak, useGlobalAudioSource, voiceSettings, noiseRangeMultiplier); } } public static void StopSpeakingTTSAtNetworkObject(NetworkObjectReference networkObjectRefOfSpeaker, bool useGlobalAudioSource = true) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) TTSCompanyUtils.GetAudioHashes(networkObjectRefOfSpeaker, useGlobalAudioSource, out var callingAHash, out var _); TTSCompanyNetworking.Request_Server_StopSpeakingTTS(new StopSpeakingTTS_NET(networkObjectRefOfSpeaker, callingAHash)); } public static void StopSpeakingTTSAtNetworkObject(GameObject objectRefOfSpeaker, bool useGlobalAudioSource = true) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (TTSCompanyUtils.TryGetCachedNetworkObject(objectRefOfSpeaker, out var networkObject)) { TTSCompanyAPI.StopSpeakingTTSAtNetworkObject(new NetworkObjectReference(networkObject), useGlobalAudioSource); } } public static void PreGenerateTTS(string textToSpeak, PiperVoiceSettings voiceSettings = null) { if (!string.IsNullOrWhiteSpace(textToSpeak)) { PreGenerateTTS(TTSCompanyUtils.SplitTextToSpeak(textToSpeak), voiceSettings); } } public static void PreGenerateTTS(string[] textsToSpeak, PiperVoiceSettings voiceSettings = null) { LogConstants.CODE_TRIGGERED.Log("TTSCompanyAPI", "PreGenerateTTS"); if (textsToSpeak != null && textsToSpeak.Length != 0) { voiceSettings = voiceSettings ?? TTSCompanyUtils.DefaultVoiceSettings; ulong trackingKeyHash = HashHelper.GetTrackingKeyHash(string.Join("|", textsToSpeak), voiceSettings); CancellationTokenSource cts = new CancellationTokenSource(TTSTimeoutHelper.GetGenerationTimeout(textsToSpeak, voiceSettings)); ((MonoBehaviour)TTSCompanyPlugin.instance).StartCoroutine(TTSCompanyBackend.PreGenerateTTS(trackingKeyHash, textsToSpeak, voiceSettings, cts)); } } } public static class TTSCompanyUtils { private static readonly Regex SentenceRegex = new Regex("[^.!?]+[.!?]?", RegexOptions.Compiled); internal static readonly PiperVoiceSettings DefaultVoiceSettings = new PiperVoiceSettings(); internal static readonly TTSAudioSourceSettings DefaultTTSAudioSourceSettings = new TTSAudioSourceSettings(); internal static readonly ConditionalWeakTable NetworkObjectCache = new ConditionalWeakTable(); public static bool HasTTSVoiceModelBeenLoadedIntoMemory(string voiceModelName) { return TTSCompanyPlugin._tts.isVoiceModelLoaded(voiceModelName); } public static string GetRandomFoundTTSVoiceName() { return TTSCompanyPlugin._tts._server._memoryManager.GetRandomFoundTTSVoiceName(); } public static string[] GetAllFoundTTSVoiceNames() { return TTSCompanyPlugin._tts._server._memoryManager.GetAllFoundTTSVoiceNames(); } public static string GetRandomLoadedTTSVoiceName() { return TTSCompanyPlugin._tts._server._memoryManager.GetRandomLoadedTTSVoiceName(); } public static string[] GetAllLoadedTTSVoiceNames() { return TTSCompanyPlugin._tts._server._memoryManager.GetAllLoadedTTSVoiceNames(); } [MethodImpl(MethodImplOptions.NoInlining)] public static bool IsNetworkObjectCurrentlySpeaking(GameObject gameObject, bool useGlobalAudioSource = true) { if (TryGetCachedNetworkObject(gameObject, out var networkObject)) { return IsNetworkObjectCurrentlySpeaking(networkObject, useGlobalAudioSource); } return false; } [MethodImpl(MethodImplOptions.NoInlining)] public static bool IsNetworkObjectCurrentlySpeaking(NetworkObject networkObject, bool useGlobalAudioSource = true) { if ((Object)(object)networkObject == (Object)null) { return false; } ulong callerHash = (useGlobalAudioSource ? HashHelper.GlobalCallerHash : HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly())); return IsAssemblyTrackedFor(TTSCompanyBackend.SpeakingNetworkObjectIds, networkObject, callerHash); } public static bool IsNetworkObjectAwaitingTTSGeneration(GameObject gameObject, bool useGlobalAudioSource = true) { if (TryGetCachedNetworkObject(gameObject, out var networkObject)) { return IsNetworkObjectAwaitingTTSGeneration(networkObject, useGlobalAudioSource); } return false; } public static bool IsNetworkObjectAwaitingTTSGeneration(NetworkObject networkObject, bool useGlobalAudioSource = true) { if ((Object)(object)networkObject == (Object)null) { return false; } ulong callerHash = (useGlobalAudioSource ? HashHelper.GlobalCallerHash : HashHelper.GetCallingAssemblyHash(Assembly.GetCallingAssembly())); return IsAssemblyTrackedFor(TTSCompanyBackend.GeneratingNetworkObjectIds, networkObject, callerHash); } public static TTSNetworkObjectState GetTTSNetworkObjectState(GameObject gameObject, bool useGlobalAudioSource = true) { if (TryGetCachedNetworkObject(gameObject, out var networkObject)) { return GetTTSNetworkObjectState(networkObject); } return TTSNetworkObjectState.Invalid; } public static TTSNetworkObjectState GetTTSNetworkObjectState(NetworkObject networkObject, bool useGlobalAudioSource = true) { if ((Object)(object)networkObject == (Object)null) { return TTSNetworkObjectState.Invalid; } if (IsNetworkObjectCurrentlySpeaking(networkObject, useGlobalAudioSource)) { return TTSNetworkObjectState.ActivelySpeaking; } if (IsNetworkObjectAwaitingTTSGeneration(networkObject, useGlobalAudioSource)) { return TTSNetworkObjectState.GeneratingTTS; } return TTSNetworkObjectState.Idle; } internal static string[] SplitTextToSpeak(string textToSpeak) { if (string.IsNullOrEmpty(textToSpeak)) { return Array.Empty(); } textToSpeak.Replace("...", "_#_ELLIPSIS_#_"); MatchCollection matchCollection = SentenceRegex.Matches(textToSpeak); List list = new List(matchCollection.Count); for (int i = 0; i < matchCollection.Count; i++) { Match match = matchCollection[i]; ReadOnlySpan readOnlySpan = textToSpeak.AsSpan(match.Index, match.Length).Trim(); if (readOnlySpan.Length > 0) { list.Add(readOnlySpan.ToString().Replace("_#_ELLIPSIS_#_", "...")); } } if (list.Count == 0) { list.Add(textToSpeak); } return list.ToArray(); } internal static void GetAudioHashes(NetworkObjectReference networkObjectRefOfSpeaker, bool useGlobalAudioSource, out ulong callingAHash, out ulong trackingKeyHash) { if (useGlobalAudioSource) { callingAHash = HashHelper.GlobalCallerHash; trackingKeyHash = HashHelper.GetTrackingKeyHash(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId); } else { Assembly callingAssembly = Assembly.GetCallingAssembly(); callingAHash = HashHelper.GetCallingAssemblyHash(callingAssembly); trackingKeyHash = HashHelper.GetTrackingKeyHash(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, callingAssembly); } } internal static float DetermineEndPause(string sentenceText, float sentenceSilence, float punctuationSilence) { if (string.IsNullOrEmpty(sentenceText)) { return 0f; } int num = sentenceText.TrimEnd(Array.Empty()).Length - 1; while (num >= 0 && char.IsWhiteSpace(sentenceText[num])) { num--; } while (num >= 0) { char c = sentenceText[num]; if (c != '"' && c != '\'' && c != ')' && c != ']') { break; } num--; } if (num < 0) { return 0f; } if (num >= 2 && sentenceText[num] == '.' && sentenceText[num - 1] == '.' && sentenceText[num - 2] == '.') { return punctuationSilence; } switch (sentenceText[num]) { case '!': case '.': case '?': return sentenceSilence; case ',': case ':': case ';': return punctuationSilence; default: return 0f; } } internal static (int TotalWordCount, int SentenceCount) GetTextToSpeakInfo(string[] textsToSpeak) { if (textsToSpeak == null || textsToSpeak.Length == 0) { return (TotalWordCount: 0, SentenceCount: 0); } int num = 0; int num2 = 0; foreach (string text in textsToSpeak) { if (string.IsNullOrWhiteSpace(text)) { continue; } int num3 = 0; bool flag = false; foreach (char c in text) { if (c == ' ' || c == '\r' || c == '\n') { if (flag) { num3++; flag = false; } } else { flag = true; } } if (flag) { num3++; } num += num3; num2++; } LogConstants.UTILS_TIMEOUT_TIME_GENERATION.Log("TTSGenerator", string.Join(", ", textsToSpeak), num, num2); return (TotalWordCount: num, SentenceCount: num2); } internal static bool TryGetCachedNetworkObject(GameObject gameObject, out NetworkObject networkObject) { networkObject = null; if ((Object)(object)gameObject == (Object)null) { return false; } if (!NetworkObjectCache.TryGetValue(gameObject, out networkObject)) { if (!gameObject.TryGetComponent(ref networkObject)) { return false; } NetworkObjectCache.Remove(gameObject); NetworkObjectCache.Add(gameObject, networkObject); } return (Object)(object)networkObject != (Object)null; } private static bool IsAssemblyTrackedFor(ConcurrentDictionary> list, NetworkObject networkObject, ulong callerHash) { if ((Object)(object)networkObject == (Object)null) { return false; } if (!list.TryGetValue(networkObject.NetworkObjectId, out var value)) { return false; } return value.ContainsKey(callerHash); } } } namespace TTSCompany.Patches { [HarmonyPatch(typeof(GameNetworkManager))] internal static class GameNetworkManagerPatch { [HarmonyPostfix] [HarmonyPatch("ResetGameValuesToDefault")] private static void OnReturnedToMainMenu() { TTSCompanyBackend.OnReturnedToMainMenu(); } } [HarmonyPatch(typeof(StartOfRound))] internal static class StartOfRoundPatch { [HarmonyPostfix] [HarmonyPatch("OnPlayerConnectedClientRpc")] private static void SyncTTSAudioSources(ulong clientId) { TTSCompanyNetworking.SyncActiveAudioSourcesTo(clientId); } [HarmonyPostfix] [HarmonyPatch("OnClientDisconnect")] private static void OnPlayerDisconnected(ulong clientId) { TTSCompanyNetworking.HandlePlayerDisconnected(clientId); } } } namespace TTSCompany.Debug { internal static class DebugManualTrigger { private static readonly string[] randomVoiceLines = new string[10] { "This is a testing text-to-speech voice line.", "System online. All networks are fully functional.", "Warning, localized anomaly detected near your position.", "Please do not touch the operational machinery.", "The quick brown fox jumps over the lazy dog.", "I guess...this voice lines works.", "Testing complete.", "This is a voice line test.", "Warning! Retreat immediately!", "FUCK!" }; private static readonly string[] enemyNames = new string[8] { "Barber", "Bracken", "Bunker Spider", "Coil-Head", "Hoarding Bug", "Hygrodere", "Jester", "Nutcracker" }; private static readonly string[] multipleLines = new string[3] { "This is a testing text-to-speech voice line.", "This is a voice line test.", "Testing complete." }; private static PlayerControllerB speakingPlayer = null; private static void GetSpeakingPlayer() { if ((Object)(object)speakingPlayer != (Object)null) { return; } if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.localPlayerController == (Object)null) { LogConstants.CODE_INPUT_VARIABLES_INVALID.Log("DebugManualTrigger", "GetSpeakingPlayer", 1); } else { speakingPlayer = StartOfRound.Instance.localPlayerController; if (!((Object)(object)speakingPlayer == (Object)null)) { TTSCompanyAPI.AddTTSAudioSourceOnNetworkObject(((Component)speakingPlayer).gameObject); } } } internal static async void TriggerTestTTS01(CallbackContext obj) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (((CallbackContext)(ref obj)).performed) { LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "TriggerTestTTS01"); GetSpeakingPlayer(); if ((Object)(object)speakingPlayer == (Object)null) { LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "speakingPlayer == null"); return; } PiperVoiceSettings piperVoiceSettings = new PiperVoiceSettings(); piperVoiceSettings.ModelName = TTSCompanyUtils.GetRandomFoundTTSVoiceName(); int num = Random.Range(0, randomVoiceLines.Length); TTSAudioSourceSettings tTSAudioSourceSettings = new TTSAudioSourceSettings(); tTSAudioSourceSettings.Volume = 1f; TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(((Component)speakingPlayer).gameObject, tTSAudioSourceSettings); TTSCompanyAPI.SpeakTTSAtNetworkObject(((Component)speakingPlayer).gameObject, randomVoiceLines[num], useGlobalAudioSource: true, piperVoiceSettings); } } internal static async void TriggerTestTTS02(CallbackContext obj) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (((CallbackContext)(ref obj)).performed) { LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "TriggerTestTTS02"); GetSpeakingPlayer(); if ((Object)(object)speakingPlayer == (Object)null) { LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "speakingPlayer == null"); return; } string[] array = new string[3] { "Warning, ", "ENTITYNAME", " detected near your position." }; string[] array2 = new string[3] { "WATCH OUT, ", "ENTITYNAME", " BEHIND YOU!" }; string[][] obj2 = new string[2][] { array, array2 }; PiperVoiceSettings voiceSettings = new PiperVoiceSettings { ModelName = TTSCompanyUtils.GetRandomFoundTTSVoiceName() }; int num = Random.Range(0, array.Length); string[] array3 = obj2[num]; num = Random.Range(0, enemyNames.Length); array3[1] = enemyNames[num]; TTSAudioSourceSettings tTSAudioSourceSettings = new TTSAudioSourceSettings(); tTSAudioSourceSettings.Volume = 0.5f; TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(((Component)speakingPlayer).gameObject, tTSAudioSourceSettings); TTSCompanyAPI.SpeakTTSAtNetworkObject(((Component)speakingPlayer).gameObject, array3, useGlobalAudioSource: true, voiceSettings); } } internal static async void TriggerTestTTS03(CallbackContext obj) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (((CallbackContext)(ref obj)).performed) { LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "TriggerTestTTS03"); GetSpeakingPlayer(); if ((Object)(object)speakingPlayer == (Object)null) { LogConstants.CODE_TRIGGERED.Log("DebugManualTrigger", "speakingPlayer == null"); } else { TTSAudioSourceSettings tTSAudioSourceSettings = new TTSAudioSourceSettings(); tTSAudioSourceSettings.Volume = 1f; TTSCompanyAPI.UpdateTTSAudioSourceSettingsOnNetworkObject(((Component)speakingPlayer).gameObject, tTSAudioSourceSettings); TTSCompanyAPI.SpeakTTSAtNetworkObject(((Component)speakingPlayer).gameObject, multipleLines, useGlobalAudioSource: true, null, 1.5f); } } } } } namespace TTSCompany.Debug.Inputs { public class TTSCompanyDebugInputs : LcInputActions { [InputAction(/*Could not decode attribute arguments.*/)] public InputAction PixelIndieDev_TestTTS_01 { get; set; } [InputAction(/*Could not decode attribute arguments.*/)] public InputAction PixelIndieDev_TestTTS_02 { get; set; } [InputAction(/*Could not decode attribute arguments.*/)] public InputAction PixelIndieDev_TestTTS_03 { get; set; } } } namespace TTSCompany.Components { internal sealed class PiperTTSServer { private const string ReadyPrefix = "READY ON PORT "; private Process _process; private int _port; private readonly StringBuilder _stderrLog = new StringBuilder(); private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(1, 1); private TcpClient _connectionClient; private NetworkStream _connectionStream; internal readonly VoiceModelMemoryManager _memoryManager; internal bool IsRunning { get { if (_process != null) { return !_process.HasExited; } return false; } } internal int Port => _port; public PiperTTSServer() { _memoryManager = new VoiceModelMemoryManager(this); } internal async Task StartAsync(int startupTimeoutMs, CancellationToken cancellationToken) { if (IsRunning) { return true; } if (!File.Exists(TTSConstants.PIPER_EXECUTABLE_LOCATION)) { LogConstants.PIPER_TTS_SERVER_EXE_NOT_FOUND.Log("PiperTTSServer", TTSConstants.PIPER_EXECUTABLE_LOCATION); return false; } if (!Directory.Exists(TTSConstants.TTS_DEFAULT_VOICE_MODELS_FOLDER_LOCATION)) { LogConstants.PIPER_TTS_SERVER_VOICE_FOLDER_NOT_FOUND.Log("PiperTTSServer", TTSConstants.TTS_DEFAULT_VOICE_MODELS_FOLDER_LOCATION); return false; } _memoryManager.InitializeModelRegistry(); ProcessStartInfo startInfo = new ProcessStartInfo { FileName = TTSConstants.PIPER_EXECUTABLE_LOCATION, UseShellExecute = false, RedirectStandardInput = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = false, WorkingDirectory = Path.GetDirectoryName(TTSConstants.PIPER_EXECUTABLE_LOCATION) }; Process process = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; TaskCompletionSource exitTcs = new TaskCompletionSource(); process.Exited += delegate { exitTcs.TrySetResult(result: true); }; try { process.Start(); } catch (Exception ex) { LogConstants.PIPER_TTS_SERVER_VOICE_FOLDER_NOT_FOUND.Log("PiperTTSServer", ex.Message); return false; } DrainStreamAsync(process.StandardError, delegate(string onLine) { lock (_stderrLog) { _stderrLog.AppendLine(onLine); if (_stderrLog.Length > 8000) { _stderrLog.Remove(0, _stderrLog.Length - 8000); } } }); Task readyLineTask = process.StandardOutput.ReadLineAsync(); Task task = Task.Delay(startupTimeoutMs, cancellationToken); Task task2 = await Task.WhenAny(readyLineTask, exitTcs.Task, task).ConfigureAwait(continueOnCapturedContext: false); if (task2 == exitTcs.Task) { string text; lock (_stderrLog) { text = _stderrLog.ToString(); } LogConstants.PIPER_TTS_SERVER_STARTUP_ISSUE.Log("PiperTTSServer", SafeExitCode(process), text); return false; } if (task2 != readyLineTask) { LogConstants.PIPER_TTS_SERVER_STARTUP_ISSUE.Log("PiperTTSServer", "Timed out", "Timed out waiting for server to report its port"); TryKill(process); return false; } string text2 = await readyLineTask.ConfigureAwait(continueOnCapturedContext: false); if (text2 == null || !text2.StartsWith("READY ON PORT ", StringComparison.Ordinal) || !int.TryParse(text2.Substring("READY ON PORT ".Length).Trim(), out _port)) { string text3; lock (_stderrLog) { text3 = _stderrLog.ToString(); } LogConstants.PIPER_TTS_SERVER_STARTUP_ISSUE.Log("PiperTTSServer", "Unexpected startup output at: " + text2, text3); TryKill(process); return false; } DrainStreamAsync(process.StandardOutput, delegate(string outLine) { LogConstants.PIPER_TTS_SERVER_OUTPUT_DRAIN.Log("PiperTTSServer", outLine); }); _process = process; LogConstants.PIPER_TTS_SERVER_SUCCESS_STARTUP.Log("PiperTTSServer", _port, process.Id); return true; } internal async Task ShutdownAsync(int timeoutMs = 3000) { Process process = _process; if (process == null) { DisposeConnectionClient(); return; } if (!process.HasExited) { try { await SendSimpleCommandAsync("{\"command\":\"shutdown\"}\n", CancellationToken.None, 1000).ConfigureAwait(continueOnCapturedContext: false); } catch { LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SendSimpleCommandAsync"); } } try { if (!process.HasExited) { TaskCompletionSource exitTcs = new TaskCompletionSource(); process.EnableRaisingEvents = true; process.Exited += delegate { exitTcs.TrySetResult(result: true); }; if (process.HasExited) { exitTcs.TrySetResult(result: true); } if (await Task.WhenAny(new Task[2] { exitTcs.Task, Task.Delay(timeoutMs) }).ConfigureAwait(continueOnCapturedContext: false) != exitTcs.Task) { TryKill(process); } } } catch { TryKill(process); } finally { _process = null; DisposeConnectionClient(); LogConstants.PIPER_TTS_SERVER_STOPPED.Log("PiperTTSServer"); } } internal bool HasVoiceModelBeenLoaded(string modelName) { return _memoryManager.HasVoiceModelBeenLoaded(modelName); } internal bool IsVoiceModelValid(string modelName) { return _memoryManager.IsVoiceModelValid(modelName); } internal async Task<(bool Success, string Error)> LoadModelAsync(string modelName, ulong callingAssemblyHash, CancellationToken cancellationToken) { return await _memoryManager.LoadModelAsync(modelName, callingAssemblyHash, cancellationToken); } internal async Task<(bool Success, string Error)> UnloadModelAsync(string modelName, ulong callingAssemblyHash, CancellationToken cancellationToken) { return await _memoryManager.UnloadModelAsync(modelName, callingAssemblyHash, cancellationToken); } internal async Task> SendSimpleCommandAsync(string requestJsonLine, CancellationToken cancellationToken, int timeoutMs = 30000) { using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { cancellationToken }); cts.CancelAfter(timeoutMs); await _connectionLock.WaitAsync(cts.Token).ConfigureAwait(continueOnCapturedContext: false); try { using (cts.Token.Register(delegate { try { _connectionClient?.Close(); } catch { LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SendSimpleCommandAsync"); } })) { bool reusedExistingConnection = _connectionClient != null && _connectionClient.Connected; if (!reusedExistingConnection) { await OpenConnectionAsync(cts.Token).ConfigureAwait(continueOnCapturedContext: false); } try { return await SendOnConnectionAsync(requestJsonLine, cts.Token).ConfigureAwait(continueOnCapturedContext: false); } catch (Exception) when (reusedExistingConnection && !cts.IsCancellationRequested) { DisposeConnectionClient(); await OpenConnectionAsync(cts.Token).ConfigureAwait(continueOnCapturedContext: false); return await SendOnConnectionAsync(requestJsonLine, cts.Token).ConfigureAwait(continueOnCapturedContext: false); } } } finally { _connectionLock.Release(); } } private async Task> SendOnConnectionAsync(string requestJsonLine, CancellationToken cancellationToken) { NetworkStream stream = _connectionStream; byte[] bytes = Encoding.UTF8.GetBytes(requestJsonLine); await stream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); string item = (await ReadLineWithLeftoverAsync(stream, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).Item1; if (string.IsNullOrEmpty(item)) { LogConstants.CODE_GENERIC_EXCEPTION.Log("PiperTTSServer", "SendOnConnectionAsync", "PiperTTS connection closed by server before a response was received"); } return JSONHelper.ParseFlatObject(item); } private async Task OpenConnectionAsync(CancellationToken cancellationToken) { TcpClient client = new TcpClient(); await ConnectAsync(client, _port, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); client.NoDelay = true; _connectionClient = client; _connectionStream = client.GetStream(); } private void DisposeConnectionClient() { TcpClient connectionClient = _connectionClient; _connectionClient = null; _connectionStream = null; if (connectionClient != null) { try { connectionClient.Close(); } catch { LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "DisposeConnectionClient"); } } } internal (bool Success, string Error) ToResult(Dictionary response) { int num; object obj; if (response.TryGetValue("status", out var value)) { num = ((value as string == "ok") ? 1 : 0); if (num != 0) { obj = null; goto IL_0044; } } else { num = 0; } obj = (response.TryGetValue("message", out var value2) ? (value2 as string) : "unknown error"); goto IL_0044; IL_0044: string item = (string)obj; return (Success: (byte)num != 0, Error: item); } internal async Task PingAsync(CancellationToken cancellationToken) { try { bool b = default(bool); int num; if ((await SendSimpleCommandAsync("{\"command\":\"ping\"}\n", cancellationToken).ConfigureAwait(continueOnCapturedContext: false)).TryGetValue("alive", out var value)) { if (value is bool) { b = (bool)value; num = 1; } else { num = 0; } } else { num = 0; } return (byte)((uint)num & (b ? 1u : 0u)) != 0; } catch { return false; } } internal async Task SynthesizeAsync(string text, string hash, PiperVoiceSettings options, CancellationToken cancellationToken) { if (!_memoryManager.HasVoiceModelBeenLoaded(options.ModelName)) { LogConstants.PIPER_TTS_VOICE_MODEL_NOT_LOADED.Log("PiperTTSServer", options.ModelName); return TTSRawResult.Cancelled(); } try { if (_memoryManager.WasVoiceModelEvicted(options.ModelName)) { if (!(await _memoryManager.ReloadModelAsync(options.ModelName, cancellationToken)).Item1) { return TTSRawResult.Cancelled(); } } else { _memoryManager.UpdateLastUse(options.ModelName); } } catch (OperationCanceledException) { return TTSRawResult.Cancelled(); } catch (Exception ex2) when (ex2 is IOException || ex2 is SocketException || ex2 is ObjectDisposedException) { return TTSRawResult.Failure("Piper server unreachable while preparing voice model: " + ex2.Message); } cancellationToken.ThrowIfCancellationRequested(); int port = _port; TcpClient client = null; try { client = new TcpClient(); using (cancellationToken.Register(delegate { try { client.Close(); } catch (Exception ex7) { LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SynthesizeAsync", ex7); } SendCancelAsync(hash, port); })) { await ConnectAsync(client, port, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); client.NoDelay = true; NetworkStream stream = client.GetStream(); string s = BuildSynthesizeRequest(options.ModelName, text, hash, options); byte[] bytes = Encoding.UTF8.GetBytes(s); await stream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); (string, byte[]) obj = await ReadLineWithLeftoverAsync(stream, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); string item = obj.Item1; byte[] item2 = obj.Item2; Dictionary dictionary = JSONHelper.ParseFlatObject(item); object value; string text2 = (dictionary.TryGetValue("status", out value) ? (value as string) : null); switch (text2) { case "ok": { object value3; int sampleRate = ((dictionary.TryGetValue("sample_rate", out value3) && value3 != null) ? Convert.ToInt32(value3, CultureInfo.InvariantCulture) : 22050); using MemoryStream ms = new MemoryStream(); if (item2.Length != 0) { ms.Write(item2, 0, item2.Length); } await stream.CopyToAsync(ms, 81920, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); return TTSRawResult.Ok(ms.ToArray(), sampleRate); } case "cancelled": return TTSRawResult.Cancelled(); case "error": { object value2; return TTSRawResult.Failure(dictionary.TryGetValue("message", out value2) ? (value2 as string) : "unknown error"); } default: return TTSRawResult.Failure("unexpected response status: '" + text2 + "'"); } } } catch (OperationCanceledException) { return TTSRawResult.Cancelled(); } catch (ObjectDisposedException) { return cancellationToken.IsCancellationRequested ? TTSRawResult.Cancelled() : TTSRawResult.Failure("connection closed unexpectedly"); } catch (IOException ex5) { return cancellationToken.IsCancellationRequested ? TTSRawResult.Cancelled() : TTSRawResult.Failure(ex5.Message); } catch (SocketException ex6) { return TTSRawResult.Failure("socket error: " + ex6.Message); } finally { client?.Close(); } } private static string BuildSynthesizeRequest(string modelPath, string text, string hash, PiperVoiceSettings options) { StringBuilder stringBuilder = new StringBuilder(text.Length + 128); stringBuilder.Append("{\"command\":\"synthesize\""); stringBuilder.Append(",\"model\":\"").Append(JSONHelper.Escape(modelPath)).Append('"'); stringBuilder.Append(",\"text\":\"").Append(JSONHelper.Escape(text)).Append('"'); stringBuilder.Append(",\"hash\":\"").Append(JSONHelper.Escape(hash)).Append('"'); stringBuilder.Append(",\"length_scale\":").Append((1f / options.SpeechRate).ToString("F4", CultureInfo.InvariantCulture)); stringBuilder.Append(",\"noise_scale\":").Append(options.NoiseScale.ToString("F4", CultureInfo.InvariantCulture)); stringBuilder.Append(",\"noise_w\":").Append(options.NoiseScaleW.ToString("F4", CultureInfo.InvariantCulture)); stringBuilder.Append("}\n"); return stringBuilder.ToString(); } private async Task SendCancelAsync(string hash, int port) { if (string.IsNullOrEmpty(hash)) { return; } try { using TcpClient client = new TcpClient(); Task connectTask = client.ConnectAsync(IPAddress.Loopback, port); if (await Task.WhenAny(new Task[2] { connectTask, Task.Delay(1000) }).ConfigureAwait(continueOnCapturedContext: false) != connectTask) { return; } await connectTask.ConfigureAwait(continueOnCapturedContext: false); client.NoDelay = true; string s = "{\"command\":\"cancel\",\"hash\":\"" + JSONHelper.Escape(hash) + "\"}\n"; byte[] bytes = Encoding.UTF8.GetBytes(s); await client.GetStream().WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(continueOnCapturedContext: false); } catch { LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SendCancelAsync"); } } private static async Task ConnectAsync(TcpClient client, int port, CancellationToken cancellationToken) { using (cancellationToken.Register(delegate { try { client.Close(); } catch { LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "ConnectAsync"); } })) { await client.ConnectAsync(IPAddress.Loopback, port).ConfigureAwait(continueOnCapturedContext: false); } } private static async Task<(string Line, byte[] Leftover)> ReadLineWithLeftoverAsync(NetworkStream stream, CancellationToken cancellationToken) { byte[] buffer = ArrayPool.Shared.Rent(4096); try { using MemoryStream ms = new MemoryStream(); while (true) { int num = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (num == 0) { break; } int num2 = Array.IndexOf(buffer, (byte)10, 0, num); if (num2 >= 0) { ms.Write(buffer, 0, num2); int num3 = num - num2 - 1; byte[] array = Array.Empty(); if (num3 > 0) { array = new byte[num3]; Array.Copy(buffer, num2 + 1, array, 0, num3); } return (Line: Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length), Leftover: array); } ms.Write(buffer, 0, num); } return (Line: Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length), Leftover: Array.Empty()); } finally { ArrayPool.Shared.Return(buffer); } } private static async Task DrainStreamAsync(StreamReader reader, Action onLine) { try { while (true) { string text = await reader.ReadLineAsync().ConfigureAwait(continueOnCapturedContext: false); if (text != null) { onLine?.Invoke(text); continue; } break; } } catch { LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "DrainStreamAsync"); } } private static void TryKill(Process process) { try { if (!process.HasExited) { process.Kill(); } } catch { LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "TryKill"); } } private static int SafeExitCode(Process process) { try { return process.HasExited ? process.ExitCode : (-1); } catch { LogConstants.CODE_GENERIC_CATCH.Log("PiperTTSServer", "SafeExitCode"); return -1; } } } internal static class TTSCompanyBackend { private static readonly ConcurrentDictionary ActiveTTSCoroutines = new ConcurrentDictionary(); private static readonly ConcurrentDictionary ActivePreGenTasks = new ConcurrentDictionary(); internal static readonly ConcurrentDictionary WantedAudioClips = new ConcurrentDictionary(); internal static readonly ConcurrentDictionary> SpeakingNetworkObjectIds = new ConcurrentDictionary>(); internal static readonly ConcurrentDictionary> GeneratingNetworkObjectIds = new ConcurrentDictionary>(); internal static readonly ConcurrentQueue NewSpeakerQueue = new ConcurrentQueue(); internal static void SpeakTTSAtNetworkObject_OnClient(TTSSpeakTTS_PLUS_NET data) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) LogConstants.CODE_TRIGGERED.Log("TTSCompanyBackend", "SpeakTTSAtNetworkObject_OnClient"); if (ActiveTTSCoroutines.TryGetValue(data._trackingKeyHash, out var value)) { if (value.Cts != null) { value.Cts.SafeCancel(); } if (value.Coroutine != null) { ((MonoBehaviour)TTSCompanyPlugin.instance).StopCoroutine(value.Coroutine); } RemoveAssemblyTracking(GeneratingNetworkObjectIds, value.NetworkObjectId, data._callingAssemblyHash); ActiveTTSCoroutines.TryRemove(data._trackingKeyHash, out var _); } CancellationTokenSource cts = new CancellationTokenSource(); ActiveTTSState obj = new ActiveTTSState { Cts = cts }; NetworkObjectReference networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker; obj.NetworkObjectId = ((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId; ActiveTTSState activeTTSState = obj; if (!((Object)(object)TTSCompanyPlugin.instance == (Object)null)) { activeTTSState.Coroutine = ((MonoBehaviour)TTSCompanyPlugin.instance).StartCoroutine(SpeakMultipleTTSInternalRoutine(data._sessionId, data._trackingKeyHash, data._networkObjectRefOfSpeaker, data._callingAssemblyHash, data._textsToSpeak, data._voiceSettings, data._noiseRangeMultiplier, cts)); ActiveTTSCoroutines[data._trackingKeyHash] = activeTTSState; ConcurrentDictionary> generatingNetworkObjectIds = GeneratingNetworkObjectIds; networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker; AddAssemblyTracking(generatingNetworkObjectIds, ((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, data._callingAssemblyHash); } } internal static IEnumerator SpeakMultipleTTSInternalRoutine(ulong sessionId, ulong trackingKeyHash, NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash, string[] textsToSpeak, PiperVoiceSettings voiceSettings, float noiseRangeMultiplier, CancellationTokenSource cts) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) LogConstants.CODE_TRIGGERED.Log("TTSCompanyBackend", "SpeakMultipleTTSInternalRoutine"); try { Task[] ttsTasks = new Task[textsToSpeak.Length]; for (int i = 0; i < textsToSpeak.Length; i++) { ttsTasks[i] = TTSCompanyPlugin._tts.GenerateTTSAsync(textsToSpeak[i], voiceSettings, cts.Token); } TTSCompanyNetworking.CreateClientTask(sessionId, networkObjectRefOfSpeaker, callingAssemblyHash, textsToSpeak, voiceSettings.SentenceSilence, voiceSettings.PunctuationSilence, noiseRangeMultiplier, cts); for (int j = 0; j < ttsTasks.Length; j++) { Task currentTask = ttsTasks[j]; yield return (object)new WaitUntil((Func)(() => currentTask.IsCompleted)); if (currentTask.IsFaulted || currentTask.IsCanceled || !currentTask.Result.Success || (Object)(object)currentTask.Result.AudioClip == (Object)null) { cts.SafeCancel(); break; } TTSResult result = currentTask.Result; TTSCompanyNetworking.UpdateClientTask(sessionId, j, result.AudioClip); TTSCompanyNetworking.Request_Server_UpdateSentenceProgress(new SentenceProgressData_NET(sessionId, j, success: true)); } } finally { cts.Dispose(); if (ActiveTTSCoroutines.TryGetValue(trackingKeyHash, out var value) && value.Cts == cts) { ActiveTTSCoroutines.TryRemove(trackingKeyHash, out var _); RemoveAssemblyTracking(GeneratingNetworkObjectIds, ((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, callingAssemblyHash); } } } internal static void PlaySpeakTTSAtNetworkObject_OnClient(ulong taskid, NetworkObjectReference networkObjectReference, ulong callingAssemblyHash, AudioClip[] audioClip, float[] pauseDurations, bool isFinalBatch, float noiseRangeMultiplier) { NetworkObject val = default(NetworkObject); if (audioClip == null || !((NetworkObjectReference)(ref networkObjectReference)).TryGet(ref val, (NetworkManager)null)) { return; } GameObject gameObject = ((Component)val).gameObject; if (!((Object)(object)gameObject == (Object)null) && gameObject.activeInHierarchy) { if (WantedAudioClips.TryGetValue(taskid, out var value)) { value.AddAudioClips(audioClip, pauseDurations); } else { value = new SpeakTTSAudioClipCache(gameObject, callingAssemblyHash, audioClip, pauseDurations, noiseRangeMultiplier); WantedAudioClips.TryAdd(taskid, value); AddAssemblyTracking(SpeakingNetworkObjectIds, val.NetworkObjectId, callingAssemblyHash); NewSpeakerQueue.Enqueue(taskid); } if (isFinalBatch) { value.MarkLastBatch(); } } } internal static IEnumerator PreGenerateTTS(ulong trackingKeyHash, string[] textsToSpeak, PiperVoiceSettings voiceSettings, CancellationTokenSource cts) { LogConstants.CODE_TRIGGERED.Log("TTSCompanyBackend", "PreGenerateTTS"); if (ActivePreGenTasks.TryRemove(trackingKeyHash, out var value)) { value.SafeCancel(); } ActivePreGenTasks[trackingKeyHash] = cts; try { Task[] ttsTasks = new Task[textsToSpeak.Length]; for (int i = 0; i < textsToSpeak.Length; i++) { ttsTasks[i] = TTSCompanyPlugin._tts.GenerateTTSAsync(textsToSpeak[i], voiceSettings, cts.Token); } foreach (Task currentTask in ttsTasks) { yield return (object)new WaitUntil((Func)(() => currentTask.IsCompleted)); if (currentTask.IsFaulted || currentTask.IsCanceled || !currentTask.Result.Success) { cts.SafeCancel(); break; } TTSResult result = currentTask.Result; if ((Object)(object)result.AudioClip != (Object)null) { Object.Destroy((Object)(object)result.AudioClip); } } } finally { cts.Dispose(); if (ActivePreGenTasks.TryGetValue(trackingKeyHash, out var value2) && value2 == cts) { ActivePreGenTasks.TryRemove(trackingKeyHash, out var _); } } } internal static void AddAssemblyTracking(ConcurrentDictionary> tracker, ulong networkObjectId, ulong assemblyHash) { tracker.GetOrAdd(networkObjectId, (ulong _) => new ConcurrentDictionary()).TryAdd(assemblyHash, 0); } internal static void RemoveAssemblyTracking(ConcurrentDictionary> tracker, ulong networkObjectId, ulong assemblyHash) { if (tracker.TryGetValue(networkObjectId, out var value)) { value.TryRemove(assemblyHash, out var _); if (value.IsEmpty) { tracker.TryRemove(networkObjectId, out var _); } } } internal static void OnReturnedToMainMenu() { foreach (ActiveTTSState value in ActiveTTSCoroutines.Values) { value.Cts?.SafeCancel(); } ActiveTTSCoroutines.Clear(); foreach (SpeakTTSAudioClipCache value2 in WantedAudioClips.Values) { QueuedClip result; while (value2._audioQueue.TryDequeue(out result)) { if ((Object)(object)result.Clip != (Object)null) { Object.Destroy((Object)(object)result.Clip); } } } WantedAudioClips.Clear(); ulong result2; while (NewSpeakerQueue.TryDequeue(out result2)) { } SpeakingNetworkObjectIds.Clear(); GeneratingNetworkObjectIds.Clear(); TTSCompanyNetworking.ClearClientTasks(); TTSCompanyNetworking.ClearServerTasks(); } } internal sealed class TTSGenerator { private const float DecodeOggOffThreadMultiplier = 3.0517578E-05f; internal readonly PiperTTSServer _server = new PiperTTSServer(); private readonly ConcurrentDictionary _inFlightRequests = new ConcurrentDictionary(); private int _maxConcurrent; private SemaphoreSlim _semaphore; private bool _disposed; private bool _isAvailable; private volatile bool _cacheDirectoryEnsured; private static readonly int CPU_totalCores = Environment.ProcessorCount; private static readonly int CPU_coresReservedForGame = Mathf.Max(1, Mathf.CeilToInt((float)CPU_totalCores * 0.1f)); private const int CPU_minimumMaxConcurrentRequests = 1; private static readonly int CPU_availableCoresForTTS = Math.Max(1, CPU_totalCores - CPU_coresReservedForGame); internal int MaxConcurrentRequests { get { return _maxConcurrent; } set { if (value < 1) { LogConstants.TTS_GENERATOR_ARGUMENT_OUT_OF_RANGE_EX.Log("TTSGenerator"); } int num = (_maxConcurrent = Math.Max(1, value)); _ = _semaphore; _semaphore = new SemaphoreSlim(num, num); } } internal TTSGenerator() { SetMaxConcurrentRequests(TTSGenPriority.Normal); } private void SetMaxConcurrentRequests(int maxConcurrentRequests) { MaxConcurrentRequests = maxConcurrentRequests; LogConstants.CODE_NEW_VALUE_SET.Log("TTSGenerator", "maxConcurrentRequests", MaxConcurrentRequests); } internal void SetMaxConcurrentRequests(TTSGenPriority priority) { switch (priority) { case TTSGenPriority.VeryLow: SetMaxConcurrentRequests(Math.Max(1, CPU_availableCoresForTTS / 6)); break; case TTSGenPriority.Low: SetMaxConcurrentRequests(Math.Max(1, CPU_availableCoresForTTS / 4)); break; default: SetMaxConcurrentRequests(Math.Max(1, CPU_availableCoresForTTS / 2)); break; case TTSGenPriority.High: SetMaxConcurrentRequests(Math.Max(1, (int)((float)CPU_availableCoresForTTS * 0.75f))); break; case TTSGenPriority.Max: SetMaxConcurrentRequests(CPU_availableCoresForTTS); break; } } internal async Task InitializeAsync(CancellationToken cancellationToken = default(CancellationToken)) { if (_isAvailable) { return true; } if (!FolderHelper.CheckForPiperTTS()) { LogConstants.TTS_GENERATOR_UNZIP_FAILED.Log("TTSGenerator", "piper-server.exe"); return false; } if (!FolderHelper.CheckForDefaultVoiceModels()) { LogConstants.TTS_GENERATOR_UNZIP_FAILED.Log("TTSGenerator", "TTS-Company-Voices"); return false; } bool flag = await _server.StartAsync(15000, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (flag) { EnsureCacheDirectoryExists(); } _isAvailable = flag; return flag; } private void EnsureCacheDirectoryExists() { if (!_cacheDirectoryEnsured) { Directory.CreateDirectory(TTSConstants.TTS_VOICE_CACHE_SOUNDCLIPS_PATH); _cacheDirectoryEnsured = true; } } internal async Task ShutdownAsync() { if (!_disposed) { _disposed = true; _isAvailable = false; await _server.ShutdownAsync().ConfigureAwait(continueOnCapturedContext: false); _semaphore?.Dispose(); } } internal void Dispose() { if (!_disposed) { _disposed = true; try { ShutdownAsync().GetAwaiter().GetResult(); } catch { LogConstants.CODE_GENERIC_CATCH.Log("TTSGenerator", "Dispose"); } _semaphore?.Dispose(); } } internal Task<(bool Success, string Error)> PreloadVoiceAsync(string voiceName, ulong callingAssemblyHash, CancellationToken cancellationToken = default(CancellationToken)) { if (!_isAvailable || _disposed) { return Task.FromResult((false, "TTS server is not available")); } if (string.IsNullOrWhiteSpace(voiceName)) { return Task.FromResult((false, "voice model name must not be empty")); } return _server.LoadModelAsync(voiceName, callingAssemblyHash, cancellationToken); } internal Task<(bool Success, string Error)> UnloadVoiceAsync(string voiceName, ulong callingAssemblyHash, CancellationToken cancellationToken = default(CancellationToken)) { if (!_isAvailable || _disposed) { return Task.FromResult((false, "TTS server is not available")); } if (string.IsNullOrWhiteSpace(voiceName)) { return Task.FromResult((false, "voice model name must not be empty")); } return _server.UnloadModelAsync(voiceName, callingAssemblyHash, cancellationToken); } internal bool isVoiceModelLoaded(string voiceModelName) { return _server.HasVoiceModelBeenLoaded(voiceModelName); } internal async Task GenerateTTSAsync(string textToSpeak, PiperVoiceSettings settings, CancellationToken cancellationToken) { if (!_isAvailable || _disposed) { return new TTSResult { AudioClip = null, Success = false }; } cancellationToken.ThrowIfCancellationRequested(); if (!ValidateInputs(textToSpeak, settings)) { return new TTSResult { AudioClip = null, Success = false }; } EnsureCacheDirectoryExists(); string hashCacheFileName = HashHelper.GetHashTTSFileNameWithFileType(textToSpeak, settings); if (string.IsNullOrWhiteSpace(hashCacheFileName)) { return new TTSResult { AudioClip = null, Success = false }; } string fullCachePath = Path.Combine(TTSConstants.TTS_VOICE_CACHE_SOUNDCLIPS_PATH, hashCacheFileName); if (File.Exists(fullCachePath) && new FileInfo(fullCachePath).Length > 0) { LogConstants.TTS_GENERATOR_FOUND_CACHED_TTS.Log("TTSGenerator", hashCacheFileName); AudioClip val = await LoadAudioClipFromDiskAsync(fullCachePath, hashCacheFileName).ConfigureAwait(continueOnCapturedContext: false); if ((Object)(object)val != (Object)null) { return new TTSResult { AudioClip = val, Success = true }; } } BusyGeneration inFlight; BusyGeneration value; while (true) { inFlight = _inFlightRequests.GetOrAdd(hashCacheFileName, (string _) => new BusyGeneration((CancellationToken ct) => RunGenerationAsync(hashCacheFileName, fullCachePath, textToSpeak, settings, ct))); if (inFlight.TryAddBusy()) { break; } _inFlightRequests.TryRemove(hashCacheFileName, out value); } try { TaskCompletionSource cancellationTcs = new TaskCompletionSource(); using (cancellationToken.Register(delegate { cancellationTcs.TrySetCanceled(cancellationToken); })) { if (await Task.WhenAny(new Task[2] { inFlight.Task, cancellationTcs.Task }).ConfigureAwait(continueOnCapturedContext: false) == cancellationTcs.Task) { LogConstants.TTS_GENERATOR_TTS_CANCELLED.Log("TTSGenerator", textToSpeak, hashCacheFileName); await cancellationTcs.Task.ConfigureAwait(continueOnCapturedContext: false); } return await inFlight.Task.ConfigureAwait(continueOnCapturedContext: false); } } finally { inFlight.RemoveBusy(); if (inFlight.Task.IsCompleted) { _inFlightRequests.TryRemove(hashCacheFileName, out value); } } } private async Task RunGenerationAsync(string hashCacheFileName, string fullCachePath, string textToSpeak, PiperVoiceSettings settings, CancellationToken sharedCancellationToken) { SemaphoreSlim semaphore = _semaphore; await semaphore.WaitAsync(sharedCancellationToken).ConfigureAwait(continueOnCapturedContext: false); try { if (File.Exists(fullCachePath) && new FileInfo(fullCachePath).Length > 0) { AudioClip val = await LoadAudioClipFromDiskAsync(fullCachePath, hashCacheFileName); return new TTSResult { AudioClip = val, Success = ((Object)(object)val != (Object)null) }; } TTSResult endResult = new TTSResult(); LogConstants.TTS_GENERATOR_GENERATING_TTS.Log("TTSGenerator", textToSpeak, hashCacheFileName); TTSRawResult tTSRawResult = await _server.SynthesizeAsync(textToSpeak, hashCacheFileName, settings, sharedCancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (!tTSRawResult.IsSuccess) { endResult.AudioClip = null; endResult.Success = false; LogConstants.CODE_GENERIC_FAIL.Log("TTSGenerator", "synthResult", tTSRawResult.Error); return endResult; } if (await ConvertPcmToOggAsync(tTSRawResult.Pcm, tTSRawResult.SampleRate, hashCacheFileName, fullCachePath, sharedCancellationToken)) { TTSResult tTSResult = endResult; tTSResult.AudioClip = await LoadAudioClipFromDiskAsync(fullCachePath, hashCacheFileName); endResult.Success = (Object)(object)endResult.AudioClip != (Object)null; } return endResult; } finally { if (File.Exists(fullCachePath) && new FileInfo(fullCachePath).Length == 0L) { TryDeleteCorruptedCache(fullCachePath, hashCacheFileName); } try { semaphore.Release(); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "RunGenerationAsync", ex); } _inFlightRequests.TryRemove(hashCacheFileName, out var _); } } private static Task ConvertPcmToOggAsync(byte[] pcmData, int sourceSampleRate, string fileHashName, string oggOutputPath, CancellationToken cancellationToken) { LogConstants.CODE_TRIGGERED.Log("TTSGenerator", "ConvertPcmToOggAsync"); return Task.Run(delegate { //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) string text = oggOutputPath + "." + Guid.NewGuid().ToString("N") + ".tmp"; short[] array = null; short[] array2 = null; try { int num = pcmData.Length / 2; array = ArrayPool.Shared.Rent(num); Buffer.BlockCopy(pcmData, 0, array, 0, pcmData.Length); short[] array3; int num2; if (sourceSampleRate == 24000 || num == 0) { array3 = array; num2 = num; } else { num2 = (int)((double)num * (24000.0 / (double)sourceSampleRate)); array2 = ArrayPool.Shared.Rent(num2); Resample(array, num, sourceSampleRate, 24000, array2, num2); array3 = array2; } cancellationToken.ThrowIfCancellationRequested(); using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None)) { IOpusEncoder obj = OpusCodecFactory.CreateEncoder(24000, 1, (OpusApplication)2048, (TextWriter)null); obj.Bitrate = 16000; obj.UseVBR = true; OpusOggWriteStream val = new OpusOggWriteStream(obj, (Stream)fileStream, (OpusTags)null, 0, 5, false); val.WriteSamples(array3, 0, num2); val.Finish(); } if (File.Exists(oggOutputPath) && new FileInfo(oggOutputPath).Length > 0) { TryDeleteTempFile(text); return true; } AtomicReplace(text, oggOutputPath); return true; } catch (Exception ex) { TryDeleteTempFile(text); LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ConvertPcmToOggAsync", ex); return false; } finally { if (array != null) { ArrayPool.Shared.Return(array); } if (array2 != null) { ArrayPool.Shared.Return(array2); } } }); } private static void AtomicReplace(string tempPath, string destPath) { try { if (File.Exists(destPath)) { File.Delete(destPath); } File.Move(tempPath, destPath); } catch (IOException) { TryDeleteTempFile(tempPath); } } private static void TryDeleteTempFile(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "TryDeleteTempFile", ex); } } private static void Resample(short[] input, int inputCount, int sourceRate, int targetRate, short[] output, int outputCount) { double num = (double)sourceRate / (double)targetRate; for (int i = 0; i < outputCount; i++) { double num2 = (double)i * num; int num3 = (int)Math.Floor(num2); int num4 = Math.Min(num3 + 1, inputCount - 1); double num5 = num2 - (double)num3; output[i] = (short)((1.0 - num5) * (double)input[num3] + num5 * (double)input[num4]); } } private static void TryDeleteCorruptedCache(string fullCachePath, string hashCacheFileName) { try { if (File.Exists(fullCachePath) && new FileInfo(fullCachePath).Length == 0L) { LogConstants.TTS_GENERATOR_DELETE_0KB_CACHE.Log("TTSGenerator", hashCacheFileName); File.Delete(fullCachePath); } } catch (Exception ex) { LogConstants.TTS_GENERATOR_FAILED_TO_DELETE_0KB_CACHE.Log("TTSGenerator", hashCacheFileName, ex.Message); } } private static Task LoadAudioClipFromDiskAsync(string absoluteFilePath, string clipName) { if (!File.Exists(absoluteFilePath)) { LogConstants.TTS_GENERATOR_NO_CACHED_AUDIO_FOUND.Log("TTSGenerator", clipName, absoluteFilePath); return Task.FromResult(null); } TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); ((MonoBehaviour)TTSCompanyPlugin.instance).StartCoroutine(LoadCoroutine(absoluteFilePath, clipName, taskCompletionSource)); return taskCompletionSource.Task; } private static IEnumerator LoadCoroutine(string absoluteFilePath, string clipName, TaskCompletionSource tcs) { Task decodeTask = Task.Run(() => DecodeOggOffThread(absoluteFilePath)); yield return (object)new WaitUntil((Func)(() => decodeTask.IsCompleted)); if (decodeTask.IsFaulted) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "LoadCoroutine", decodeTask.Exception?.GetBaseException().Message); tcs.SetResult(null); } else if (decodeTask.Result == null || decodeTask.Result.Length == 0) { tcs.SetResult(null); } else { AudioClip val = AudioClip.Create(clipName, decodeTask.Result.Length, 1, 24000, false); val.SetData(decodeTask.Result, 0); tcs.SetResult(val); } } private static float[] DecodeOggOffThread(string absoluteFilePath) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown for (int i = 1; i <= 3; i++) { try { using FileStream fileStream = new FileStream(absoluteFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); OpusOggReadStream val = new OpusOggReadStream(OpusCodecFactory.CreateDecoder(24000, 1, (TextWriter)null), (Stream)fileStream); List list = new List(); int num = 0; while (val.HasNextPacket) { short[] array = val.DecodeNextPacket(); if (array != null) { list.Add(array); num += array.Length; } } float[] array2 = new float[num]; int num2 = 0; foreach (short[] item in list) { for (int j = 0; j < item.Length; j++) { array2[num2 + j] = (float)item[j] * 3.0517578E-05f; } num2 += item.Length; } return array2; } catch (IOException) when (i < 3) { Thread.Sleep(50 * i); } catch (Exception ex2) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "DecodeOggOffThread", ex2.Message); return null; } } return null; } private bool ValidateInputs(string textToSpeak, PiperVoiceSettings settings) { if (string.IsNullOrWhiteSpace(textToSpeak)) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - textToSpeak", "TTS text cannot be empty"); return false; } if (settings == null) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - settings", "PiperVoiceSettings cannot be NULL"); return false; } if (string.IsNullOrWhiteSpace(settings.ModelName)) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - settings.ModelName", "Voice model name must be set in settings"); return false; } if (!_server.IsVoiceModelValid(settings.ModelName)) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - IsVoiceModelValid(settings.ModelName)", "Piper voice model not found or valid"); return false; } if (settings.SpeechRate <= 0f) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSGenerator", "ValidateInputs - settings.SpeechRate", "Speech rate must be > 0"); return false; } return true; } } public sealed class PiperVoiceSettings { private string modelNameWithoutPathOrExtention = "en_US-hfc_female-medium"; private float speechRate = 1f; private float noiseScale = 0.67f; private float noiseScaleW = 0.8f; private float sentenceSilence = 0.2f; private float punctuationSilence = 0.08f; [SerializeField] public string ModelName { get { return modelNameWithoutPathOrExtention; } set { string text = VoiceHelper.CleanupVoiceModelname(value); if (modelNameWithoutPathOrExtention != text) { modelNameWithoutPathOrExtention = text; } } } [SerializeField] public float SpeechRate { get { return speechRate; } set { speechRate = ClampHelper.ClampAndRound(value, 0.05f, 5f); } } [SerializeField] public float NoiseScale { get { return noiseScale; } set { noiseScale = ClampHelper.ClampAndRound(value, 0f, 1f); } } [SerializeField] public float NoiseScaleW { get { return noiseScaleW; } set { noiseScaleW = ClampHelper.ClampAndRound(value, 0f, 1f); } } [SerializeField] public float SentenceSilence { get { return sentenceSilence; } set { sentenceSilence = ClampHelper.ClampAndRound(value, 0f, 5f); } } [SerializeField] public float PunctuationSilence { get { return punctuationSilence; } set { punctuationSilence = ClampHelper.ClampAndRound(value, 0f, 2.5f); } } } public sealed class TTSAudioSourceSettings { private int priority = 128; private float volume = 1f; private float spatialBlend = 1f; private float reverbZoneMix = 1f; private float dopplerLevel; private float minDistance = 1f; private float maxDistance = 40f; [SerializeField] public bool BypassEffects { get; set; } [SerializeField] public bool BypassListenerEffects { get; set; } [SerializeField] public bool BypassReverbZones { get; set; } [SerializeField] public int Priority { get { return priority; } set { priority = Mathf.Clamp(value, 0, 256); } } [SerializeField] public float Volume { get { return volume; } set { volume = Mathf.Clamp(value, 0f, 1f); } } [SerializeField] public float SpatialBlend { get { return spatialBlend; } set { spatialBlend = Mathf.Clamp(value, 0f, 1f); } } [SerializeField] public float ReverbZoneMix { get { return reverbZoneMix; } set { reverbZoneMix = Mathf.Clamp(value, 0f, 1f); } } [SerializeField] public float DopplerLevel { get { return dopplerLevel; } set { dopplerLevel = Mathf.Clamp(value, 0f, 1f); } } [SerializeField] public float MinDistance { get { return minDistance; } set { minDistance = Mathf.Clamp(value, 0f, 128f); } } [SerializeField] public float MaxDistance { get { return maxDistance; } set { maxDistance = Mathf.Clamp(value, 1f, 128f); } } [SerializeField] public AudioRolloffMode RolloffMode { get; set; } = (AudioRolloffMode)1; [SerializeField] public AudioMixerGroup OutputAudioMixerGroup { get; set; } [SerializeField] public (AudioSourceCurveType type, AnimationCurve curve)? CustomCurve { get; set; } } public sealed class TTSResult { public AudioClip AudioClip { get; set; } public bool Success { get; set; } } internal sealed class TTSPlaybackManager : MonoBehaviour { private readonly Dictionary _activeCoroutines = new Dictionary(); private void Update() { ulong result; while (TTSCompanyBackend.NewSpeakerQueue.TryDequeue(out result)) { if (!_activeCoroutines.ContainsKey(result)) { _activeCoroutines.Add(result, ((MonoBehaviour)this).StartCoroutine(ProcessAudioQueue(result))); } } } private IEnumerator ProcessAudioQueue(ulong speakerHash) { GameObject cachedSpeakingNetworkObject = null; SpeakTTSAudioClipCache cache; while (TTSCompanyBackend.WantedAudioClips.TryGetValue(speakerHash, out cache) && !((Object)(object)cache._foundNetworkObject == (Object)null)) { cachedSpeakingNetworkObject = cache._foundNetworkObject; if (cache._audioQueue.TryDequeue(out var queued)) { if (TTSAudioSourceManager.PlayAudioSource(cache._foundNetworkObject, cache._callingAssemblyHash, queued.Clip, cache._noiseRangeMultiplier)) { yield return (object)new WaitForSeconds(queued.Clip.length); if ((!cache._isLastBatch || !cache._audioQueue.IsEmpty) && queued.PauseAfter > 0f) { yield return (object)new WaitForSeconds(queued.PauseAfter); } } else { yield return null; } } else { if (cache._isLastBatch) { break; } yield return null; } queued = default(QueuedClip); } TTSCompanyBackend.WantedAudioClips.TryRemove(speakerHash, out var value); _activeCoroutines.Remove(speakerHash); NetworkObject val = default(NetworkObject); if ((Object)(object)cachedSpeakingNetworkObject != (Object)null && cachedSpeakingNetworkObject.TryGetComponent(ref val)) { ulong assemblyHash = value?._callingAssemblyHash ?? 0; TTSCompanyBackend.RemoveAssemblyTracking(TTSCompanyBackend.SpeakingNetworkObjectIds, val.NetworkObjectId, assemblyHash); } } internal void CancelPlayback(CancelAudioTTS_NET data) { TTSCompanyNetworking.CancelClientTask(data._taskId); if (TTSCompanyBackend.WantedAudioClips.TryRemove(data._taskId, out var value)) { QueuedClip result; while (value._audioQueue.TryDequeue(out result)) { if ((Object)(object)result.Clip != (Object)null) { Object.Destroy((Object)(object)result.Clip); } } if ((Object)(object)value._foundNetworkObject != (Object)null) { TTSAudioSourceManager.StopAudioSource(value._foundNetworkObject, value._callingAssemblyHash); NetworkObject val = default(NetworkObject); if (value._foundNetworkObject.TryGetComponent(ref val)) { TTSCompanyBackend.RemoveAssemblyTracking(TTSCompanyBackend.SpeakingNetworkObjectIds, val.NetworkObjectId, value._callingAssemblyHash); } } } if (_activeCoroutines.TryGetValue(data._taskId, out var value2)) { if (value2 != null) { ((MonoBehaviour)this).StopCoroutine(value2); } _activeCoroutines.Remove(data._taskId); } } } } namespace TTSCompany.Components.Server.Components { internal sealed class ActiveTTSState { internal Coroutine Coroutine; internal CancellationTokenSource Cts; internal ulong NetworkObjectId; } internal sealed class BusyGeneration { internal readonly CancellationTokenSource Cts = new CancellationTokenSource(); internal readonly Task Task; private readonly object _lock = new object(); private int _waiterCount; private bool _finalized; internal BusyGeneration(Func> factory) { Task = factory(Cts.Token); } internal bool TryAddBusy() { lock (_lock) { if (_finalized) { return false; } _waiterCount++; return true; } } internal void RemoveBusy() { lock (_lock) { if (--_waiterCount <= 0) { _finalized = true; Cts.SafeCancel(); } } } } internal static class JSONHelper { internal static string Escape(string s) { if (string.IsNullOrEmpty(s)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(s.Length + 8); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; case '\b': stringBuilder.Append("\\b"); continue; case '\f': stringBuilder.Append("\\f"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } internal static Dictionary ParseFlatObject(string json) { Dictionary dictionary = new Dictionary(); if (string.IsNullOrEmpty(json)) { return dictionary; } int i = 0; SkipWhitespace(json, ref i); Expect(json, ref i, '{'); SkipWhitespace(json, ref i); if (Peek(json, i) == '}') { i++; return dictionary; } while (true) { SkipWhitespace(json, ref i); string key = ParseString(json, ref i); SkipWhitespace(json, ref i); Expect(json, ref i, ':'); SkipWhitespace(json, ref i); object value = ParseValue(json, ref i); dictionary[key] = value; SkipWhitespace(json, ref i); switch (Peek(json, i)) { case ',': break; case '}': i++; return dictionary; default: throw new FormatException($"Unexpected character at position {i} in JSON: {json}"); } i++; } } private static object ParseValue(string json, ref int i) { switch (Peek(json, i)) { case '"': return ParseString(json, ref i); case 't': Expect(json, ref i, "true"); return true; case 'f': Expect(json, ref i, "false"); return false; case 'n': Expect(json, ref i, "null"); return null; default: return ParseNumber(json, ref i); } } private static string ParseString(string json, ref int i) { Expect(json, ref i, '"'); StringBuilder stringBuilder = new StringBuilder(); while (true) { char c = json[i++]; switch (c) { case '\\': { char c2 = json[i++]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': { int num = Convert.ToInt32(json.Substring(i, 4), 16); stringBuilder.Append((char)num); i += 4; break; } default: stringBuilder.Append(c2); break; } break; } default: stringBuilder.Append(c); break; case '"': return stringBuilder.ToString(); } } } private static object ParseNumber(string json, ref int i) { int num = i; while (i < json.Length && (char.IsDigit(json[i]) || json[i] == '-' || json[i] == '+' || json[i] == '.' || json[i] == 'e' || json[i] == 'E')) { i++; } string text = json.Substring(num, i - num); if (text.IndexOfAny(new char[3] { '.', 'e', 'E' }) >= 0) { return double.Parse(text, CultureInfo.InvariantCulture); } return long.Parse(text, CultureInfo.InvariantCulture); } private static void SkipWhitespace(string json, ref int i) { while (i < json.Length && char.IsWhiteSpace(json[i])) { i++; } } private static char Peek(string json, int i) { if (i >= json.Length) { return '\0'; } return json[i]; } private static void Expect(string json, ref int i, char c) { if (i >= json.Length || json[i] != c) { throw new FormatException($"Expected '{c}' at position {i} in JSON: {json}"); } i++; } private static void Expect(string json, ref int i, string token) { if (i + token.Length > json.Length || string.CompareOrdinal(json, i, token, 0, token.Length) != 0) { throw new FormatException($"Expected '{token}' at position {i} in JSON: {json}"); } i += token.Length; } } internal struct TTSRawResult { internal bool IsSuccess; internal bool IsCancelled; internal string Error; internal byte[] Pcm; internal int SampleRate; internal static TTSRawResult Ok(byte[] pcm, int sampleRate) { return new TTSRawResult { IsSuccess = true, Pcm = pcm, SampleRate = sampleRate }; } internal static TTSRawResult Failure(string error) { return new TTSRawResult { IsSuccess = false, Error = error }; } internal static TTSRawResult Cancelled() { return new TTSRawResult { IsSuccess = false, IsCancelled = true, Error = "Cancelled" }; } } internal sealed class VoiceModelMemoryManager { private struct MEMORYSTATUSEX { public uint dwLength; public uint dwMemoryLoad; public ulong ullTotalPhys; public ulong ullAvailPhys; public ulong ullTotalPageFile; public ulong ullAvailPageFile; public ulong ullTotalVirtual; public ulong ullAvailVirtual; public ulong ullAvailExtendedVirtual; } private readonly long _maxMemoryPoolBytes; private readonly long _fallbackModelSizeBytes = ConvertMBToLong(65); private readonly PiperTTSServer _piperServer; private readonly ConcurrentDictionary _modelLocations = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary _modelSizes = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary _modelLastAccess = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary> _modelAssemblies = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); private string[] _cachedFoundVoiceNames = Array.Empty(); private string[] _cachedLoadedVoiceNames = Array.Empty(); private readonly ConcurrentDictionary _evictedModels = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private long _currentLoadedBytes; private readonly LinkedList _LRU_list = new LinkedList(); private readonly Dictionary> _LRU_elements = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly object _LRU_lock = new object(); internal VoiceModelMemoryManager(PiperTTSServer piperServer) { _piperServer = piperServer ?? throw new ArgumentNullException("piperServer"); _maxMemoryPoolBytes = ConvertMBToLong(DetermineOptimalPoolSizeMegabytes()); } internal void InitializeModelRegistry() { string pluginPath = Paths.PluginPath; foreach (string item in FindVoiceModelFolders()) { string text = Path.Combine(pluginPath, item); foreach (FileInfo item2 in new DirectoryInfo(text).EnumerateFiles("*.onnx", SearchOption.TopDirectoryOnly)) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item2.Name); _modelSizes[fileNameWithoutExtension] = item2.Length; _modelLocations.TryAdd(fileNameWithoutExtension, Path.Combine(text, fileNameWithoutExtension + ".onnx")); LogConstants.VOICE_MODEL_MEM_MANAGER_FOUND_VOICE_MODEL_WITH_SIZE.Log("VoiceModelMemoryManager", fileNameWithoutExtension, item2.Length); } } UpdateFoundVoiceNamesCache(); UpdateLoadedVoiceNamesCache(); } internal int GetAssemblyCountForModel(string modelName) { if (_modelAssemblies.TryGetValue(modelName, out var value)) { lock (value) { return value.Count; } } return 0; } internal bool HasVoiceModelBeenLoaded(string modelName) { return GetAssemblyCountForModel(modelName) > 0; } internal bool WasVoiceModelEvicted(string modelName) { return _evictedModels.ContainsKey(modelName); } internal bool IsVoiceModelValid(string modelName) { return _modelSizes.ContainsKey(modelName); } internal void UpdateLastUse(string modelName) { _modelLastAccess[modelName] = DateTime.UtcNow; lock (_LRU_lock) { if (_LRU_elements.TryGetValue(modelName, out var value)) { _LRU_list.Remove(value); } else { value = new LinkedListNode(modelName); } _LRU_list.AddLast(value); _LRU_elements[modelName] = value; } } private string FindOldestEvictableModel(string excludeModelName) { lock (_LRU_lock) { for (LinkedListNode linkedListNode = _LRU_list.First; linkedListNode != null; linkedListNode = linkedListNode.Next) { string value = linkedListNode.Value; if (_modelAssemblies.ContainsKey(value) && !_evictedModels.ContainsKey(value) && !value.Equals(excludeModelName, StringComparison.OrdinalIgnoreCase)) { return value; } } return null; } } internal string GetRandomFoundTTSVoiceName() { if (_modelLocations.Count == 0) { return null; } string[] cachedFoundVoiceNames = _cachedFoundVoiceNames; if (cachedFoundVoiceNames.Length != 0) { return cachedFoundVoiceNames[Random.Range(0, cachedFoundVoiceNames.Length)]; } return null; } internal string GetRandomLoadedTTSVoiceName() { if (_modelAssemblies.Count == 0) { return null; } string[] cachedLoadedVoiceNames = _cachedLoadedVoiceNames; if (cachedLoadedVoiceNames.Length != 0) { return cachedLoadedVoiceNames[Random.Range(0, cachedLoadedVoiceNames.Length)]; } return null; } internal string[] GetAllFoundTTSVoiceNames() { return _cachedFoundVoiceNames; } internal string[] GetAllLoadedTTSVoiceNames() { return _cachedLoadedVoiceNames; } private string GetLoadModelString(string modelName, string voiceModelLocation) { return "{\"command\":\"load_model\",\"model\":\"" + JSONHelper.Escape(modelName) + "\",\"model_path\":\"" + JSONHelper.Escape(voiceModelLocation.TrimEnd('\\', '/')).Replace("\\", "\\\\") + "\",\"use_cuda\":false}\n"; } internal async Task<(bool Success, string Error)> ReloadModelAsync(string modelName, CancellationToken cancellationToken) { if (!_modelLocations.TryGetValue(modelName, out var voiceModelLocation)) { return (Success: false, Error: "Voice model file location value not found"); } UpdateLastUse(modelName); await EnforceDynamicMemoryLimitsAsync(modelName, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); Dictionary response = await _piperServer.SendSimpleCommandAsync(GetLoadModelString(modelName, voiceModelLocation), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); (bool, string) result = _piperServer.ToResult(response); if (result.Item1) { _evictedModels.TryRemove(modelName, out var _); long value3; long value2 = (_modelSizes.TryGetValue(modelName, out value3) ? value3 : _fallbackModelSizeBytes); Interlocked.Add(ref _currentLoadedBytes, value2); UpdateLoadedVoiceNamesCache(); LogConstants.PIPER_TTS_RELOADED_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName); } return result; } internal async Task<(bool Success, string Error)> LoadModelAsync(string modelName, ulong callingAssemblyHash, CancellationToken cancellationToken) { if (!_modelLocations.TryGetValue(modelName, out var voiceModelLocation)) { return (Success: false, Error: "Voice model file location value not found"); } UpdateLastUse(modelName); HashSet assemblies = _modelAssemblies.GetOrAdd(modelName, (string _) => new HashSet()); bool flag; lock (assemblies) { if (_evictedModels.ContainsKey(modelName)) { flag = true; } else { if (assemblies.Contains(callingAssemblyHash)) { return (Success: true, Error: string.Empty); } flag = assemblies.Count == 0; } } if (!flag) { lock (assemblies) { assemblies.Add(callingAssemblyHash); } return (Success: true, Error: string.Empty); } await EnforceDynamicMemoryLimitsAsync(modelName, cancellationToken); Dictionary response = await _piperServer.SendSimpleCommandAsync(GetLoadModelString(modelName, voiceModelLocation), cancellationToken).ConfigureAwait(continueOnCapturedContext: false); (bool, string) result = _piperServer.ToResult(response); if (result.Item1) { _evictedModels.TryRemove(modelName, out var _); long value3; long value2 = (_modelSizes.TryGetValue(modelName, out value3) ? value3 : _fallbackModelSizeBytes); Interlocked.Add(ref _currentLoadedBytes, value2); lock (assemblies) { assemblies.Add(callingAssemblyHash); } LogConstants.PIPER_TTS_LOADED_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName); UpdateLoadedVoiceNamesCache(); } else { lock (assemblies) { if (assemblies.Count == 0) { _modelAssemblies.TryRemove(modelName, out var _); } } LogConstants.PIPER_TTS_FAILED_LOADING_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName); UpdateLoadedVoiceNamesCache(); } return result; } internal async Task<(bool Success, string Error)> UnloadModelAsync(string modelName, ulong callingAssemblyHash, CancellationToken cancellationToken) { if (!_modelAssemblies.TryGetValue(modelName, out var assemblies)) { return (Success: true, Error: string.Empty); } lock (assemblies) { if (!assemblies.Contains(callingAssemblyHash)) { return (Success: true, Error: string.Empty); } if (assemblies.Count != 1) { assemblies.Remove(callingAssemblyHash); return (Success: true, Error: string.Empty); } } HashSet value2; DateTime value3; if (_evictedModels.TryRemove(modelName, out var _)) { lock (assemblies) { assemblies.Remove(callingAssemblyHash); if (assemblies.Count == 0) { _modelAssemblies.TryRemove(modelName, out value2); _modelLastAccess.TryRemove(modelName, out value3); } } UpdateLoadedVoiceNamesCache(); return (Success: true, Error: string.Empty); } string requestJsonLine = "{\"command\":\"unload_model\",\"model\":\"" + JSONHelper.Escape(modelName) + "\"}\n"; Dictionary response = await _piperServer.SendSimpleCommandAsync(requestJsonLine, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); (bool, string) result = _piperServer.ToResult(response); if (result.Item1) { long value4; long num = (_modelSizes.TryGetValue(modelName, out value4) ? value4 : _fallbackModelSizeBytes); Interlocked.Add(ref _currentLoadedBytes, -num); lock (assemblies) { assemblies.Remove(callingAssemblyHash); if (assemblies.Count == 0) { _modelAssemblies.TryRemove(modelName, out value2); _modelLastAccess.TryRemove(modelName, out value3); } } LogConstants.PIPER_TTS_UNLOADED_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName); UpdateLoadedVoiceNamesCache(); } else { LogConstants.PIPER_TTS_FAILED_UNLOADING_VOICE_MODEL.Log("VoiceModelMemoryManager", modelName); } return result; } private async Task EnforceDynamicMemoryLimitsAsync(string targetModelName, CancellationToken cancellationToken) { long value; long targetModelSize = (_modelSizes.TryGetValue(targetModelName, out value) ? value : _fallbackModelSizeBytes); while (Interlocked.Read(in _currentLoadedBytes) + targetModelSize > _maxMemoryPoolBytes) { string text = FindOldestEvictableModel(targetModelName); if (string.IsNullOrEmpty(text)) { LogConstants.VOICE_MODEL_MEM_MANAGER_NO_MODEL_TO_EVICT.Log("VoiceModelMemoryManager", targetModelName); break; } LogConstants.VOICE_MODEL_MEM_MANAGER_POOL_LIMIT_REACHED.Log("VoiceModelMemoryManager", text); await ForceUnloadModelAsync(text, cancellationToken); } } private async Task ForceUnloadModelAsync(string modelName, CancellationToken cancellationToken) { string requestJsonLine = "{\"command\":\"unload_model\",\"model\":\"" + JSONHelper.Escape(modelName) + "\"}\n"; try { Dictionary response = await _piperServer.SendSimpleCommandAsync(requestJsonLine, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (_piperServer.ToResult(response).Success) { _evictedModels.TryAdd(modelName, value: true); long value; long num = (_modelSizes.TryGetValue(modelName, out value) ? value : _fallbackModelSizeBytes); Interlocked.Add(ref _currentLoadedBytes, -num); UpdateLoadedVoiceNamesCache(); } else { LogConstants.VOICE_MODEL_MEM_MANAGER_NO_MODEL_TO_EVICT.Log("VoiceModelMemoryManager", modelName); } } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("VoiceModelMemoryManager", "ForceUnloadModelAsync", ex.Message); } } private void UpdateFoundVoiceNamesCache() { _cachedFoundVoiceNames = _modelLocations.Keys.ToArray(); } private void UpdateLoadedVoiceNamesCache() { List list = new List(); foreach (string key in _modelAssemblies.Keys) { if (!_evictedModels.ContainsKey(key)) { list.Add(key); } } _cachedLoadedVoiceNames = list.ToArray(); } private static long ConvertMBToLong(int valueInMb) { return (long)valueInMb * 1024L * 1024; } private static List FindVoiceModelFolders() { string pluginPath = Paths.PluginPath; List list = new List(); foreach (string item in Directory.EnumerateDirectories(pluginPath)) { string text = Path.Combine(item, "TTS-Company-Voices"); if (Directory.Exists(text)) { list.Add(text.Substring(pluginPath.Length).TrimStart(new char[1] { Path.DirectorySeparatorChar })); } } return list; } [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); private static int DetermineOptimalPoolSizeMegabytes() { MEMORYSTATUSEX lpBuffer = new MEMORYSTATUSEX { dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX)) }; if (GlobalMemoryStatusEx(ref lpBuffer)) { double num = (double)lpBuffer.ullTotalPhys / 1073741824.0; if (num <= 2.5) { return 512; } if (num <= 4.5) { return 1536; } if (num <= 8.5) { return 3072; } } return 4096; } } } namespace TTSCompany.Components.Networking { internal static class TTSCompanyNetworking { private const string message_PREFIX = ".msg_"; private const string messageId_SpawnTTSAudioSource_Server = "PixelIndieDev_TTSCompany.msg_SpawnTTSAudioSource_Server"; private const string messageId_SpawnTTSAudioSource_Clients = "PixelIndieDev_TTSCompany.msg_SpawnTTSAudioSource_Clients"; private const string messageId_UpdateTTSAudioSourceSettings_Server = "PixelIndieDev_TTSCompany.msg_UpdateTTSAudioSourceSettings_Server"; private const string messageId_UpdateTTSAudioSourceSettings_Clients = "PixelIndieDev_TTSCompany.msg_UpdateTTSAudioSourceSettings_Clients"; private const string messageId_DespawnTTSAudioSource_Server = "PixelIndieDev_TTSCompany.msg_DespawnTTSAudioSource_Server"; private const string messageId_DespawnTTSAudioSource_Clients = "PixelIndieDev_TTSCompany.msg_DespawnTTSAudioSource_Clients"; private const string messageId_SpeakTTS_Clients = "PixelIndieDev_TTSCompany.msg_SpeakTTS_Clients"; private const string messageId_SpeakTTS_Server = "PixelIndieDev_TTSCompany.msg_SpeakTTS_Server"; private const string messageId_SentenceProgress = "PixelIndieDev_TTSCompany.msg_SentenceProgress"; private const string messageId_PlaySpeakTTS = "PixelIndieDev_TTSCompany.msg_PlaySpeakTTS"; private const string messageId_CancelSpeakTTS = "PixelIndieDev_TTSCompany.msg_CancelSpeakTTS"; private const string messageId_StopSpeakingTTS_Server = "PixelIndieDev_TTSCompany.msg_StopSpeakingTTS_Server"; private static LNetworkMessage TTS_networkMessage_SpawnTTSAudioSource_Server; private static LNetworkMessage TTS_networkMessage_SpawnTTSAudioSource_Clients; private static LNetworkMessage TTS_networkMessage_UpdateTTSAudioSourceSettings_Server; private static LNetworkMessage TTS_networkMessage_UpdateTTSAudioSourceSettings_Clients; private static LNetworkMessage TTS_networkMessage_DespawnTTSAudioSource_Server; private static LNetworkMessage TTS_networkMessage_DespawnTTSAudioSource_Clients; private static LNetworkMessage TTS_networkMessage_SpeakTTS_Clients; private static LNetworkMessage TTS_networkMessage_SpeakTTS_Server; private static LNetworkMessage TTS_networkMessage_SentenceProgress; private static LNetworkMessage TTS_networkMessage_PlaySpeakTTS; private static LNetworkMessage TTS_networkMessage_CancelSpeakTTS; private static LNetworkMessage TTS_networkMessage_StopSpeakingTTS_Server; private static readonly ConcurrentDictionary ActiveTasks_Server = new ConcurrentDictionary(); private static ulong _nextSessionId_Speak = 0uL; private static readonly ConcurrentDictionary<(ulong NetworkObjectId, ulong CallingAssemblyHash), SpawnTTSAudioSource_NET> ActiveAudioSources_Server = new ConcurrentDictionary<(ulong, ulong), SpawnTTSAudioSource_NET>(); private static readonly ConcurrentDictionary ClientTasks = new ConcurrentDictionary(); internal static void Initialize() { TTS_networkMessage_SpawnTTSAudioSource_Server = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_SpawnTTSAudioSource_Server", (Action)SpawnTTSAudioSource, (Action)null, (Action)null); TTS_networkMessage_SpawnTTSAudioSource_Clients = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_SpawnTTSAudioSource_Clients", (Action)null, (Action)TTSAudioSourceManager.AddPermanentTTSAudioSource, (Action)null); TTS_networkMessage_UpdateTTSAudioSourceSettings_Server = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_UpdateTTSAudioSourceSettings_Server", (Action)UpdateTTSAudioSourceSettings, (Action)null, (Action)null); TTS_networkMessage_UpdateTTSAudioSourceSettings_Clients = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_UpdateTTSAudioSourceSettings_Clients", (Action)null, (Action)TTSAudioSourceManager.UpdateTTSAudioSourceSettings, (Action)null); TTS_networkMessage_DespawnTTSAudioSource_Server = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_DespawnTTSAudioSource_Server", (Action)DespawnTTSAudioSource, (Action)null, (Action)null); TTS_networkMessage_DespawnTTSAudioSource_Clients = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_DespawnTTSAudioSource_Clients", (Action)null, (Action)TTSAudioSourceManager.RemovePermanentTTSAudioSource, (Action)null); TTS_networkMessage_SpeakTTS_Clients = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_SpeakTTS_Clients", (Action)null, (Action)TTSCompanyBackend.SpeakTTSAtNetworkObject_OnClient, (Action)null); TTS_networkMessage_SpeakTTS_Server = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_SpeakTTS_Server", (Action)StartActiveTask, (Action)null, (Action)null); TTS_networkMessage_SentenceProgress = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_SentenceProgress", (Action)UpdateActiveTask, (Action)null, (Action)null); TTS_networkMessage_PlaySpeakTTS = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_PlaySpeakTTS", (Action)null, (Action)PlayTTS, (Action)null); TTS_networkMessage_CancelSpeakTTS = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_CancelSpeakTTS", (Action)null, (Action)TTSCompanyPlugin._ttsPlaybackManagerObject.CancelPlayback, (Action)null); TTS_networkMessage_StopSpeakingTTS_Server = LNetworkMessage.Connect("PixelIndieDev_TTSCompany.msg_StopSpeakingTTS_Server", (Action)StopSpeakingTTS, (Action)null, (Action)null); } private static void SpawnTTSAudioSource(SpawnTTSAudioSource_NET data, ulong recievedFromPlayer) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!LNetworkUtils.IsHostOrServer) { return; } ConcurrentDictionary<(ulong NetworkObjectId, ulong CallingAssemblyHash), SpawnTTSAudioSource_NET> activeAudioSources_Server = ActiveAudioSources_Server; NetworkObjectReference networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker; activeAudioSources_Server[(((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, data._callingAssemblyHash)] = data; try { TTS_networkMessage_SpawnTTSAudioSource_Clients.SendClients(data); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "SpawnTTSAudioSource", ex); } } private static void UpdateTTSAudioSourceSettings(UpdateTTSAudioSourceSettings_NET data, ulong recievedFromPlayer) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (!LNetworkUtils.IsHostOrServer) { return; } (ulong, ulong) key = (((NetworkObjectReference)(ref data._networkObjectRefOfSpeaker)).NetworkObjectId, data._callingAssemblyHash); if (ActiveAudioSources_Server.TryGetValue(key, out var value)) { ActiveAudioSources_Server[key] = new SpawnTTSAudioSource_NET(value._networkObjectRefOfSpeaker, value._callingAssemblyHash, data._audioSourceSettings); } try { TTS_networkMessage_UpdateTTSAudioSourceSettings_Clients.SendClients(data); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "UpdateTTSAudioSourceSettings", ex); } } private static void DespawnTTSAudioSource(DespawnTTSAudioSource_NET data, ulong recievedFromPlayer) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!LNetworkUtils.IsHostOrServer) { return; } ConcurrentDictionary<(ulong NetworkObjectId, ulong CallingAssemblyHash), SpawnTTSAudioSource_NET> activeAudioSources_Server = ActiveAudioSources_Server; NetworkObjectReference networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker; activeAudioSources_Server.TryRemove((((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).NetworkObjectId, data._callingAssemblyHash), out var _); try { TTS_networkMessage_DespawnTTSAudioSource_Clients.SendClients(data); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "DespawnTTSAudioSource", ex); } } private static void UpdateActiveTask(SentenceProgressData_NET data, ulong recievedFromPlayer) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (!LNetworkUtils.IsHostOrServer) { return; } LogConstants.TTS_COMPANY_NETWORKING_UPDATE_TASK.Log("TTSCompanyNetworking", recievedFromPlayer, data._sessionId); if (ActiveTasks_Server.TryGetValue(data._sessionId, out var value)) { bool[] value2; if (!data._success) { CancelAnyExistingSessionFor(value._speakingObject, value._callingAssemblyHash, "client failed generation"); } else if (value._completionList.TryGetValue(recievedFromPlayer, out value2)) { value2[data._textIndex] = data._success; CheckForFinishedTask(value); } } } private static void CheckForFinishedTask(TTSTask task) { int num = int.MaxValue; foreach (KeyValuePair snapshotClientId in task._snapshotClientIds) { if (LNetworkUtils.AllConnectedClients.Contains(snapshotClientId.Key) && task._completionList.TryGetValue(snapshotClientId.Key, out var value)) { int i; for (i = 0; i < value.Length && value[i]; i++) { } if (i < num) { num = i; } } } if (num == int.MaxValue || num <= task._lastStartSpeakingIndex) { return; } int num2 = num - task._lastStartSpeakingIndex; bool flag = num >= task._textsToSpeak.Length; if (num2 >= task._startSpeakingAtAmountOfFinishedTasks || flag) { int lastStartSpeakingIndex = task._lastStartSpeakingIndex; int endIndex = num - 1; try { TTS_networkMessage_PlaySpeakTTS.SendClients(new PlayAudioTTS_NET(task._taskId, lastStartSpeakingIndex, endIndex, flag)); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "TTS_networkMessage_PlaySpeakTTS.SendClients", ex); } task._lastStartSpeakingIndex = num; if (flag) { task._cts?.Dispose(); task._cts = null; StartServerPlaybackCleanupTimeout(task); } } } private static void StartServerPlaybackCleanupTimeout(TTSTask task) { if (!LNetworkUtils.IsHostOrServer) { return; } Task.Delay(TTSTimeoutHelper.GetPlaybackTimeout(task._textsToSpeak, task._voiceSettings)).ContinueWith(delegate { if (ActiveTasks_Server.TryRemove(task._taskId, out var _)) { LogConstants.TTS_COMPANY_NETWORKING_PLAYBACK_CLEANUP.Log("TTSCompanyNetworking", task._taskId); } }); } private static void StartActiveTask(TTSSpeakTTS_NET data, ulong recievedFromPlayer) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if (LNetworkUtils.IsHostOrServer) { CancelAnyExistingSessionFor(data._networkObjectRefOfSpeaker, data._callingAssemblyHash, "Superseded by new session"); _nextSessionId_Speak++; ulong currentSessionId = _nextSessionId_Speak; TTSTask tTSTask = new TTSTask(LNetworkUtils.AllConnectedClients, data._textsToSpeak.Length) { _taskId = currentSessionId, _speakingObject = data._networkObjectRefOfSpeaker, _textsToSpeak = data._textsToSpeak, _callingAssemblyHash = data._callingAssemblyHash, _voiceSettings = data._voiceSettings, _textsWaited = 0, _cancelled = false, _cts = new CancellationTokenSource() }; TimeSpan generationTimeout = TTSTimeoutHelper.GetGenerationTimeout(data._textsToSpeak, data._voiceSettings); tTSTask._cts.Token.Register(delegate { HostCancelSession(currentSessionId, "Timed out"); }); tTSTask._cts.CancelAfter(generationTimeout); ActiveTasks_Server.TryAdd(currentSessionId, tTSTask); TTS_networkMessage_SpeakTTS_Clients.SendClients(new TTSSpeakTTS_PLUS_NET(data, currentSessionId)); } } private static void CancelAnyExistingSessionFor(NetworkObjectReference target, ulong callingAssemblyHash, string reason) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) TTSTask tTSTask = ActiveTasks_Server.Values.FirstOrDefault((TTSTask s) => !s._cancelled && s._callingAssemblyHash == callingAssemblyHash && ((NetworkObjectReference)(ref s._speakingObject)).NetworkObjectId == ((NetworkObjectReference)(ref target)).NetworkObjectId); if (tTSTask != null) { HostCancelSession(tTSTask._taskId, reason); } } private static void HostCancelSession(ulong sessionId, string reason) { if (!ActiveTasks_Server.TryGetValue(sessionId, out var value) || value._cancelled) { return; } value._cancelled = true; ActiveTasks_Server.TryRemove(sessionId, out var _); value._cts?.Dispose(); LogConstants.TTS_COMPANY_NETWORKING_TASK_CANCELLED.Log("TTSCompanyNetworking", sessionId, reason); try { TTS_networkMessage_CancelSpeakTTS.SendClients(new CancelAudioTTS_NET(sessionId, reason)); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "HostCancelSession", ex); } } private static void StopSpeakingTTS(StopSpeakingTTS_NET data, ulong recievedFromPlayer) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (LNetworkUtils.IsHostOrServer) { CancelAnyExistingSessionFor(data._networkObjectRefOfSpeaker, data._callingAssemblyHash, "Stopped speaking"); } } private static void PlayTTS(PlayAudioTTS_NET playData) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (ClientTasks.TryGetValue(playData._taskId, out var value)) { int num = playData._endIndex - playData._startIndex + 1; AudioClip[] array = (AudioClip[])(object)new AudioClip[num]; float[] array2 = new float[num]; for (int i = playData._startIndex; i <= playData._endIndex; i++) { int num2 = i - playData._startIndex; array[num2] = value._generatedClips[i]; array2[num2] = value._pauseDurations[i]; value._generatedClips[i] = null; } TTSCompanyBackend.PlaySpeakTTSAtNetworkObject_OnClient(playData._taskId, value._networkObjectReference, value._callingAssemblyHash, array, array2, playData._isLastBatch, value._noiseRangeMultiplier); if (playData._endIndex >= value._generatedClips.Length - 1) { ClientTasks.TryRemove(playData._taskId, out var _); } } } internal static void Request_Server_SpawnTTSSource(SpawnTTSAudioSource_NET data) { try { TTS_networkMessage_SpawnTTSAudioSource_Server.SendServer(data); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "Request_Server_SpawnTTSSource", ex); } } internal static void Request_Server_UpdateTTSAudioSourceSettings(UpdateTTSAudioSourceSettings_NET data) { try { TTS_networkMessage_UpdateTTSAudioSourceSettings_Server.SendServer(data); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "Request_Server_UpdateTTSAudioSourceSettings", ex); } } internal static void Request_Server_DespawnTTSSource(DespawnTTSAudioSource_NET data) { try { TTS_networkMessage_DespawnTTSAudioSource_Server.SendServer(data); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "Request_Server_DespawnTTSSource", ex); } } internal static void Request_Server_SpeakTTS(TTSSpeakTTS_NET data) { try { TTS_networkMessage_SpeakTTS_Server.SendServer(data); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "Request_Server_SpeakTTS", ex); } } internal static void Request_Server_UpdateSentenceProgress(SentenceProgressData_NET data) { try { TTS_networkMessage_SentenceProgress.SendServer(data); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "Request_Server_UpdateSentenceProgress", ex); } } internal static void Request_Server_StopSpeakingTTS(StopSpeakingTTS_NET data) { try { TTS_networkMessage_StopSpeakingTTS_Server.SendServer(data); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "Request_Server_StopSpeakingTTS", ex); } } internal static void CreateClientTask(ulong taskId, NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash, string[] textsToSpeak, float sentenceSilence, float punctuationSilence, float noiseRangeMultiplier, CancellationTokenSource cts) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) if (ClientTasks.TryRemove(taskId, out var value)) { value._cts.SafeCancel(); value._cts?.Dispose(); } float[] array = new float[textsToSpeak.Length]; for (int i = 0; i < textsToSpeak.Length; i++) { array[i] = TTSCompanyUtils.DetermineEndPause(textsToSpeak[i], sentenceSilence, punctuationSilence); } ClientTaskState value2 = new ClientTaskState { _sentences = textsToSpeak, _generatedClips = (AudioClip[])(object)new AudioClip[textsToSpeak.Length], _callingAssemblyHash = callingAssemblyHash, _pauseDurations = array, _cts = cts, _networkObjectReference = networkObjectRefOfSpeaker, _noiseRangeMultiplier = noiseRangeMultiplier }; ClientTasks.TryAdd(taskId, value2); } internal static void UpdateClientTask(ulong taskId, int textIndex, AudioClip audioClip) { if (ClientTasks.TryGetValue(taskId, out var value) && (Object)(object)value._generatedClips[textIndex] == (Object)null) { value._generatedClips[textIndex] = audioClip; } } internal static void CancelClientTask(ulong taskId) { if (ClientTasks.TryRemove(taskId, out var value)) { value._cts.SafeCancel(); value._cts?.Dispose(); if (value._generatedClips != null) { Array.Clear(value._generatedClips, 0, value._generatedClips.Length); } } } internal static void SyncActiveAudioSourcesTo(ulong clientId) { if (!LNetworkUtils.IsHostOrServer) { return; } foreach (SpawnTTSAudioSource_NET value in ActiveAudioSources_Server.Values) { try { TTS_networkMessage_SpawnTTSAudioSource_Clients.SendClient(value, clientId); LogConstants.TTS_COMPANY_NETWORKING_SEND_AUDIO_SOURCES.Log("TTSCompanyNetworking", clientId); } catch (Exception ex) { LogConstants.CODE_GENERIC_EXCEPTION.Log("TTSCompanyNetworking", "SyncActiveAudioSourcesTo", ex); } } } internal static void HandlePlayerDisconnected(ulong clientId) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (!LNetworkUtils.IsHostOrServer) { return; } foreach (TTSTask value4 in ActiveTasks_Server.Values) { value4._snapshotClientIds.TryRemove(clientId, out var _); value4._completionList.TryRemove(clientId, out var _); } NetworkObject val = default(NetworkObject); foreach (KeyValuePair<(ulong, ulong), SpawnTTSAudioSource_NET> item in ActiveAudioSources_Server) { NetworkObjectReference networkObjectRefOfSpeaker = item.Value._networkObjectRefOfSpeaker; if (!((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null)) { ActiveAudioSources_Server.TryRemove(item.Key, out var _); } } } internal static void ClearClientTasks() { foreach (ClientTaskState value in ClientTasks.Values) { value._cts?.SafeCancel(); value._cts?.Dispose(); if (value._generatedClips == null) { continue; } for (int i = 0; i < value._generatedClips.Length; i++) { if ((Object)(object)value._generatedClips[i] != (Object)null) { Object.Destroy((Object)(object)value._generatedClips[i]); value._generatedClips[i] = null; } } } ClientTasks.Clear(); } internal static void ClearServerTasks() { if (!LNetworkUtils.IsHostOrServer) { return; } foreach (TTSTask value in ActiveTasks_Server.Values) { value._cts?.SafeCancel(); value._cts?.Dispose(); } ActiveTasks_Server.Clear(); ActiveAudioSources_Server.Clear(); } } } namespace TTSCompany.Components.Networking.Components { internal sealed class ClientTaskState { internal string[] _sentences; internal AudioClip[] _generatedClips; internal ulong _callingAssemblyHash; internal float[] _pauseDurations; internal CancellationTokenSource _cts; internal NetworkObjectReference _networkObjectReference; internal float _noiseRangeMultiplier; } internal sealed class TTSTask { internal ulong _taskId; internal ulong _callingAssemblyHash; internal string[] _textsToSpeak; internal PiperVoiceSettings _voiceSettings; internal NetworkObjectReference _speakingObject; internal ConcurrentDictionary _snapshotClientIds; internal ConcurrentDictionary _completionList; internal int _textsWaited; internal int _amountOfTexts; internal int _startSpeakingAtAmountOfFinishedTasks; internal int _lastStartSpeakingIndex; internal bool _cancelled; internal CancellationTokenSource _cts; internal TTSTask(ulong[] expectedClients, int amountOfTexts) { _amountOfTexts = amountOfTexts - 1; _startSpeakingAtAmountOfFinishedTasks = Mathf.CeilToInt(Mathf.Clamp((float)amountOfTexts * 0.35f, 0f, 3f)); Dictionary collection = expectedClients.ToDictionary((ulong id) => id, (ulong id) => false); _snapshotClientIds = new ConcurrentDictionary(collection); Dictionary collection2 = expectedClients.ToDictionary((ulong id) => id, (ulong _) => new bool[amountOfTexts]); _completionList = new ConcurrentDictionary(collection2); } } } namespace TTSCompany.Components.Networking.Components.Structs { internal readonly struct CancelAudioTTS_NET { [SerializeField] internal readonly ulong _taskId; [SerializeField] internal readonly string _reason; internal CancelAudioTTS_NET(ulong taskId, string reason) { _taskId = taskId; _reason = reason; } } internal readonly struct DespawnTTSAudioSource_NET { [SerializeField] internal readonly NetworkObjectReference _networkObjectRefOfSpeaker; [SerializeField] internal readonly ulong _callingAssemblyHash; internal DespawnTTSAudioSource_NET(NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _networkObjectRefOfSpeaker = networkObjectRefOfSpeaker; _callingAssemblyHash = callingAssemblyHash; } } internal readonly struct PlayAudioTTS_NET { [SerializeField] internal readonly ulong _taskId; [SerializeField] internal readonly int _startIndex; [SerializeField] internal readonly int _endIndex; [SerializeField] internal readonly bool _isLastBatch; internal PlayAudioTTS_NET(ulong taskId, int startIndex, int endIndex, bool isLastBatch) { _taskId = taskId; _startIndex = startIndex; _endIndex = endIndex; _isLastBatch = isLastBatch; } } internal readonly struct QueuedClip { internal readonly AudioClip Clip; internal readonly float PauseAfter; internal QueuedClip(AudioClip clip, float pauseAfter) { Clip = clip; PauseAfter = pauseAfter; } } internal readonly struct SentenceProgressData_NET { [SerializeField] internal readonly ulong _sessionId; [SerializeField] internal readonly int _textIndex; [SerializeField] internal readonly bool _success; internal SentenceProgressData_NET(ulong sessionId, int textIndex, bool success) { _sessionId = sessionId; _textIndex = textIndex; _success = success; } } internal sealed class SpeakTTSAudioClipCache { internal GameObject _foundNetworkObject; internal ulong _callingAssemblyHash; internal ConcurrentQueue _audioQueue = new ConcurrentQueue(); private readonly ConcurrentDictionary _knownClips = new ConcurrentDictionary(); internal readonly float _noiseRangeMultiplier; internal bool _isLastBatch; internal void MarkLastBatch() { _isLastBatch = true; } internal SpeakTTSAudioClipCache(GameObject foundNetworkObject, ulong callingAssemblyHash, AudioClip[] audioClips, float[] pauseDurations, float noiseRangeMultiplier) { _foundNetworkObject = foundNetworkObject; _callingAssemblyHash = callingAssemblyHash; _noiseRangeMultiplier = noiseRangeMultiplier; AddAudioClips(audioClips, pauseDurations); } internal void AddAudioClips(AudioClip[] audioClips, float[] pauseDurations) { for (int i = 0; i < audioClips.Length; i++) { AudioClip val = audioClips[i]; if (!((Object)(object)val == (Object)null) && _knownClips.TryAdd(val, value: false)) { float pauseAfter = ((pauseDurations != null && i < pauseDurations.Length) ? pauseDurations[i] : 0f); _audioQueue.Enqueue(new QueuedClip(val, pauseAfter)); } } } } internal readonly struct SpawnTTSAudioSource_NET { [SerializeField] internal readonly NetworkObjectReference _networkObjectRefOfSpeaker; [SerializeField] internal readonly ulong _callingAssemblyHash; [SerializeField] internal readonly TTSAudioSourceSettings _audioSourceSettings; internal SpawnTTSAudioSource_NET(NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash, TTSAudioSourceSettings audioSourceSettings) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _networkObjectRefOfSpeaker = networkObjectRefOfSpeaker; _callingAssemblyHash = callingAssemblyHash; _audioSourceSettings = audioSourceSettings; } } internal readonly struct TTSPregenerateTTS_NET { [SerializeField] internal readonly ulong _callingAssemblyHash; [SerializeField] internal readonly string[] _textsToSpeak; [SerializeField] internal readonly PiperVoiceSettings _voiceSettings; [SerializeField] internal readonly ulong _trackingKeyHash; internal TTSPregenerateTTS_NET(ulong callingAssemblyHash, string[] textToSpeak, PiperVoiceSettings voiceSettings, ulong trackingKeyHash) { _callingAssemblyHash = callingAssemblyHash; _textsToSpeak = textToSpeak; _voiceSettings = voiceSettings; _trackingKeyHash = trackingKeyHash; } } internal readonly struct TTSSpeakTTS_PLUS_NET { [SerializeField] internal readonly NetworkObjectReference _networkObjectRefOfSpeaker; [SerializeField] internal readonly ulong _callingAssemblyHash; [SerializeField] internal readonly string[] _textsToSpeak; [SerializeField] internal readonly PiperVoiceSettings _voiceSettings; [SerializeField] internal readonly ulong _trackingKeyHash; [SerializeField] internal readonly float _noiseRangeMultiplier; [SerializeField] internal readonly ulong _sessionId; internal TTSSpeakTTS_PLUS_NET(TTSSpeakTTS_NET nonLiteVersion, ulong sessionId) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) _networkObjectRefOfSpeaker = nonLiteVersion._networkObjectRefOfSpeaker; _textsToSpeak = nonLiteVersion._textsToSpeak; _voiceSettings = nonLiteVersion._voiceSettings; _trackingKeyHash = nonLiteVersion._trackingKeyHash; _callingAssemblyHash = nonLiteVersion._callingAssemblyHash; _noiseRangeMultiplier = nonLiteVersion._noiseRangeMultiplier; _sessionId = sessionId; } } internal readonly struct TTSSpeakTTS_NET { [SerializeField] internal readonly NetworkObjectReference _networkObjectRefOfSpeaker; [SerializeField] internal readonly ulong _callingAssemblyHash; [SerializeField] internal readonly string[] _textsToSpeak; [SerializeField] internal readonly PiperVoiceSettings _voiceSettings; [SerializeField] internal readonly ulong _trackingKeyHash; [SerializeField] internal readonly float _noiseRangeMultiplier; internal TTSSpeakTTS_NET(NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash, string[] textToSpeak, PiperVoiceSettings voiceSettings, ulong trackingKeyHash, float noiseRangeMultiplier) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _networkObjectRefOfSpeaker = networkObjectRefOfSpeaker; _callingAssemblyHash = callingAssemblyHash; _textsToSpeak = textToSpeak; _voiceSettings = voiceSettings; _trackingKeyHash = trackingKeyHash; _noiseRangeMultiplier = noiseRangeMultiplier; } } internal struct UpdateTTSAudioSourceSettings_NET { [SerializeField] internal NetworkObjectReference _networkObjectRefOfSpeaker; [SerializeField] internal ulong _callingAssemblyHash; [SerializeField] internal TTSAudioSourceSettings _audioSourceSettings; internal UpdateTTSAudioSourceSettings_NET(NetworkObjectReference networkObjectRefOfSpeaker, ulong callingAssemblyHash, TTSAudioSourceSettings audioSourceSettings) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _networkObjectRefOfSpeaker = networkObjectRefOfSpeaker; _callingAssemblyHash = callingAssemblyHash; _audioSourceSettings = audioSourceSettings; } } } namespace TTSCompany.Components.Managers { internal static class TTSAudioSourceManager { private static readonly ConditionalWeakTable GameObjectWithTTSAudioSourcesComponent = new ConditionalWeakTable(); private static bool DoesGameObjectHaveTTSAudioSourcesComponent(GameObject networkObject, out TTSAudioSourcesComponent audioSourceContainingGameObject) { if ((Object)(object)networkObject == (Object)null || !Object.op_Implicit((Object)(object)networkObject)) { audioSourceContainingGameObject = null; return false; } return GameObjectWithTTSAudioSourcesComponent.TryGetValue(networkObject, out audioSourceContainingGameObject); } internal static void AddPermanentTTSAudioSource(SpawnTTSAudioSource_NET data) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) NetworkObjectReference networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker; NetworkObject val = default(NetworkObject); if (!((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null)) { LogConstants.API_NETWORK_OBJECT_NOT_FOUND.Log("TTSCompanyAPI", data._networkObjectRefOfSpeaker); } else { AddPermanentTTSAudioSource(((Component)val).gameObject, data._callingAssemblyHash, data._audioSourceSettings); } } internal static bool AddPermanentTTSAudioSource(GameObject networkObject, ulong callingAssemblyHash, TTSAudioSourceSettings audioSourceSettings) { LogConstants.CODE_TRIGGERED.Log("TTSAudioSourceManager", "AddPermanentTTSAudioSource"); if ((Object)(object)networkObject == (Object)null) { LogConstants.CODE_INPUT_VARIABLES_INVALID.Log("TTSAudioSourceManager", "AddPermanentTTSAudioSource", 1); return false; } if (!DoesGameObjectHaveTTSAudioSourcesComponent(networkObject, out var audioSourceContainingGameObject)) { audioSourceContainingGameObject = networkObject.AddComponent(); GameObjectWithTTSAudioSourcesComponent.Add(networkObject, audioSourceContainingGameObject); AddAudioSource(audioSourceContainingGameObject, callingAssemblyHash, audioSourceSettings); return true; } if (!audioSourceContainingGameObject.DoesAudioSourceExist(callingAssemblyHash)) { AddAudioSource(audioSourceContainingGameObject, callingAssemblyHash, audioSourceSettings); return true; } return false; } internal static void RemovePermanentTTSAudioSource(DespawnTTSAudioSource_NET data) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) NetworkObjectReference networkObjectRefOfSpeaker = data._networkObjectRefOfSpeaker; NetworkObject val = default(NetworkObject); if (!((NetworkObjectReference)(ref networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null)) { LogConstants.API_NETWORK_OBJECT_NOT_FOUND.Log("TTSCompanyAPI", data._networkObjectRefOfSpeaker); } else { RemovePermanentTTSAudioSource(((Component)val).gameObject, data._callingAssemblyHash); } } internal static bool RemovePermanentTTSAudioSource(GameObject networkObject, ulong callingAssemblyHash) { LogConstants.CODE_TRIGGERED.Log("TTSAudioSourceManager", "RemovePermanentTTSAudioSource"); if ((Object)(object)networkObject == (Object)null) { LogConstants.CODE_INPUT_VARIABLES_INVALID.Log("TTSAudioSourceManager", "RemovePermanentTTSAudioSource", 1); return false; } if (DoesGameObjectHaveTTSAudioSourcesComponent(networkObject, out var audioSourceContainingGameObject)) { return audioSourceContainingGameObject.RemoveAudioSource(callingAssemblyHash); } return false; } internal static bool PlayAudioSource(GameObject networkObject, ulong callingAssemblyHash, AudioClip audioClipToPlay, float noiseRangeMultiplier) { LogConstants.CODE_TRIGGERED.Log("TTSAudioSourceManager", "PlayAudioSource"); if ((Object)(object)networkObject == (Object)null || (Object)(object)audioClipToPlay == (Object)null) { LogConstants.CODE_INPUT_VARIABLES_INVALID.Log("TTSAudioSourceManager", "PlayAudioSource", 1); return false; } if (!DoesGameObjectHaveTTSAudioSourcesComponent(networkObject, out var audioSourceContainingGameObject)) { LogConstants.TTS_AUDIO_SOURCE_MANAGER_FAIL_PLAYING_NO_AUDIO_SOURCE.Log("TTSAudioSourceManager", "PlayAudioSource", ((Object)networkObject).name); return false; } return audioSourceContainingGameObject.PlayAudioClip(callingAssemblyHash, audioClipToPlay, noiseRangeMultiplier); } internal static bool StopAudioSource(GameObject networkObject, ulong callingAssemblyHash) { if (!DoesGameObjectHaveTTSAudioSourcesComponent(networkObject, out var audioSourceContainingGameObject)) { return false; } return audioSourceContainingGameObject.StopAudioClip(callingAssemblyHash); } private static void AddAudioSource(TTSAudioSourcesComponent audioSourceContainingGameObject, ulong callingAssemblyHash, TTSAudioSourceSettings audioSourceSettings) { if (audioSourceContainingGameObject.AddAudioSource(callingAssemblyHash)) { LogConstants.TTS_AUDIO_SOURCE_MANAGER_AUDIO_SOURCE_ADDED.Log("TTSAudioSourceManager", callingAssemblyHash); audioSourceContainingGameObject.UpdateAudioSourceSettings(callingAssemblyHash, audioSourceSettings); } } internal static void UpdateTTSAudioSourceSettings(UpdateTTSAudioSourceSettings_NET data) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) NetworkObject val = default(NetworkObject); if (!((NetworkObjectReference)(ref data._networkObjectRefOfSpeaker)).TryGet(ref val, (NetworkManager)null)) { LogConstants.API_NETWORK_OBJECT_NOT_FOUND.Log("TTSCompanyAPI", data._networkObjectRefOfSpeaker); } else { UpdateTTSAudioSourceSettings(((Component)val).gameObject, data._callingAssemblyHash, data._audioSourceSettings); } } internal static bool UpdateTTSAudioSourceSettings(GameObject networkObject, ulong callingAssemblyHash, TTSAudioSourceSettings audioSourceSettings) { if ((Object)(object)networkObject == (Object)null || audioSourceSettings == null) { LogConstants.CODE_INPUT_VARIABLES_INVALID.Log("TTSAudioSourceManager", "UpdateTTSAudioSourceSettings", 1); return false; } if (!DoesGameObjectHaveTTSAudioSourcesComponent(networkObject, out var audioSourceContainingGameObject)) { LogConstants.CODE_INPUT_VARIABLES_INVALID.Log("TTSAudioSourceManager", "UpdateTTSAudioSourceSettings", 2); return false; } return audioSourceContainingGameObject.UpdateAudioSourceSettings(callingAssemblyHash, audioSourceSettings); } } } namespace TTSCompany.Components.Managers.Components { internal sealed class TTSAudioSourcesComponent : MonoBehaviour { private readonly ConcurrentDictionary audioSources = new ConcurrentDictionary(); private readonly ConcurrentDictionary noiseLoopCoroutines = new ConcurrentDictionary(); private const float RmsScaler = 2.9f; private const float LoudnessScaler = 2f; internal bool AddAudioSource(ulong callingAssemblyHash) { if (!DoesAudioSourceExist(callingAssemblyHash)) { AudioSource value = ((Component)this).gameObject.AddComponent(); return audioSources.TryAdd(callingAssemblyHash, value); } return false; } internal bool DoesAudioSourceExist(ulong callingAssemblyHash) { return audioSources.ContainsKey(callingAssemblyHash); } internal bool GetAudioSource(ulong callingAssemblyHash, out AudioSource source) { return audioSources.TryGetValue(callingAssemblyHash, out source); } internal bool PlayAudioClip(ulong callingAssemblyHash, AudioClip newAudioClip, float noiseRangeMultiplier) { LogConstants.CODE_TRIGGERED.Log("TTSAudioSourcesComponent", "PlayAudioClip"); if ((Object)(object)newAudioClip == (Object)null) { return false; } if (!GetAudioSource(callingAssemblyHash, out var source)) { return false; } AudioClip clip = source.clip; source.clip = newAudioClip; source.Play(); StopNoiseLoop(callingAssemblyHash); Coroutine value = ((MonoBehaviour)this).StartCoroutine(EmitNoise(callingAssemblyHash, source, noiseRangeMultiplier)); noiseLoopCoroutines[callingAssemblyHash] = value; if ((Object)(object)clip != (Object)null && (Object)(object)clip != (Object)(object)newAudioClip) { Object.Destroy((Object)(object)clip); } return true; } private IEnumerator EmitNoise(ulong callingAssemblyHash, AudioSource audioSource, float noiseRangeMultiplier) { LogConstants.CODE_TRIGGERED.Log("TTSAudioSourcesComponent", "EmitNoise"); if (noiseRangeMultiplier <= 0f) { yield break; } LogConstants.CODE_TRIGGERED.Log("TTSAudioSourcesComponent", "EmitNoise after"); float[] TTSAudioBuffer = new float[512]; float maxRmsSnapshot = 0f; byte loopsMade = 0; WaitForSeconds wait = new WaitForSeconds(0.1f); while ((Object)(object)audioSource != (Object)null && audioSource.isPlaying) { audioSource.GetOutputData(TTSAudioBuffer, 0); float num = 0f; for (int i = 0; i < TTSAudioBuffer.Length; i++) { num += TTSAudioBuffer[i] * TTSAudioBuffer[i]; } float num2 = Mathf.Sqrt(num / (float)TTSAudioBuffer.Length) * 2.9f; if (num2 > maxRmsSnapshot) { maxRmsSnapshot = num2; } if (loopsMade >= 2) { loopsMade = 0; float num3 = Mathf.Clamp01(maxRmsSnapshot * audioSource.volume * 2.9f); if (num3 > 0.01f && (Object)(object)RoundManager.Instance != (Object)null) { float num4 = Mathf.Lerp(audioSource.minDistance, audioSource.maxDistance, num3); float num5 = Mathf.Clamp(num3 * 2f, 0.6f, 0.9f); bool flag = false; LogConstants.TTS_AUDIO_SOURCE_COMPONENT_NOISE_LEVEL.Log("TTSAudioSourcesComponent", num4, num5); RoundManager.Instance.PlayAudibleNoise(((Component)this).transform.position, num4, num5, 0, flag, 75); } maxRmsSnapshot = 0f; } else { loopsMade++; } yield return wait; } noiseLoopCoroutines.TryRemove(callingAssemblyHash, out var _); } private void StopNoiseLoop(ulong callingAssemblyHash) { if (noiseLoopCoroutines.TryRemove(callingAssemblyHash, out var value) && value != null) { ((MonoBehaviour)this).StopCoroutine(value); } } internal bool StopAudioClip(ulong callingAssemblyHash) { if (!GetAudioSource(callingAssemblyHash, out var source)) { return false; } StopNoiseLoop(callingAssemblyHash); source.Stop(); if ((Object)(object)source.clip != (Object)null) { Object.Destroy((Object)(object)source.clip); } source.clip = null; return true; } internal bool UpdateAudioSourceSettings(ulong callingAssemblyHash, TTSAudioSourceSettings audioSourceSettings) { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) if (!GetAudioSource(callingAssemblyHash, out var source)) { return false; } source.spatialize = false; source.spatializePostEffects = false; source.bypassEffects = audioSourceSettings.BypassEffects; source.bypassListenerEffects = audioSourceSettings.BypassListenerEffects; source.bypassReverbZones = audioSourceSettings.BypassReverbZones; source.playOnAwake = false; source.loop = false; source.priority = audioSourceSettings.Priority; source.volume = audioSourceSettings.Volume; source.spatialBlend = audioSourceSettings.SpatialBlend; source.reverbZoneMix = audioSourceSettings.ReverbZoneMix; source.dopplerLevel = audioSourceSettings.DopplerLevel; source.minDistance = audioSourceSettings.MinDistance; source.maxDistance = audioSourceSettings.MaxDistance; source.rolloffMode = audioSourceSettings.RolloffMode; source.outputAudioMixerGroup = audioSourceSettings.OutputAudioMixerGroup; if (audioSourceSettings.CustomCurve.HasValue) { (AudioSourceCurveType, AnimationCurve) value = audioSourceSettings.CustomCurve.Value; source.SetCustomCurve(value.Item1, value.Item2); } return true; } internal bool RemoveAudioSource(ulong callingAssemblyHash) { StopNoiseLoop(callingAssemblyHash); if (audioSources.TryRemove(callingAssemblyHash, out var value)) { Object.Destroy((Object)(object)value); return true; } return false; } private void OnDestroy() { foreach (Coroutine value in noiseLoopCoroutines.Values) { if (value != null) { ((MonoBehaviour)this).StopCoroutine(value); } } noiseLoopCoroutines.Clear(); foreach (AudioSource value2 in audioSources.Values) { if ((Object)(object)value2 != (Object)null) { Object.Destroy((Object)(object)value2); } } audioSources.Clear(); } } } namespace TTSCompany.Components.Helpers { internal static class ClampHelper { internal static float ClampAndRound(float value, float min, float max) { return (float)Math.Round(Mathf.Clamp(value, min, max), 2, MidpointRounding.AwayFromZero); } } internal static class CtsHelper { internal static void SafeCancel(this CancellationTokenSource cts) { if (cts == null) { return; } try { cts.Cancel(); } catch (ObjectDisposedException) { LogConstants.CODE_GENERIC_CATCH.Log("CtsHelper", "SafeCancel"); } } } internal static class HashHelper { private const string GlobalCallerName = "TTSCompanyGlobalAudioSourceCaller"; internal static readonly ulong GlobalCallerHash = CalculateGlobalCallerHash(); private const ulong Prime = 1099511628211uL; private const ulong OffsetBasis = 14695981039346656037uL; private static readonly ConcurrentDictionary _assemblyHashCache = new ConcurrentDictionary(); internal static ulong GetTrackingKeyHash(ulong networkObjectId, Assembly callingAssembly) { ulong hash = _assemblyHashCache.GetOrAdd(callingAssembly, (Assembly asm) => GetCallingAssemblyHash(asm)); CombineULong(ref hash, networkObjectId); return hash; } internal static ulong GetTrackingKeyHash(ulong networkObjectId) { ulong hash = GlobalCallerHash; CombineULong(ref hash, networkObjectId); return hash; } internal static ulong GetTrackingKeyHash(string textToSpeak, PiperVoiceSettings settings) { ulong hash = 14695981039346656037uL; CombineString(ref hash, textToSpeak); if (settings != null) { CombineString(ref hash, settings.ModelName); CombineFloat(ref hash, settings.SpeechRate); CombineFloat(ref hash, settings.NoiseScale); CombineFloat(ref hash, settings.NoiseScaleW); CombineFloat(ref hash, settings.SentenceSilence); } return hash; } internal static ulong GetCallingAssemblyHash(Assembly callingAssembly) { ulong hash = 14695981039346656037uL; CombineString(ref hash, callingAssembly.GetName().Name); return hash; } internal static string GetHashTTSFileNameWithFileType(string textToSpeak, PiperVoiceSettings settings) { ulong hash = 14695981039346656037uL; CombineString(ref hash, textToSpeak); if (settings != null) { CombineString(ref hash, settings.ModelName); CombineFloat(ref hash, settings.SpeechRate); CombineFloat(ref hash, settings.NoiseScale); CombineFloat(ref hash, settings.NoiseScaleW); } return $"{hash:X16}.ogg"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void CombineString(ref ulong hash, string value) { if (!string.IsNullOrEmpty(value)) { foreach (uint num in value) { hash = (hash ^ (num & 0xFF)) * 1099511628211L; hash = (hash ^ (num >> 8)) * 1099511628211L; } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void CombineFloat(ref ulong hash, float value) { uint num = Unsafe.As(ref value); hash = (hash ^ (num & 0xFF)) * 1099511628211L; hash = (hash ^ ((num >> 8) & 0xFF)) * 1099511628211L; hash = (hash ^ ((num >> 16) & 0xFF)) * 1099511628211L; hash = (hash ^ (num >> 24)) * 1099511628211L; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void CombineULong(ref ulong hash, ulong value) { hash = (hash ^ (value & 0xFF)) * 1099511628211L; hash = (hash ^ ((value >> 8) & 0xFF)) * 1099511628211L; hash = (hash ^ ((value >> 16) & 0xFF)) * 1099511628211L; hash = (hash ^ ((value >> 24) & 0xFF)) * 1099511628211L; hash = (hash ^ ((value >> 32) & 0xFF)) * 1099511628211L; hash = (hash ^ ((value >> 40) & 0xFF)) * 1099511628211L; hash = (hash ^ ((value >> 48) & 0xFF)) * 1099511628211L; hash = (hash ^ (value >> 56)) * 1099511628211L; } private static ulong CalculateGlobalCallerHash() { ulong hash = 14695981039346656037uL; CombineString(ref hash, "TTSCompanyGlobalAudioSourceCaller"); return hash; } } internal static class TTSTimeoutHelper { private const float baseWordsPerSecond = 2.5f; internal static TimeSpan GetPlaybackTimeout(string[] textsToSpeak, PiperVoiceSettings settings) { TimeSpan timeSpan = TimeSpan.FromSeconds(4.0); if (textsToSpeak == null || textsToSpeak.Length == 0) { return timeSpan; } var (num, num2) = TTSCompanyUtils.GetTextToSpeakInfo(textsToSpeak); if (num == 0) { return timeSpan; } float num3 = ((settings.SpeechRate > 0.05f) ? settings.SpeechRate : 1f); float num4 = (float)num / 2.5f / num3; float num5 = (float)num2 * settings.SentenceSilence; TimeSpan timeSpan2 = TimeSpan.FromSeconds(num4 + num5 + TTSConstants.TTS_PLAYBACK_TIMEOUT_BUFFER_SECONDS_SCALED); if (!(timeSpan2 > timeSpan)) { return timeSpan; } return timeSpan2; } internal static TimeSpan GetGenerationTimeout(string[] textsToSpeak, PiperVoiceSettings settings) { TimeSpan timeSpan = TimeSpan.FromSeconds(4.0); if (textsToSpeak == null || textsToSpeak.Length == 0) { return timeSpan; } int item = TTSCompanyUtils.GetTextToSpeakInfo(textsToSpeak).TotalWordCount; if (item == 0) { return timeSpan; } float num = ((settings.SpeechRate > 0.05f) ? settings.SpeechRate : 1f); float num2 = (float)item / 2.5f / num * TTSConstants.GetGenerationDurationScaling(); float num3 = (float)item * TTSConstants.TTS_TIMEOUT_PER_WORD_BUFFER_SCALED; TimeSpan timeSpan2 = TimeSpan.FromSeconds(num2 + num3 + TTSConstants.TTS_TIMEOUT_BASE_BUFFER_SCALED); if (!(timeSpan2 > timeSpan)) { return timeSpan; } return timeSpan2; } } internal static class VoiceHelper { internal static string CleanupVoiceModelname(string voiceModelname) { string text = ((voiceModelname != null && voiceModelname.EndsWith(".onnx", StringComparison.OrdinalIgnoreCase)) ? voiceModelname.Substring(0, voiceModelname.Length - 5) : voiceModelname); if (text != null) { return text; } return string.Empty; } } internal static class FolderHelper { internal static bool CheckForPiperTTS() { return File.Exists(TTSConstants.PIPER_EXECUTABLE_LOCATION); } internal static bool CheckForDefaultVoiceModels() { return Directory.Exists(TTSConstants.TTS_DEFAULT_VOICE_MODELS_FOLDER_LOCATION); } } } namespace TTSCompany.Components.Enums { internal enum TimeoutBufferScaling : byte { Low, Normal, High, Max } internal enum TTSGenPriority : byte { VeryLow, Low, Normal, High, Max } public enum TTSNetworkObjectState : byte { Invalid, Idle, GeneratingTTS, ActivelySpeaking } } namespace TTSCompany.Components.Constants { internal static class APIDefaultsConstants { internal const bool USE_GLOBAL_AUDIO_SOURCE_DEFAULT = true; internal const float NOISE_RANGE_MULTIPLIER_DEFAULT = 0f; internal const PiperVoiceSettings PIPER_VOICE_SETTING_DEFAULT = null; internal const TTSAudioSourceSettings TTS_AUDIO_SOURCE_SETTING_DEFAULT = null; } internal static class ExampleConstants { internal const string VOICE_MODEL_NAME = "en_US-hfc_female-medium"; } internal static class OggConstants { internal const int OGG_BITRATE = 16000; internal const int OGG_SAMPLE_RATE = 24000; internal const int OGG_CHANNELS_AMOUNT = 1; } internal static class LogConstants { internal readonly struct LogMessage { internal string Message { get; } internal LogLevel Level { get; } internal LogMessage(LogLevel level, string message) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) Level = level; Message = message; } internal void Log(params object[] args) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) logSource.Log(Level, (object)string.Format("{0} | " + Message, args)); } } internal static ManualLogSource logSource = Logger.CreateLogSource("PixelIndieDev_TTSCompany"); private const string DEFAULT_ERROR_LOG_PREFIX = "{0} | "; internal static readonly LogMessage CODE_TRIGGERED = new LogMessage((LogLevel)32, "{1} was triggered"); internal static readonly LogMessage CODE_INPUT_VARIABLES_INVALID = new LogMessage((LogLevel)32, "In {1}, check {2} found invalid variables"); internal static readonly LogMessage CODE_NEW_VALUE_SET = new LogMessage((LogLevel)32, "New value for {1} = {2}"); internal static readonly LogMessage CODE_GENERIC_CANCELLED = new LogMessage((LogLevel)32, "{1} was cancelled"); internal static readonly LogMessage CODE_GENERIC_EXCEPTION = new LogMessage((LogLevel)2, "{1} got exception: {2}"); internal static readonly LogMessage CODE_GENERIC_ERROR = new LogMessage((LogLevel)2, "{1} got error: {2}"); internal static readonly LogMessage CODE_GENERIC_CATCH = new LogMessage((LogLevel)4, "{1} was catched"); internal static readonly LogMessage CODE_GENERIC_FAIL = new LogMessage((LogLevel)2, "{1} failed with error: {2}"); internal static readonly LogMessage TTS_TIMEOUT_HELPER_TIMEOUT_INFO = new LogMessage((LogLevel)32, "textToSpeak = {1}, totalTimeoutInSeconds = {2}"); internal static readonly LogMessage TTS_GENERATOR_TTS_CANCELLED = new LogMessage((LogLevel)16, "TTS got cancelled for text: '{1}' with hash: '{2}'"); internal static readonly LogMessage TTS_GENERATOR_UNZIP_FAILED = new LogMessage((LogLevel)1, "{1} not found"); internal static readonly LogMessage TTS_GENERATOR_PROCESS_FAILED_TO_STOP = new LogMessage((LogLevel)2, "Process {1} failed to exit"); internal static readonly LogMessage TTS_GENERATOR_FAILED_TO_DELETE_0KB_CACHE = new LogMessage((LogLevel)2, "Failed to delete 0KB cache file with hash: '{1}' - {2}"); internal static readonly LogMessage TTS_GENERATOR_FFMPEG_EXITED_PREMATURE = new LogMessage((LogLevel)1, "FFmpeg exited prematurely"); internal static readonly LogMessage TTS_GENERATOR_NO_CACHED_AUDIO_FOUND = new LogMessage((LogLevel)2, "Cached audio file with hash: '{1}' not found at: {2}"); internal static readonly LogMessage TTS_GENERATOR_ARGUMENT_OUT_OF_RANGE_EX = new LogMessage((LogLevel)1, "MaxConcurrentRequests value must be at least 1"); internal static readonly LogMessage TTS_GENERATOR_FOUND_CACHED_TTS = new LogMessage((LogLevel)32, "Found {1} in cache"); internal static readonly LogMessage TTS_GENERATOR_GENERATING_TTS = new LogMessage((LogLevel)32, "Generating TTS for text: '{1}' with hash: '{2}'"); internal static readonly LogMessage TTS_GENERATOR_RUN_PIPER_ARGUMENTS = new LogMessage((LogLevel)32, "Arguments for {1} are: {2}"); internal static readonly LogMessage TTS_GENERATOR_DELETE_0KB_CACHE = new LogMessage((LogLevel)32, "Deleting 0KB cache file with hash: '{1}'"); internal static readonly LogMessage PLUGIN_LOADED = new LogMessage((LogLevel)16, "{1} + (version - {2}) : loaded successfully"); internal static readonly LogMessage PLUGIN_ON_QUIT = new LogMessage((LogLevel)16, "Game wants to quit, stopping background processes of {1} + (version - {2})"); internal static readonly LogMessage PLUGIN_TTS_COULD_NOT_BE_INITIALIZED = new LogMessage((LogLevel)1, "{1} could not be initialized"); internal static readonly LogMessage API_NETWORK_OBJECT_NOT_FOUND = new LogMessage((LogLevel)4, "NetworkObject {1} not found"); internal static readonly LogMessage API_TRIGGER_PRELOAD_VOICE_MODEL = new LogMessage((LogLevel)32, "Started preloading voice model: {1}"); internal static readonly LogMessage API_TRIGGER_UNLOAD_VOICE_MODEL = new LogMessage((LogLevel)32, "Started unloading voice model: {1}"); internal static readonly LogMessage UTILS_TIMEOUT_TIME_GENERATION = new LogMessage((LogLevel)32, "The text input '{1}' has {2} words and {3} sentences"); internal static readonly LogMessage PIPER_TTS_SERVER_SUCCESS_STARTUP = new LogMessage((LogLevel)16, "Started piper tts server on port {1} (pid {2})"); internal static readonly LogMessage PIPER_TTS_SERVER_STOPPED = new LogMessage((LogLevel)16, "Stopped piper tts server"); internal static readonly LogMessage PIPER_TTS_LOADED_VOICE_MODEL = new LogMessage((LogLevel)16, "Loaded voice model '{1}' using CPU"); internal static readonly LogMessage PIPER_TTS_UNLOADED_VOICE_MODEL = new LogMessage((LogLevel)16, "Unloaded voice model '{1}'"); internal static readonly LogMessage PIPER_TTS_FAILED_LOADING_VOICE_MODEL = new LogMessage((LogLevel)4, "Voice model '{1}' could not be loaded"); internal static readonly LogMessage PIPER_TTS_FAILED_UNLOADING_VOICE_MODEL = new LogMessage((LogLevel)4, "Voice model '{1}' could not be unloaded"); internal static readonly LogMessage PIPER_TTS_SERVER_EXE_NOT_FOUND = new LogMessage((LogLevel)1, "Server executable not found at: {1}"); internal static readonly LogMessage PIPER_TTS_SERVER_FAILED_TO_START = new LogMessage((LogLevel)1, "Failed to start piper tts server process with exception: {1}"); internal static readonly LogMessage PIPER_TTS_SERVER_VOICE_FOLDER_NOT_FOUND = new LogMessage((LogLevel)1, "Voice model directory not found at: {1}"); internal static readonly LogMessage PIPER_TTS_SERVER_STARTUP_ISSUE = new LogMessage((LogLevel)1, "Server process exited during startup (exit code {1} | stderr: {2})"); internal static readonly LogMessage PIPER_TTS_SERVER_OUTPUT_DRAIN = new LogMessage((LogLevel)4, "Piper tts server drain: {1}"); internal static readonly LogMessage PIPER_TTS_VOICE_MODEL_NOT_LOADED = new LogMessage((LogLevel)4, "Voice model '{1}' was not loaded beforehand as it has 0 assemblies that want it"); internal static readonly LogMessage PIPER_TTS_RELOADED_VOICE_MODEL = new LogMessage((LogLevel)32, "Reloaded voice model '{1}' using CPU"); internal static readonly LogMessage VOICE_MODEL_MEM_MANAGER_POOL_LIMIT_REACHED = new LogMessage((LogLevel)16, "Memory pool limit reached, unloaded {1}"); internal static readonly LogMessage VOICE_MODEL_MEM_MANAGER_NO_MODEL_TO_EVICT = new LogMessage((LogLevel)2, "Memory pool exceeded, but no models available to evict for {1}"); internal static readonly LogMessage VOICE_MODEL_MEM_MANAGER_FOUND_VOICE_MODEL_WITH_SIZE = new LogMessage((LogLevel)32, "Found voice model named: '{1}' with file size: '{2}'"); internal static readonly LogMessage TTS_AUDIO_SOURCE_MANAGER_FAIL_PLAYING_NO_AUDIO_SOURCE = new LogMessage((LogLevel)4, "{1} failed as no audio source was found on {2}"); internal static readonly LogMessage TTS_AUDIO_SOURCE_MANAGER_AUDIO_SOURCE_ADDED = new LogMessage((LogLevel)32, "Added audio source for caller with hash: {1}"); internal static readonly LogMessage TTS_AUDIO_SOURCE_COMPONENT_NOISE_LEVEL = new LogMessage((LogLevel)32, "TTS voice made a noise with range: {1} | loudness: {2}"); internal static readonly LogMessage TTS_COMPANY_NETWORKING_TASK_CANCELLED = new LogMessage((LogLevel)4, "Host cancelled session {1} with reason: {2}"); internal static readonly LogMessage TTS_COMPANY_NETWORKING_UPDATE_TASK = new LogMessage((LogLevel)32, "Player {1} send a task update for task {2}"); internal static readonly LogMessage TTS_COMPANY_NETWORKING_PLAYBACK_CLEANUP = new LogMessage((LogLevel)32, "Playback timeout cleaned up task {1}"); internal static readonly LogMessage TTS_COMPANY_NETWORKING_SEND_AUDIO_SOURCES = new LogMessage((LogLevel)32, "Send the audio sources to player with id: {1}"); } internal static class TTSConstants { internal static readonly string TTS_COMPANY_EXECUTABLE_LOCATION = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); internal const string PIPER_EXE_NAME = "piper-server.exe"; internal static readonly string PIPER_FOLDER_LOCATION = Path.Combine(TTS_COMPANY_EXECUTABLE_LOCATION, "PiperTTS"); internal static readonly string PIPER_EXECUTABLE_LOCATION = Path.Combine(PIPER_FOLDER_LOCATION, "piper-server.exe"); internal const int PIPER_SERVER_STARTUP_TIMEOUT_MS = 15000; internal const int PIPER_SERVER_REQUEST_TIMEOUT_MS = 30000; internal const int PIPER_SERVER_SHUTDOWN_TIMEOUT_MS = 2000; internal const string TTS_VOICE_MODELS_FOLDER = "TTS-Company-Voices"; internal static readonly string TTS_DEFAULT_VOICE_MODELS_FOLDER_LOCATION = Path.Combine(TTS_COMPANY_EXECUTABLE_LOCATION, "TTS-Company-Voices"); private const string TTS_VOICE_CACHE_SOUNDCLIPS_FOLDER = "TTS-Company-Voices-Cache"; internal static readonly string TTS_VOICE_CACHE_SOUNDCLIPS_PATH = Path.Combine(TTS_COMPANY_EXECUTABLE_LOCATION, "TTS-Company-Voices-Cache"); internal const float TTS_START_SPEAKING_AT_MULTIPLIER = 0.35f; internal const int TTS_MINIMUM_START_INDEX = 0; internal const int TTS_MAXIMUM_START_INDEX = 3; internal const float TTS_TIMEOUT_MINIMUM_TIME = 4f; private const float TTS_TIMEOUT_BASE_BUFFER = 4f; private const float TTS_TIMEOUT_PER_WORD_BUFFER = 0.05f; private const float TTS_PLAYBACK_TIMEOUT_BUFFER = 1.4f; internal static float TTS_TIMEOUT_BASE_BUFFER_SCALED = 0f; internal static float TTS_TIMEOUT_PER_WORD_BUFFER_SCALED = 0f; internal static float TTS_PLAYBACK_TIMEOUT_BUFFER_SECONDS_SCALED = 0f; internal const string TTS_SERVER_UNAVAILABLE = "TTS server is not available"; internal const string TTS_VOICE_MODEL_NAME_EMPTY = "voice model name must not be empty"; internal const string TTS_VALI_TEXT_TO_SPEAK = "TTS text cannot be empty"; internal const string TTS_VALI_SETTINGS = "PiperVoiceSettings cannot be NULL"; internal const string TTS_VALI_MODEL_NAME = "Voice model name must be set in settings"; internal const string TTS_VALI_MODEL_INVALID = "Piper voice model not found or valid"; internal const string TTS_VALI_SPEECH_RATE = "Speech rate must be > 0"; internal const string TTS_MEM_MANAGER_UNKNOWN_ASSEMBLY = "Could not determine calling assembly"; internal const string TTS_MEM_MANAGER_UNKNOWN_MODEL_LOCATION = "Voice model file location value not found"; internal const string DEBUG_AUDIOSOURCE_NAME = "DEBUG_KEYBIND"; internal static void UpdateTimeoutBuffers() { TTS_TIMEOUT_BASE_BUFFER_SCALED = 4f * GetTimeoutBufferScaling(isBase: true); TTS_TIMEOUT_PER_WORD_BUFFER_SCALED = 0.05f * GetTimeoutBufferScaling(isBase: false); TTS_PLAYBACK_TIMEOUT_BUFFER_SECONDS_SCALED = 1.4f * GetTimeoutBufferScaling(isBase: true); } private static float GetTimeoutBufferScaling(bool isBase) { switch (TTSCompanyPlugin.configEntryTimeoutBuffer.Value) { case TimeoutBufferScaling.Low: if (!isBase) { return 0.95f; } return 0.6f; default: if (!isBase) { return 1f; } return 1f; case TimeoutBufferScaling.High: if (!isBase) { return 1.1f; } return 1.4f; case TimeoutBufferScaling.Max: if (!isBase) { return 1.2f; } return 1.8f; } } internal static float GetGenerationDurationScaling() { return TTSCompanyPlugin.configEntryTimeoutBuffer.Value switch { TimeoutBufferScaling.Low => 0.95f, TimeoutBufferScaling.High => 1.4f, TimeoutBufferScaling.Max => 2f, _ => 1f, }; } } }