Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of VoiceRecognitionAPI v1.0.0
plugins/DumbassStuff.VoiceCommands.dll
Decompiled 10 months agousing System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Logging; using Dissonance; using Dissonance.Audio.Capture; using HarmonyLib; using LethalVoiceCommands.API; using LethalVoiceCommands.Voice; using Microsoft.CodeAnalysis; using NAudio.Wave; using Newtonsoft.Json.Linq; using UnityEngine; using Vosk; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("AmazingAssets.TerrainToMesh")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp-firstpass")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: IgnoresAccessChecksTo("ClientNetworkTransform")] [assembly: IgnoresAccessChecksTo("com.olegknyazev.softmask")] [assembly: IgnoresAccessChecksTo("DissonanceVoip")] [assembly: IgnoresAccessChecksTo("EasyTextEffects")] [assembly: IgnoresAccessChecksTo("Facepunch Transport for Netcode for GameObjects")] [assembly: IgnoresAccessChecksTo("Facepunch.Steamworks.Win64")] [assembly: IgnoresAccessChecksTo("Unity.AI.Navigation")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging")] [assembly: IgnoresAccessChecksTo("Unity.Animation.Rigging.DocCodeExamples")] [assembly: IgnoresAccessChecksTo("Unity.Burst")] [assembly: IgnoresAccessChecksTo("Unity.Burst.Unsafe")] [assembly: IgnoresAccessChecksTo("Unity.Collections")] [assembly: IgnoresAccessChecksTo("Unity.Collections.LowLevel.ILSupport")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem")] [assembly: IgnoresAccessChecksTo("Unity.InputSystem.ForUI")] [assembly: IgnoresAccessChecksTo("Unity.Jobs")] [assembly: IgnoresAccessChecksTo("Unity.Mathematics")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.Common")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.MetricTypes")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStats")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Component")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsMonitor.Implementation")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetStatsReporting")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkProfiler.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Multiplayer.Tools.NetworkSolutionInterface")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Components")] [assembly: IgnoresAccessChecksTo("Unity.Netcode.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.Networking.Transport")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Csg")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.KdTree")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Poly2Tri")] [assembly: IgnoresAccessChecksTo("Unity.ProBuilder.Stl")] [assembly: IgnoresAccessChecksTo("Unity.Profiling.Core")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.Core.ShaderLibrary")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Config.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.HighDefinition.Runtime")] [assembly: IgnoresAccessChecksTo("Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary")] [assembly: IgnoresAccessChecksTo("Unity.Services.Authentication")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Analytics")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Components")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Configuration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Device")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Environments.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Internal")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Networking")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Registration")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Scheduler")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Telemetry")] [assembly: IgnoresAccessChecksTo("Unity.Services.Core.Threading")] [assembly: IgnoresAccessChecksTo("Unity.Services.QoS")] [assembly: IgnoresAccessChecksTo("Unity.Services.Relay")] [assembly: IgnoresAccessChecksTo("Unity.TextMeshPro")] [assembly: IgnoresAccessChecksTo("Unity.Timeline")] [assembly: IgnoresAccessChecksTo("Unity.VisualEffectGraph.Runtime")] [assembly: IgnoresAccessChecksTo("UnityEngine.ARModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.NVIDIAModule")] [assembly: IgnoresAccessChecksTo("UnityEngine.UI")] [assembly: AssemblyCompany("DumbassStuff.VoiceCommands")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LethalVoiceCommands")] [assembly: AssemblyTitle("DumbassStuff.VoiceCommands")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LethalVoiceCommands { [BepInPlugin("DumbassStuff.VoiceCommands", "LethalVoiceCommands", "1.0.0")] public class LethalVoiceCommands : BaseUnityPlugin { [HarmonyPatch(typeof(StartOfRound), "Start")] internal static class Patch_SoR_Start { [CompilerGenerated] private sealed class <WaitForComms>d__1 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitForComms>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.voiceChatModule == (Object)null || !StartOfRound.Instance.voiceChatModule.IsStarted) { <>2__current = null; <>1__state = 1; return true; } Logger.LogInfo((object)"Passed check 2!"); VoiceCaptureManager.Initialize(StartOfRound.Instance.voiceChatModule); VoiceCaptureManager.AttachToAllPlayers(); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static void Postfix(StartOfRound __instance) { ((MonoBehaviour)__instance).StartCoroutine(WaitForComms()); } [IteratorStateMachine(typeof(<WaitForComms>d__1))] private static IEnumerator WaitForComms() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitForComms>d__1(0); } } [HarmonyPatch(typeof(StartOfRound), "RefreshPlayerVoicePlaybackObjects")] internal static class Patch_RefreshPlayerVoicePlaybackObjects { private static void Postfix() { Logger.LogInfo((object)"Passed check 3!"); VoiceCaptureManager.AttachToAllPlayers(); } } [CompilerGenerated] private sealed class <BootstrapVoiceCapture>d__16 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public LethalVoiceCommands <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <BootstrapVoiceCapture>d__16(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; break; case 1: <>1__state = -1; break; } if ((Object)(object)StartOfRound.Instance == (Object)null || (Object)(object)StartOfRound.Instance.voiceChatModule == (Object)null || !StartOfRound.Instance.voiceChatModule.IsStarted) { <>2__current = null; <>1__state = 1; return true; } Logger.LogInfo((object)"Passed check 1!"); VoiceCaptureManager.Initialize(StartOfRound.Instance.voiceChatModule); VoiceCaptureManager.AttachToAllPlayers(); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public static LethalVoiceCommands Instance { get; private set; } internal static ManualLogSource Logger { get; private set; } internal static Harmony Harmony { get; set; } [DllImport("kernel32", SetLastError = true)] private static extern bool SetDllDirectory(string lpPathName); [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr LoadLibrary(string lpFileName); private void Awake() { Logger = ((BaseUnityPlugin)this).Logger; Instance = this; LoadVoskNative(); Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); Patch(); ((MonoBehaviour)this).StartCoroutine(BootstrapVoiceCapture()); Logger.LogInfo((object)"DumbassStuff.VoiceCommands v1.0.0 has loaded!"); } private void LoadVoskNative() { string pluginPath = Paths.PluginPath; Logger.LogInfo((object)("Plugin path: " + pluginPath)); SetDllDirectory(pluginPath); Logger.LogInfo((object)"SetDllDirectory called"); string environmentVariable = Environment.GetEnvironmentVariable("PATH"); Environment.SetEnvironmentVariable("PATH", pluginPath + Path.PathSeparator + environmentVariable); Logger.LogInfo((object)"PATH updated"); string text = Path.Combine(pluginPath, "libvosk.dll"); Logger.LogInfo((object)("Looking for: " + text)); Logger.LogInfo((object)$"File exists: {File.Exists(text)}"); if (File.Exists(text)) { IntPtr intPtr = LoadLibrary(text); Logger.LogInfo((object)$"LoadLibrary result: {intPtr} (0 = failed)"); if (intPtr == IntPtr.Zero) { int lastWin32Error = Marshal.GetLastWin32Error(); Logger.LogError((object)$"LoadLibrary failed with error code: {lastWin32Error}"); } return; } Logger.LogError((object)"libvosk.dll not found!"); string[] files = Directory.GetFiles(pluginPath, "*.dll"); Logger.LogInfo((object)$"DLLs in plugin path ({files.Length}):"); string[] array = files; foreach (string path in array) { Logger.LogInfo((object)(" - " + Path.GetFileName(path))); } } [IteratorStateMachine(typeof(<BootstrapVoiceCapture>d__16))] private IEnumerator BootstrapVoiceCapture() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <BootstrapVoiceCapture>d__16(0) { <>4__this = this }; } internal static void Patch() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown if (Harmony == null) { Harmony = new Harmony("DumbassStuff.VoiceCommands"); } Harmony.PatchAll(); } internal static void Unpatch() { Harmony harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "DumbassStuff.VoiceCommands"; public const string PLUGIN_NAME = "LethalVoiceCommands"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace LethalVoiceCommands.Voice { [HarmonyPatch] internal static class DissonanceCapturePatch { private static AudioCaptureManager _captureManager; private static MethodBase TargetMethod() { return AccessTools.Method(Type.GetType("Dissonance.Audio.Capture.BasicMicrophoneCapture, DissonanceVoip"), "StartCapture", (Type[])null, (Type[])null); } [HarmonyPostfix] private static void StartCapturePostfix(object __instance) { if (_captureManager != null) { _captureManager.StopCapture(); _captureManager = null; } _captureManager = new AudioCaptureManager(__instance); _captureManager.StartCapture(); } } public class AudioCaptureManager { [CompilerGenerated] private sealed class <ProcessAudioLoop>d__7 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public AudioCaptureManager <>4__this; private MicrophoneReader <reader>5__1; private WaitForSeconds <waitTime>5__2; private float[] <audioData>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ProcessAudioLoop>d__7(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <reader>5__1 = null; <waitTime>5__2 = null; <audioData>5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <reader>5__1 = new MicrophoneReader(<>4__this._micClip, <>4__this._micDeviceName); <waitTime>5__2 = new WaitForSeconds(0.01f); break; case 1: <>1__state = -1; <audioData>5__3 = <reader>5__1.ReadAvailableAudio(); if (<audioData>5__3 != null && VoiceCaptureManager.VoskInstance != null) { VoiceCaptureManager.VoskInstance.Enqueue("local", <audioData>5__3, <>4__this._micClip.channels, <>4__this._micClip.frequency); } <audioData>5__3 = null; break; } <>2__current = <waitTime>5__2; <>1__state = 1; return true; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private readonly AudioClip _micClip; private readonly string _micDeviceName; private readonly MonoBehaviour _coroutineRunner; private Coroutine _processingCoroutine; public AudioCaptureManager(object captureInstance) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown _micClip = (AudioClip)AccessTools.Field(captureInstance.GetType(), "_clip").GetValue(captureInstance); DissonanceComms componentInParent = ((Component)captureInstance).GetComponentInParent<DissonanceComms>(); _micDeviceName = componentInParent.MicrophoneName; _coroutineRunner = (MonoBehaviour)captureInstance; } public void StartCapture() { LethalVoiceCommands.Logger.LogInfo((object)$"Hooked mic: {_micDeviceName} ({_micClip.frequency}Hz, {_micClip.channels}ch)"); _processingCoroutine = _coroutineRunner.StartCoroutine(ProcessAudioLoop()); } public void StopCapture() { if (_processingCoroutine != null && (Object)(object)_coroutineRunner != (Object)null) { _coroutineRunner.StopCoroutine(_processingCoroutine); _processingCoroutine = null; } } [IteratorStateMachine(typeof(<ProcessAudioLoop>d__7))] private IEnumerator ProcessAudioLoop() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ProcessAudioLoop>d__7(0) { <>4__this = this }; } } public class MicrophoneReader { private readonly AudioClip _micClip; private readonly string _micDeviceName; private int _readPosition; private float[] _readBuffer = new float[8192]; public MicrophoneReader(AudioClip micClip, string micDeviceName) { _micClip = micClip; _micDeviceName = (string.IsNullOrEmpty(micDeviceName) ? null : micDeviceName); } public float[] ReadAvailableAudio() { int writePos = Mathf.Max(0, Microphone.GetPosition(_micDeviceName)); int samples = _micClip.samples; int num = CalculateAvailableSamples(writePos, samples); int num2 = _micClip.frequency / 50; if (num < num2) { return null; } EnsureBufferSize(num); ReadAudioData(num, samples); UpdateReadPosition(num, samples); return CopyAudioData(num); } private int CalculateAvailableSamples(int writePos, int totalSamples) { return (writePos >= _readPosition) ? (writePos - _readPosition) : (totalSamples - _readPosition + writePos); } private void EnsureBufferSize(int requiredSize) { if (_readBuffer.Length < requiredSize) { _readBuffer = new float[requiredSize]; } } private void ReadAudioData(int available, int totalSamples) { if (_readPosition + available <= totalSamples) { _micClip.GetData(_readBuffer, _readPosition); return; } int num = totalSamples - _readPosition; _micClip.GetData(_readBuffer, _readPosition); float[] array = new float[available - num]; _micClip.GetData(array, 0); Array.Copy(array, 0, _readBuffer, num, array.Length); } private void UpdateReadPosition(int available, int totalSamples) { _readPosition = (_readPosition + available) % totalSamples; } private float[] CopyAudioData(int length) { float[] array = new float[length]; Array.Copy(_readBuffer, array, length); return array; } } public sealed class LocalMicTap : IMicrophoneSubscriber { private readonly Action<string, float[], int, int> _onAudio; public LocalMicTap(Action<string, float[], int, int> onAudio) { _onAudio = onAudio; } public void ReceiveMicrophoneData(ArraySegment<float> buffer, WaveFormat format) { float[] array = new float[buffer.Count]; Array.Copy(buffer.Array, buffer.Offset, array, 0, buffer.Count); _onAudio("local", array, format.Channels, format.SampleRate); } public void Reset() { } } [DisallowMultipleComponent] public sealed class RemoteVoiceTap : MonoBehaviour { public string PlayerId = ""; public Action<string, float[], int, int>? OnAudio; private int _sampleRate; private void Awake() { _sampleRate = AudioSettings.outputSampleRate; } private void OnAudioFilterRead(float[] data, int channels) { if (OnAudio != null) { float[] array = new float[data.Length]; Array.Copy(data, array, data.Length); OnAudio(PlayerId, array, channels, _sampleRate); } } } public static class VoiceCaptureManager { private static DissonanceComms _comms; private static bool _initialized; public static VoskService VoskInstance { get; private set; } public static void Initialize(DissonanceComms comms) { if (!_initialized) { _comms = comms; VoskInstance = new VoskService(); LethalVoiceCommands.Logger.LogInfo((object)"VoskService created and ready"); _comms.OnPlayerJoinedSession += delegate { AttachToAllPlayers(); }; _comms.OnPlayerLeftSession += delegate { AttachToAllPlayers(); }; AttachToAllPlayers(); _initialized = true; } } public static void AttachToAllPlayers() { PlayerVoiceIngameSettings[] array = Object.FindObjectsOfType<PlayerVoiceIngameSettings>(true); LethalVoiceCommands.Logger.LogInfo((object)$"Found {array.Length} PlayerVoiceIngameSettings objects"); PlayerVoiceIngameSettings[] array2 = array; foreach (PlayerVoiceIngameSettings val in array2) { if ((Object)(object)val.voiceAudio == (Object)null) { LethalVoiceCommands.Logger.LogWarning((object)"PlayerVoiceIngameSettings has null voiceAudio"); continue; } if (val._playerState == null) { LethalVoiceCommands.Logger.LogWarning((object)"PlayerVoiceIngameSettings has null _playerState"); continue; } RemoteVoiceTap remoteVoiceTap = ((Component)val.voiceAudio).gameObject.GetComponent<RemoteVoiceTap>(); if ((Object)(object)remoteVoiceTap == (Object)null) { remoteVoiceTap = ((Component)val.voiceAudio).gameObject.AddComponent<RemoteVoiceTap>(); } remoteVoiceTap.PlayerId = val._playerState.Name; remoteVoiceTap.OnAudio = VoskInstance.Enqueue; LethalVoiceCommands.Logger.LogInfo((object)("Attached tap to player: " + val._playerState.Name)); } } } public sealed class VoskService { private sealed class StreamState { public string LastEmittedText = ""; public DateTime LastEmitUtc = DateTime.MinValue; public DateTime LastVoiceUtc = DateTime.UtcNow; public void Reset() { LastEmittedText = ""; LastEmitUtc = DateTime.MinValue; LastVoiceUtc = DateTime.UtcNow; } } private static readonly string ModelDir = Path.Combine(Paths.PluginPath, "vosk-model-small-en-us-0.15"); private const int TargetRate = 16000; private const int ChunkMs = 200; private const int ChunkBytes = 6400; private const float SilenceMs = 400f; private const float PartialDebounceMs = 150f; private const float VoiceThreshold = 0.008f; private static readonly Regex JsonExtractor = new Regex("\"(?:text|partial)\"\\s*:\\s*\"([^\"]*)\""); private readonly Model _model; private readonly ConcurrentDictionary<string, VoskRecognizer> _recognizers = new ConcurrentDictionary<string, VoskRecognizer>(); private readonly ConcurrentDictionary<string, List<byte>> _buffers = new ConcurrentDictionary<string, List<byte>>(); private readonly ConcurrentDictionary<string, StreamState> _states = new ConcurrentDictionary<string, StreamState>(); private readonly ConcurrentQueue<(string id, float[] data, int ch, int sr)> _queue = new ConcurrentQueue<(string, float[], int, int)>(); private readonly Thread _worker; private long _totalChunksProcessed; public VoskService() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown Vosk.SetLogLevel(0); _model = new Model(ModelDir); _worker = new Thread(WorkerLoop) { IsBackground = true, Name = "LVC_VoskWorker" }; _worker.Start(); LethalVoiceCommands.Logger.LogInfo((object)"VoskService worker thread started"); } public void Enqueue(string playerId, float[] data, int channels, int sampleRate) { _queue.Enqueue((playerId, data, channels, sampleRate)); } private VoskRecognizer GetRecognizer(string id) { string id2 = id; return _recognizers.GetOrAdd(id2, (Func<string, VoskRecognizer>)delegate { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown VoskRecognizer val = new VoskRecognizer(_model, 16000f); val.SetWords(true); val.SetMaxAlternatives(0); LethalVoiceCommands.Logger.LogInfo((object)("Created recognizer for: " + id2)); return val; }); } private StreamState GetState(string id) { return _states.GetOrAdd(id, (string _) => new StreamState()); } private void WorkerLoop() { while (true) { if (!_queue.TryDequeue(out (string, float[], int, int) result)) { Thread.Sleep(2); continue; } _totalChunksProcessed++; if (_totalChunksProcessed % 200 == 0) { LethalVoiceCommands.Logger.LogDebug((object)$"Processed {_totalChunksProcessed} audio chunks from queue"); } float[] input = ((result.Item3 > 1) ? DownmixToMono(result.Item2, result.Item3) : result.Item2); float[] array = ResampleLinear(input, result.Item4, 16000); byte[] collection = FloatToPcm16(array); StreamState state = GetState(result.Item1); List<byte> orAdd = _buffers.GetOrAdd(result.Item1, (string _) => new List<byte>(51200)); orAdd.AddRange(collection); if (HasVoice(array)) { state.LastVoiceUtc = DateTime.UtcNow; } while (orAdd.Count >= 6400) { byte[] array2 = orAdd.GetRange(0, 6400).ToArray(); orAdd.RemoveRange(0, 6400); VoskRecognizer recognizer = GetRecognizer(result.Item1); if (recognizer.AcceptWaveform(array2, array2.Length)) { string json = recognizer.Result(); PublishFinal(result.Item1, json); state.LastEmittedText = ExtractText(json); } else { string json2 = recognizer.PartialResult(); PublishPartial(result.Item1, json2, state); } } if ((DateTime.UtcNow - state.LastVoiceUtc).TotalMilliseconds > 400.0) { VoskRecognizer recognizer2 = GetRecognizer(result.Item1); string json3 = recognizer2.FinalResult(); PublishFinal(result.Item1, json3); recognizer2.Reset(); state.Reset(); } } } private void PublishFinal(string id, string json) { TranscriptEvent transcriptEvent = BuildFinalEvent(id, json); if (transcriptEvent.Text.Length != 0) { VoiceCommandsAPI.Publish(transcriptEvent); } } private void PublishPartial(string id, string json, StreamState state) { string text = ExtractPartial(json).Trim(); if (text.Length != 0) { DateTime utcNow = DateTime.UtcNow; if (!((utcNow - state.LastEmitUtc).TotalMilliseconds < 150.0) && text.Length > state.LastEmittedText.Length && text.StartsWith(state.LastEmittedText, StringComparison.Ordinal)) { state.LastEmittedText = text; state.LastEmitUtc = utcNow; TranscriptEvent evt = new TranscriptEvent(id, text, isFinal: false, 0f, Array.Empty<WordConfidence>(), utcNow); VoiceCommandsAPI.Publish(evt); } } } private static TranscriptEvent BuildFinalEvent(string id, string json) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown JObject val = JObject.Parse(json); string text = ((string)val["text"]) ?? string.Empty; List<WordConfidence> list = new List<WordConfidence>(); JArray val2 = (JArray)val["result"]; if (val2 != null) { foreach (JToken item in val2) { string word = (string)item[(object)"word"]; JToken obj = item[(object)"conf"]; float confidence = (float)((obj != null) ? obj.ToObject<double>() : 0.0); list.Add(new WordConfidence(word, confidence)); } } float confidence2 = ((list.Count > 0) ? list.Average((WordConfidence w) => w.Confidence) : 0f); return new TranscriptEvent(id, text, isFinal: true, confidence2, list, DateTime.UtcNow); } private static bool HasVoice(float[] data) { double num = 0.0; for (int i = 0; i < data.Length; i++) { num += (double)(data[i] * data[i]); } double num2 = Math.Sqrt(num / (double)Math.Max(1, data.Length)); return num2 > 0.00800000037997961; } private static float[] DownmixToMono(float[] data, int channels) { float[] array = new float[data.Length / channels]; int num = 0; int num2 = 0; while (num < data.Length) { float num3 = 0f; for (int i = 0; i < channels; i++) { num3 += data[num + i]; } array[num2] = num3 / (float)channels; num += channels; num2++; } return array; } private static float[] ResampleLinear(float[] input, int fromRate, int toRate) { if (fromRate == toRate) { return (float[])input.Clone(); } double num = (double)toRate / (double)fromRate; int num2 = (int)Math.Round((double)input.Length * num); float[] array = new float[num2]; for (int i = 0; i < num2; i++) { double num3 = (double)i / num; int num4 = (int)num3; int num5 = Math.Min(num4 + 1, input.Length - 1); double num6 = num3 - (double)num4; array[i] = (float)((1.0 - num6) * (double)input[num4] + num6 * (double)input[num5]); } return array; } private static byte[] FloatToPcm16(float[] input) { byte[] array = new byte[input.Length * 2]; int num = 0; int num2 = 0; while (num < input.Length) { short num3 = (short)Mathf.Clamp(input[num] * 32767f, -32768f, 32767f); array[num2] = (byte)num3; array[num2 + 1] = (byte)(num3 >> 8); num++; num2 += 2; } return array; } private static string ExtractText(string json) { Match match = JsonExtractor.Match(json); return match.Success ? match.Groups[1].Value : string.Empty; } private static string ExtractPartial(string json) { JObject val = JObject.Parse(json); return ((string)val["partial"]) ?? string.Empty; } } } namespace LethalVoiceCommands.API { public enum MatchKind { Contains, Equals, StartsWith, Regex } public sealed class WordConfidence { public string Word { get; } public float Confidence { get; } public WordConfidence(string word, float confidence) { Word = word; Confidence = confidence; } } public sealed class TranscriptEvent { public string PlayerId { get; } public string Text { get; } public bool IsFinal { get; } public float Confidence { get; } public IReadOnlyList<WordConfidence> Words { get; } public DateTime TimestampUtc { get; } public TranscriptEvent(string playerId, string text, bool isFinal, float confidence, IReadOnlyList<WordConfidence> words, DateTime timestampUtc) { PlayerId = playerId; Text = text; IsFinal = isFinal; Confidence = confidence; Words = words; TimestampUtc = timestampUtc; } } public sealed class DetectionContext { public TranscriptEvent Transcript { get; } public Match? RegexMatch { get; } public DetectionContext(TranscriptEvent transcript, Match? regexMatch = null) { Transcript = transcript; RegexMatch = regexMatch; } } internal sealed class DetectionRule { public Guid Id { get; } public MatchKind Kind { get; } public string Phrase { get; } public Regex Regex { get; } public bool FinalOnly { get; } public float MinConfidence { get; } public bool CaseInsensitive { get; } public Action<DetectionContext> Callback { get; } public DetectionRule(Guid id, MatchKind kind, string phrase, Regex regex, bool finalOnly, float minConfidence, bool caseInsensitive, Action<DetectionContext> callback) { Id = id; Kind = kind; Phrase = phrase; Regex = regex; FinalOnly = finalOnly; MinConfidence = minConfidence; CaseInsensitive = caseInsensitive; Callback = callback; } } public static class VoiceCommandsAPI { private static readonly ConcurrentDictionary<Guid, Action<TranscriptEvent>> _transcriptSubs = new ConcurrentDictionary<Guid, Action<TranscriptEvent>>(); private static readonly ConcurrentDictionary<Guid, DetectionRule> _rules = new ConcurrentDictionary<Guid, DetectionRule>(); public static Guid SubscribeTranscripts(Action<TranscriptEvent> callback) { Guid guid = Guid.NewGuid(); _transcriptSubs[guid] = callback; return guid; } public static void UnsubscribeTranscripts(Guid subscriptionId) { _transcriptSubs.TryRemove(subscriptionId, out Action<TranscriptEvent> _); } public static Guid RegisterKeyword(string phrase, Action<DetectionContext> callback, MatchKind kind = MatchKind.Contains, bool finalOnly = true, float minConfidence = 0f, bool caseInsensitive = true) { Guid guid = Guid.NewGuid(); DetectionRule value = new DetectionRule(guid, kind, phrase, null, finalOnly, minConfidence, caseInsensitive, callback); _rules[guid] = value; return guid; } public static Guid RegisterRegex(Regex regex, Action<DetectionContext> callback, bool finalOnly = true, float minConfidence = 0f) { Guid guid = Guid.NewGuid(); DetectionRule value = new DetectionRule(guid, MatchKind.Regex, null, regex, finalOnly, minConfidence, caseInsensitive: false, callback); _rules[guid] = value; return guid; } public static void Unregister(Guid ruleId) { _rules.TryRemove(ruleId, out DetectionRule _); } internal static void Publish(TranscriptEvent evt) { foreach (Action<TranscriptEvent> value in _transcriptSubs.Values) { value(evt); } foreach (DetectionRule value2 in _rules.Values) { if ((value2.FinalOnly && !evt.IsFinal) || (evt.IsFinal && evt.Confidence < value2.MinConfidence)) { continue; } switch (value2.Kind) { case MatchKind.Equals: { StringComparison comparisonType3 = (value2.CaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); if (string.Equals(evt.Text, value2.Phrase, comparisonType3)) { value2.Callback(new DetectionContext(evt)); } break; } case MatchKind.StartsWith: { StringComparison comparisonType = (value2.CaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); if (evt.Text.StartsWith(value2.Phrase, comparisonType)) { value2.Callback(new DetectionContext(evt)); } break; } case MatchKind.Contains: { StringComparison comparisonType2 = (value2.CaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); if (evt.Text.IndexOf(value2.Phrase, comparisonType2) >= 0) { value2.Callback(new DetectionContext(evt)); } break; } case MatchKind.Regex: { Match match = value2.Regex.Match(evt.Text); if (match.Success) { value2.Callback(new DetectionContext(evt, match)); } break; } } } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }
plugins/Vosk.dll
Decompiled 10 months agousing System; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace Vosk; public class Model : IDisposable { private HandleRef handle; internal Model(IntPtr cPtr) { handle = new HandleRef(this, cPtr); } public Model(string model_path) : this(VoskPINVOKE.new_Model(model_path)) { } internal static HandleRef getCPtr(Model obj) { return obj?.handle ?? new HandleRef(null, IntPtr.Zero); } ~Model() { Dispose(disposing: false); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { lock (this) { if (handle.Handle != IntPtr.Zero) { VoskPINVOKE.delete_Model(handle); handle = new HandleRef(null, IntPtr.Zero); } } } public int FindWord(string word) { return VoskPINVOKE.Model_vosk_model_find_word(handle, word); } } public class SpkModel : IDisposable { private HandleRef handle; internal SpkModel(IntPtr cPtr) { handle = new HandleRef(this, cPtr); } public SpkModel(string model_path) : this(VoskPINVOKE.new_SpkModel(model_path)) { } internal static HandleRef getCPtr(SpkModel obj) { return obj?.handle ?? new HandleRef(null, IntPtr.Zero); } ~SpkModel() { Dispose(disposing: false); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { lock (this) { if (handle.Handle != IntPtr.Zero) { VoskPINVOKE.delete_SpkModel(handle); handle = new HandleRef(null, IntPtr.Zero); } } } } public class Vosk { public static void SetLogLevel(int level) { VoskPINVOKE.SetLogLevel(level); } public static void GpuInit() { VoskPINVOKE.GpuInit(); } public static void GpuThreadInit() { VoskPINVOKE.GpuThreadInit(); } } internal class VoskPINVOKE { static VoskPINVOKE() { } [DllImport("libvosk", EntryPoint = "vosk_model_new")] public static extern IntPtr new_Model(string jarg1); [DllImport("libvosk", EntryPoint = "vosk_model_free")] public static extern void delete_Model(HandleRef jarg1); [DllImport("libvosk", EntryPoint = "vosk_model_find_word")] public static extern int Model_vosk_model_find_word(HandleRef jarg1, string jarg2); [DllImport("libvosk", EntryPoint = "vosk_spk_model_new")] public static extern IntPtr new_SpkModel(string jarg1); [DllImport("libvosk", EntryPoint = "vosk_spk_model_free")] public static extern void delete_SpkModel(HandleRef jarg1); [DllImport("libvosk", EntryPoint = "vosk_recognizer_new")] public static extern IntPtr new_VoskRecognizer(HandleRef jarg1, float jarg2); [DllImport("libvosk", EntryPoint = "vosk_recognizer_new_spk")] public static extern IntPtr new_VoskRecognizerSpk(HandleRef jarg1, float jarg2, HandleRef jarg3); [DllImport("libvosk", EntryPoint = "vosk_recognizer_new_grm")] public static extern IntPtr new_VoskRecognizerGrm(HandleRef jarg1, float jarg2, string jarg3); [DllImport("libvosk", EntryPoint = "vosk_recognizer_free")] public static extern void delete_VoskRecognizer(HandleRef jarg1); [DllImport("libvosk", EntryPoint = "vosk_recognizer_set_max_alternatives")] public static extern void VoskRecognizer_SetMaxAlternatives(HandleRef jarg1, int jarg2); [DllImport("libvosk", EntryPoint = "vosk_recognizer_set_words")] public static extern void VoskRecognizer_SetWords(HandleRef jarg1, int jarg2); [DllImport("libvosk", EntryPoint = "vosk_recognizer_set_partial_words")] public static extern void VoskRecognizer_SetPartialWords(HandleRef jarg1, int jarg2); [DllImport("libvosk", EntryPoint = "vosk_recognizer_set_spk_model")] public static extern void VoskRecognizer_SetSpkModel(HandleRef jarg1, HandleRef jarg2); [DllImport("libvosk", EntryPoint = "vosk_recognizer_accept_waveform")] public static extern bool VoskRecognizer_AcceptWaveform(HandleRef jarg1, [In][MarshalAs(UnmanagedType.LPArray)] byte[] jarg2, int jarg3); [DllImport("libvosk", EntryPoint = "vosk_recognizer_accept_waveform_s")] public static extern bool VoskRecognizer_AcceptWaveformShort(HandleRef jarg1, [In][MarshalAs(UnmanagedType.LPArray)] short[] jarg2, int jarg3); [DllImport("libvosk", EntryPoint = "vosk_recognizer_accept_waveform_f")] public static extern bool VoskRecognizer_AcceptWaveformFloat(HandleRef jarg1, [In][MarshalAs(UnmanagedType.LPArray)] float[] jarg2, int jarg3); [DllImport("libvosk", EntryPoint = "vosk_recognizer_result")] public static extern IntPtr VoskRecognizer_Result(HandleRef jarg1); [DllImport("libvosk", EntryPoint = "vosk_recognizer_partial_result")] public static extern IntPtr VoskRecognizer_PartialResult(HandleRef jarg1); [DllImport("libvosk", EntryPoint = "vosk_recognizer_final_result")] public static extern IntPtr VoskRecognizer_FinalResult(HandleRef jarg1); [DllImport("libvosk", EntryPoint = "vosk_recognizer_reset")] public static extern void VoskRecognizer_Reset(HandleRef jarg1); [DllImport("libvosk", EntryPoint = "vosk_set_log_level")] public static extern void SetLogLevel(int jarg1); [DllImport("libvosk", EntryPoint = "vosk_gpu_init")] public static extern void GpuInit(); [DllImport("libvosk", EntryPoint = "vosk_gpu_thread_init")] public static extern void GpuThreadInit(); } public class VoskRecognizer : IDisposable { private HandleRef handle; internal VoskRecognizer(IntPtr cPtr) { handle = new HandleRef(this, cPtr); } public VoskRecognizer(Model model, float sample_rate) : this(VoskPINVOKE.new_VoskRecognizer(Model.getCPtr(model), sample_rate)) { } public VoskRecognizer(Model model, float sample_rate, SpkModel spk_model) : this(VoskPINVOKE.new_VoskRecognizerSpk(Model.getCPtr(model), sample_rate, SpkModel.getCPtr(spk_model))) { } public VoskRecognizer(Model model, float sample_rate, string grammar) : this(VoskPINVOKE.new_VoskRecognizerGrm(Model.getCPtr(model), sample_rate, grammar)) { } internal static HandleRef getCPtr(VoskRecognizer obj) { return obj?.handle ?? new HandleRef(null, IntPtr.Zero); } ~VoskRecognizer() { Dispose(disposing: false); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { lock (this) { if (handle.Handle != IntPtr.Zero) { VoskPINVOKE.delete_VoskRecognizer(handle); handle = new HandleRef(null, IntPtr.Zero); } } } public void SetMaxAlternatives(int max_alternatives) { VoskPINVOKE.VoskRecognizer_SetMaxAlternatives(handle, max_alternatives); } public void SetWords(bool words) { VoskPINVOKE.VoskRecognizer_SetWords(handle, words ? 1 : 0); } public void SetPartialWords(bool partial_words) { VoskPINVOKE.VoskRecognizer_SetPartialWords(handle, partial_words ? 1 : 0); } public void SetSpkModel(SpkModel spk_model) { VoskPINVOKE.VoskRecognizer_SetSpkModel(handle, SpkModel.getCPtr(spk_model)); } public bool AcceptWaveform(byte[] data, int len) { return VoskPINVOKE.VoskRecognizer_AcceptWaveform(handle, data, len); } public bool AcceptWaveform(short[] sdata, int len) { return VoskPINVOKE.VoskRecognizer_AcceptWaveformShort(handle, sdata, len); } public bool AcceptWaveform(float[] fdata, int len) { return VoskPINVOKE.VoskRecognizer_AcceptWaveformFloat(handle, fdata, len); } private static string PtrToStringUTF8(IntPtr ptr) { int i; for (i = 0; Marshal.ReadByte(ptr, i) != 0; i++) { } byte[] array = new byte[i]; Marshal.Copy(ptr, array, 0, i); return Encoding.UTF8.GetString(array); } public string Result() { return PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_Result(handle)); } public string PartialResult() { return PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_PartialResult(handle)); } public string FinalResult() { return PtrToStringUTF8(VoskPINVOKE.VoskRecognizer_FinalResult(handle)); } public void Reset() { VoskPINVOKE.VoskRecognizer_Reset(handle); } }