Decompiled source of VoiceRecognitionAPI v1.0.0

plugins/DumbassStuff.VoiceCommands.dll

Decompiled 10 months ago
using 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 ago
using 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);
	}
}