Please disclose if your mod was created primarily 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 LethalMic v1.5.3
LethalMic.dll
Decompiled 10 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using LethalCompanyInputUtils.Api; using LethalCompanyInputUtils.BindingPathEnums; using LethalMic.Patches; using LethalMic.UI.Components; using Microsoft.CodeAnalysis; using TMPro; using Unity.Netcode; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("LethalMic")] [assembly: AssemblyDescription("LethalMic - Advanced audio processing for Lethal Company")] [assembly: AssemblyCompany("xenoveni")] [assembly: AssemblyProduct("LethalMic")] [assembly: AssemblyCopyright("Copyright © xenoveni 2025")] [assembly: AssemblyFileVersion("2.0.0")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace LethalMic { public static class StaticAudioManager { private static ManualLogSource Logger; private static bool isInitialized; private static bool isRecording; private static float[] audioBuffer; private static float[] processedBuffer; private static int bufferSize = 1024; private static string selectedDevice; private static AudioClip microphoneClip; private static int sampleRate = 44100; private static int channels = 1; private static float currentMicLevel; private static float peakMicLevel; private static float noiseFloor; private static bool voiceDetected; private static float cpuUsage; private static int lastMicPosition = 0; private static AINoiseSuppressionProcessor noiseSuppressor; private static AdvancedEchoCanceller echoCanceller; private static SpectralSubtractionProcessor spectralProcessor; private static VoiceDuckingProcessor voiceDucker; private static AudioCompressorProcessor compressor; private static bool processorsInitialized = false; private static float[] tempBuffer; private static float[] outputBuffer; private static float lastGain = -1f; private static bool lastNoiseGate = false; private static bool lastCompression = false; private static float lastRatio = -1f; public static void Initialize(ManualLogSource logger) { if (!isInitialized) { Logger = logger; Logger.LogInfo((object)"Initializing StaticAudioManager..."); audioBuffer = new float[bufferSize]; processedBuffer = new float[bufferSize]; tempBuffer = new float[bufferSize]; outputBuffer = new float[bufferSize]; Logger.LogInfo((object)$"Audio buffers initialized with size: {bufferSize}"); InitializeProcessors(); StartSpeakerCapture(); Logger.LogInfo((object)"StaticAudioManager initialized with advanced processing pipeline"); isInitialized = true; } } private static void InitializeProcessors() { try { noiseSuppressor = new AINoiseSuppressionProcessor(sampleRate, bufferSize); echoCanceller = new AdvancedEchoCanceller(sampleRate, bufferSize); spectralProcessor = new SpectralSubtractionProcessor(sampleRate, channels); voiceDucker = new VoiceDuckingProcessor(sampleRate, bufferSize); compressor = new AudioCompressorProcessor(sampleRate); processorsInitialized = true; Logger.LogInfo((object)"Audio processors initialized successfully"); } catch (Exception arg) { Logger.LogError((object)$"Failed to initialize audio processors: {arg}"); processorsInitialized = false; } } public static void StartRecording() { if (!isInitialized || isRecording) { return; } try { string[] devices = Microphone.devices; if (devices.Length == 0) { Logger.LogWarning((object)"No microphone devices found"); return; } string text = LethalMicStatic.GetInputDevice(); if (string.IsNullOrEmpty(text)) { text = devices[0]; } microphoneClip = Microphone.Start(text, true, 1, sampleRate); if ((Object)(object)microphoneClip != (Object)null) { isRecording = true; lastMicPosition = 0; selectedDevice = text; Logger.LogInfo((object)("Started recording from device: " + text)); } else { Logger.LogError((object)"Failed to start microphone recording"); } } catch (Exception arg) { Logger.LogError((object)$"Error starting microphone recording: {arg}"); } } public static void StopRecording() { if (!isRecording) { return; } try { Microphone.End(selectedDevice); isRecording = false; Logger.LogInfo((object)"Stopped recording"); } catch (Exception arg) { Logger.LogError((object)$"Failed to stop recording: {arg}"); } } public static void ProcessAudio() { if (!isInitialized || !isRecording || (Object)(object)microphoneClip == (Object)null) { return; } try { int samples = microphoneClip.samples; int position = Microphone.GetPosition(selectedDevice); if (position < 0 || position >= samples) { lastMicPosition = 0; return; } int num = position - lastMicPosition; if (num < 0) { num += samples; } if (num <= 0 || num > samples) { lastMicPosition = position; return; } float[] array = new float[num]; if (!microphoneClip.GetData(array, lastMicPosition) || array.Length == 0) { lastMicPosition = position; return; } UpdateAudioLevels(ProcessAudioPipeline(array)); UpdateUI(); lastMicPosition = position; } catch (Exception arg) { Logger.LogError((object)$"Error processing audio: {arg}"); } } private static float[] ProcessAudioPipeline(float[] inputData) { if (!processorsInitialized || inputData == null || inputData.Length == 0) { return inputData; } try { Array.Copy(inputData, tempBuffer, Math.Min(inputData.Length, tempBuffer.Length)); float microphoneGain = LethalMicStatic.GetMicrophoneGain(); for (int i = 0; i < tempBuffer.Length; i++) { tempBuffer[i] *= microphoneGain; } if (LethalMicStatic.GetNoiseGateEnabled()) { float noiseGateThreshold = LethalMicStatic.GetNoiseGateThreshold(); float num = 0f; for (int j = 0; j < tempBuffer.Length; j++) { num += tempBuffer[j] * tempBuffer[j]; } num = Mathf.Sqrt(num / (float)tempBuffer.Length); float num2 = noiseGateThreshold; if (num > noiseGateThreshold * 3f) { num2 = noiseGateThreshold * 5f; Logger.LogInfo((object)$"Echo detected! RMS: {num:F4}, using adaptive threshold: {num2:F4}"); } for (int k = 0; k < tempBuffer.Length; k++) { if (Mathf.Abs(tempBuffer[k]) < num2) { tempBuffer[k] = 0f; } } } if (noiseSuppressor != null && noiseSuppressor.IsEnabled) { tempBuffer = noiseSuppressor.ProcessAudio(tempBuffer); } if (spectralProcessor != null) { spectralProcessor.ProcessAudio(tempBuffer, 0, tempBuffer.Length); } if (compressor != null && compressor.IsEnabled) { tempBuffer = compressor.ProcessAudio(tempBuffer); } Array.Copy(tempBuffer, outputBuffer, Math.Min(tempBuffer.Length, outputBuffer.Length)); return outputBuffer; } catch (Exception arg) { Logger.LogError((object)$"Error in audio processing pipeline: {arg}"); return inputData; } } private static void UpdateAudioLevels(float[] audioData) { if (audioData != null && audioData.Length != 0) { float num = 0f; for (int i = 0; i < audioData.Length; i++) { num += audioData[i] * audioData[i]; } currentMicLevel = Mathf.Sqrt(num / (float)audioData.Length); peakMicLevel = Mathf.Max(peakMicLevel * 0.95f, currentMicLevel); float noiseGateThreshold = LethalMicStatic.GetNoiseGateThreshold(); voiceDetected = currentMicLevel > noiseGateThreshold; if (currentMicLevel < noiseFloor || noiseFloor == 0f) { noiseFloor = Mathf.Lerp(noiseFloor, currentMicLevel, 0.01f); } cpuUsage = Mathf.Lerp(cpuUsage, currentMicLevel * 100f, Time.deltaTime); } } private static void UpdateUI() { if (LethalMicStatic.IsUIVisible()) { LethalMicUI uIIInstance = LethalMicStatic.GetUIIInstance(); if ((Object)(object)uIIInstance != (Object)null) { uIIInstance.UpdateMicStatus(selectedDevice, "Connected", currentMicLevel); uIIInstance.UpdateCPUUsage(cpuUsage); } } } public static void Cleanup() { if (isRecording) { StopRecording(); } if ((Object)(object)microphoneClip != (Object)null) { Object.Destroy((Object)(object)microphoneClip); microphoneClip = null; } if (noiseSuppressor != null) { noiseSuppressor.Dispose(); noiseSuppressor = null; } if (echoCanceller != null) { echoCanceller.Dispose(); echoCanceller = null; } if (spectralProcessor != null) { spectralProcessor.Dispose(); spectralProcessor = null; } if (voiceDucker != null) { voiceDucker.Dispose(); voiceDucker = null; } if (compressor != null) { compressor.Dispose(); compressor = null; } isInitialized = false; processorsInitialized = false; Logger.LogInfo((object)"StaticAudioManager cleaned up"); } public static float GetCurrentMicrophoneLevel() { return currentMicLevel; } public static float GetPeakMicrophoneLevel() { return peakMicLevel; } public static bool IsVoiceDetected() { return voiceDetected; } public static float GetNoiseFloor() { return noiseFloor; } public static void SetNoiseFloor(float value) { noiseFloor = value; } public static float GetCPUUsage() { return cpuUsage; } public static void UpdateProcessorSettings() { if (!processorsInitialized) { return; } try { bool flag = Math.Abs(LethalMicStatic.GetMicrophoneGain() - lastGain) > 0.1f || LethalMicStatic.GetNoiseGateEnabled() != lastNoiseGate || LethalMicStatic.GetCompressionEnabled() != lastCompression || Math.Abs(LethalMicStatic.GetCompressionRatio() - lastRatio) > 0.5f; if (flag) { Logger.LogInfo((object)"Updating audio processor settings..."); } if (noiseSuppressor != null) { noiseSuppressor.NoiseReductionStrength = LethalMicStatic.GetNoiseGateThreshold(); noiseSuppressor.IsEnabled = LethalMicStatic.GetNoiseGateEnabled(); if (flag) { Logger.LogInfo((object)$"Noise suppressor: Enabled={noiseSuppressor.IsEnabled}, Strength={noiseSuppressor.NoiseReductionStrength:F3}"); } } if (echoCanceller != null) { echoCanceller.EchoCancellationStrength = 0f; echoCanceller.IsEnabled = false; if (flag) { Logger.LogInfo((object)"Echo canceller: DISABLED (prevents echo loops)"); } } if (voiceDucker != null) { voiceDucker.SetDuckingLevel(0.3f); if (flag) { Logger.LogInfo((object)"Voice ducker: Ducking level=0.3"); } } if (compressor != null) { compressor.IsEnabled = LethalMicStatic.GetCompressionEnabled(); compressor.UpdateSettings(-20f, LethalMicStatic.GetCompressionRatio(), LethalMicStatic.GetAttackTime(), LethalMicStatic.GetReleaseTime(), 0f); if (flag) { Logger.LogInfo((object)$"Compressor: Enabled={compressor.IsEnabled}, Ratio={LethalMicStatic.GetCompressionRatio()}, Attack={LethalMicStatic.GetAttackTime():F0}ms, Release={LethalMicStatic.GetReleaseTime():F0}ms"); } } if (flag) { Logger.LogInfo((object)"Audio processor settings updated successfully"); lastGain = LethalMicStatic.GetMicrophoneGain(); lastNoiseGate = LethalMicStatic.GetNoiseGateEnabled(); lastCompression = LethalMicStatic.GetCompressionEnabled(); lastRatio = LethalMicStatic.GetCompressionRatio(); } } catch (Exception arg) { Logger.LogError((object)$"Error updating processor settings: {arg}"); } } public static void SetInputDevice(string deviceName) { selectedDevice = deviceName; if (isRecording) { StopRecording(); StartRecording(); } } public static string GetInputDevice() { return selectedDevice; } private static void StartSpeakerCapture() { try { Logger.LogInfo((object)"Starting speaker audio capture for echo cancellation..."); Logger.LogInfo((object)"Speaker capture initialized (simulated)"); } catch (Exception arg) { Logger.LogError((object)$"Failed to start speaker capture: {arg}"); } } private static float[] GetSpeakerAudio(int requiredLength) { return null; } public static float[] ProcessAudioBuffer(float[] inputBuffer) { if (!processorsInitialized || inputBuffer == null || inputBuffer.Length == 0) { return inputBuffer; } try { return ProcessAudioPipeline(inputBuffer); } catch (Exception arg) { Logger.LogError((object)$"Error processing audio buffer: {arg}"); return inputBuffer; } } public static void SetAggressiveSuppression() { LethalMicStatic.SetNoiseGateEnabled(value: true); LethalMicStatic.SetNoiseGateThreshold(0.05f); LethalMicStatic.SetCompressionEnabled(value: true); LethalMicStatic.SetCompressionRatio(10f); LethalMicStatic.SetAttackTime(2f); LethalMicStatic.SetReleaseTime(50f); } public static void SetStereoMixSuppression() { SetAggressiveSuppression(); } public static void SetWasapiSuppression() { SetAggressiveSuppression(); } } [Serializable] public class AudioPreset { public string Name { get; set; } public string Description { get; set; } public DateTime CreatedDate { get; set; } public bool NoiseSuppressionEnabled { get; set; } = true; public float NoiseSuppressionStrength { get; set; } = 0.8f; public bool RNNoiseEnabled { get; set; } = true; public bool VoiceEnhancementEnabled { get; set; } = true; public float VoiceGain { get; set; } = 1f; public bool AutoGainControlEnabled { get; set; } = true; public bool EchoCancellationEnabled { get; set; } = true; public float EchoCancellationStrength { get; set; } = 0.7f; public int EchoFilterLength { get; set; } = 256; public bool VoiceDuckingEnabled { get; set; } = true; public float DuckingLevel { get; set; } = 0.3f; public float DuckingAttackTime { get; set; } = 0.003f; public float DuckingReleaseTime { get; set; } = 0.1f; public int ProcessingQuality { get; set; } = 5; public bool SpectralSubtractionEnabled { get; set; } public bool LoopDetectionEnabled { get; set; } = true; public float LoopDetectionThreshold { get; set; } = 0.7f; public AudioPreset() { Name = "Default"; Description = "Default audio processing settings"; CreatedDate = DateTime.Now; } public AudioPreset(string name, string description) : this() { Name = name; Description = description; } } public static class AudioPresetManager { private static readonly string PresetsDirectory; private static readonly Dictionary<string, AudioPreset> _loadedPresets; private static AudioPreset _currentPreset; static AudioPresetManager() { PresetsDirectory = Path.Combine(Paths.ConfigPath, "LethalMic", "Presets"); _loadedPresets = new Dictionary<string, AudioPreset>(); InitializePresets(); } public static void Initialize() { InitializePresets(); } private static void InitializePresets() { try { if (!Directory.Exists(PresetsDirectory)) { Directory.CreateDirectory(PresetsDirectory); } CreateDefaultPresets(); LoadAllPresets(); _currentPreset = GetPreset("Default") ?? CreateDefaultPreset(); } catch (Exception ex) { Debug.LogError((object)("Failed to initialize audio presets: " + ex.Message)); _currentPreset = CreateDefaultPreset(); } } private static void CreateDefaultPresets() { SavePreset(CreateDefaultPreset()); SavePreset(new AudioPreset("High Quality", "Maximum quality settings for best audio processing") { NoiseSuppressionStrength = 0.9f, ProcessingQuality = 10, EchoFilterLength = 512, VoiceGain = 1.2f, SpectralSubtractionEnabled = true }); SavePreset(new AudioPreset("Performance", "Optimized for lower CPU usage") { NoiseSuppressionStrength = 0.6f, ProcessingQuality = 3, EchoFilterLength = 128, SpectralSubtractionEnabled = false, RNNoiseEnabled = false }); SavePreset(new AudioPreset("Gaming", "Balanced settings for gaming with voice chat") { NoiseSuppressionStrength = 0.7f, VoiceDuckingEnabled = true, DuckingLevel = 0.4f, ProcessingQuality = 5, LoopDetectionEnabled = true }); SavePreset(new AudioPreset("Streaming", "Professional settings for content creation") { NoiseSuppressionStrength = 0.85f, VoiceGain = 1.1f, ProcessingQuality = 8, EchoCancellationStrength = 0.8f, SpectralSubtractionEnabled = true, VoiceDuckingEnabled = false }); } private static AudioPreset CreateDefaultPreset() { return new AudioPreset("Default", "Standard audio processing settings"); } public static void SavePreset(AudioPreset preset) { try { string path = Path.Combine(PresetsDirectory, preset.Name + ".json"); string contents = JsonUtility.ToJson((object)preset, true); File.WriteAllText(path, contents); _loadedPresets[preset.Name] = preset; Debug.Log((object)("Saved audio preset: " + preset.Name)); } catch (Exception ex) { Debug.LogError((object)("Failed to save preset " + preset.Name + ": " + ex.Message)); } } public static AudioPreset LoadPreset(string name) { try { string path = Path.Combine(PresetsDirectory, name + ".json"); if (!File.Exists(path)) { Debug.LogWarning((object)("Preset file not found: " + name)); return null; } AudioPreset audioPreset = JsonUtility.FromJson<AudioPreset>(File.ReadAllText(path)); _loadedPresets[name] = audioPreset; return audioPreset; } catch (Exception ex) { Debug.LogError((object)("Failed to load preset " + name + ": " + ex.Message)); return null; } } private static void LoadAllPresets() { try { if (Directory.Exists(PresetsDirectory)) { string[] files = Directory.GetFiles(PresetsDirectory, "*.json"); for (int i = 0; i < files.Length; i++) { LoadPreset(Path.GetFileNameWithoutExtension(files[i])); } } } catch (Exception ex) { Debug.LogError((object)("Failed to load presets: " + ex.Message)); } } public static AudioPreset GetPreset(string name) { _loadedPresets.TryGetValue(name, out var value); return value; } public static List<string> GetPresetNames() { return new List<string>(_loadedPresets.Keys); } public static AudioPreset GetCurrentPreset() { return _currentPreset; } public static void SetCurrentPreset(string name) { AudioPreset preset = GetPreset(name); if (preset != null) { _currentPreset = preset; Debug.Log((object)("Switched to audio preset: " + name)); } else { Debug.LogWarning((object)("Preset not found: " + name)); } } public static void SetCurrentPreset(AudioPreset preset) { if (preset != null) { _currentPreset = preset; Debug.Log((object)("Applied audio preset: " + preset.Name)); } } public static void DeletePreset(string name) { try { if (name == "Default") { Debug.LogWarning((object)"Cannot delete the default preset"); return; } string path = Path.Combine(PresetsDirectory, name + ".json"); if (File.Exists(path)) { File.Delete(path); } _loadedPresets.Remove(name); if (_currentPreset?.Name == name) { SetCurrentPreset("Default"); } Debug.Log((object)("Deleted audio preset: " + name)); } catch (Exception ex) { Debug.LogError((object)("Failed to delete preset " + name + ": " + ex.Message)); } } public static AudioPreset CreatePresetFromCurrent(string name, string description) { AudioPreset audioPreset = new AudioPreset(name, description); if (_currentPreset != null) { audioPreset.NoiseSuppressionEnabled = _currentPreset.NoiseSuppressionEnabled; audioPreset.NoiseSuppressionStrength = _currentPreset.NoiseSuppressionStrength; audioPreset.RNNoiseEnabled = _currentPreset.RNNoiseEnabled; audioPreset.VoiceEnhancementEnabled = _currentPreset.VoiceEnhancementEnabled; audioPreset.VoiceGain = _currentPreset.VoiceGain; audioPreset.AutoGainControlEnabled = _currentPreset.AutoGainControlEnabled; audioPreset.EchoCancellationEnabled = _currentPreset.EchoCancellationEnabled; audioPreset.EchoCancellationStrength = _currentPreset.EchoCancellationStrength; audioPreset.EchoFilterLength = _currentPreset.EchoFilterLength; audioPreset.VoiceDuckingEnabled = _currentPreset.VoiceDuckingEnabled; audioPreset.DuckingLevel = _currentPreset.DuckingLevel; audioPreset.DuckingAttackTime = _currentPreset.DuckingAttackTime; audioPreset.DuckingReleaseTime = _currentPreset.DuckingReleaseTime; audioPreset.ProcessingQuality = _currentPreset.ProcessingQuality; audioPreset.SpectralSubtractionEnabled = _currentPreset.SpectralSubtractionEnabled; audioPreset.LoopDetectionEnabled = _currentPreset.LoopDetectionEnabled; audioPreset.LoopDetectionThreshold = _currentPreset.LoopDetectionThreshold; } SavePreset(audioPreset); return audioPreset; } } public class AdvancedEchoCanceller : IDisposable { private readonly int _sampleRate; private readonly int _frameSize; private readonly int _filterLength; private readonly float _stepSize; private readonly float _regularization; private readonly float[] _adaptiveFilter; private readonly float[] _referenceBuffer; private readonly float[] _errorBuffer; private readonly CircularBuffer _microphoneBuffer; private readonly CircularBuffer _speakerBuffer; private readonly int _maxDelay; private readonly float[] _echoPathEstimate; private float _echoPathStrength; private readonly float _echoPathAdaptationRate = 0.01f; private readonly float _nlpThreshold; private readonly float _nlpAttenuation; private float _nlpGain; private readonly float _nlpSmoothingCoeff; private readonly DoubleTalkDetector _doubleTalkDetector; private bool _isDoubleTalk; private readonly Complex[] _fftBuffer; private readonly Complex[] _referenceFFT; private readonly Complex[] _microphoneFFT; private readonly float[] _powerSpectralDensity; private readonly float[] _coherenceFunction; private readonly int _fftSize; private readonly float[] _windowFunction; private int _frameCounter; private readonly int _adaptationInterval = 4; private bool _disposed; public float EchoCancellationStrength { get; set; } = 1f; public bool IsEnabled { get; set; } = true; public AdvancedEchoCanceller(int sampleRate, int frameSize, int filterLength = 512, float stepSize = 0.01f, int maxDelay = 1024) { _sampleRate = sampleRate; _frameSize = frameSize; _filterLength = filterLength; _stepSize = stepSize; _regularization = 1E-06f; _maxDelay = maxDelay; _fftSize = NextPowerOfTwo(Math.Max(frameSize, filterLength) * 2); _adaptiveFilter = new float[_filterLength]; _referenceBuffer = new float[_filterLength]; _errorBuffer = new float[_frameSize]; _echoPathEstimate = new float[_filterLength]; _microphoneBuffer = new CircularBuffer(_maxDelay + _frameSize); _speakerBuffer = new CircularBuffer(_maxDelay + _frameSize); _nlpThreshold = 0.1f; _nlpAttenuation = 0.3f; _nlpGain = 1f; _nlpSmoothingCoeff = 0.95f; _doubleTalkDetector = new DoubleTalkDetector(sampleRate, frameSize); _fftBuffer = new Complex[_fftSize]; _referenceFFT = new Complex[_fftSize]; _microphoneFFT = new Complex[_fftSize]; _powerSpectralDensity = new float[_fftSize / 2 + 1]; _coherenceFunction = new float[_fftSize / 2 + 1]; _windowFunction = CreateHannWindow(_frameSize); _frameCounter = 0; } public float[] ProcessAudio(float[] microphoneInput, float[] speakerReference) { if (_disposed) { throw new ObjectDisposedException("AdvancedEchoCanceller"); } if (!IsEnabled || microphoneInput == null || speakerReference == null) { return microphoneInput ?? new float[0]; } int num = Math.Min(microphoneInput.Length, speakerReference.Length); float[] array = new float[num]; _microphoneBuffer.Write(microphoneInput); _speakerBuffer.Write(speakerReference); _isDoubleTalk = _doubleTalkDetector.DetectDoubleTalk(microphoneInput, speakerReference); for (int i = 0; i < num; i += _frameSize) { int num2 = Math.Min(_frameSize, num - i); float[] array2 = new float[num2]; float[] array3 = new float[num2]; Array.Copy(microphoneInput, i, array2, 0, num2); Array.Copy(speakerReference, i, array3, 0, num2); Array.Copy(ProcessBlock(array2, array3), 0, array, i, num2); } _frameCounter++; return array; } private float[] ProcessBlock(float[] microphoneBlock, float[] referenceBlock) { float[] array = EstimateEcho(referenceBlock); float[] array2 = new float[microphoneBlock.Length]; for (int i = 0; i < microphoneBlock.Length; i++) { array2[i] = microphoneBlock[i] - array[i] * EchoCancellationStrength; } if (!_isDoubleTalk && _frameCounter % _adaptationInterval == 0) { UpdateAdaptiveFilter(referenceBlock, array2); } float[] signal = ApplyNonLinearProcessing(array2, microphoneBlock, referenceBlock); return ApplyFrequencyDomainProcessing(signal, referenceBlock); } private float[] EstimateEcho(float[] referenceBlock) { int num = Math.Min(referenceBlock.Length, _referenceBuffer.Length); Array.Copy(_referenceBuffer, num, _referenceBuffer, 0, _referenceBuffer.Length - num); Array.Copy(referenceBlock, 0, _referenceBuffer, _referenceBuffer.Length - num, num); float[] array = new float[referenceBlock.Length]; for (int i = 0; i < referenceBlock.Length; i++) { float num2 = 0f; for (int j = 0; j < _filterLength && i + j < _referenceBuffer.Length; j++) { num2 += _adaptiveFilter[j] * _referenceBuffer[_referenceBuffer.Length - 1 - i - j]; } array[i] = num2; } return array; } private void UpdateAdaptiveFilter(float[] referenceBlock, float[] errorSignal) { float num = 0f; for (int i = 0; i < _referenceBuffer.Length; i++) { num += _referenceBuffer[i] * _referenceBuffer[i]; } num += _regularization; float num2 = _stepSize / num; for (int j = 0; j < Math.Min(errorSignal.Length, referenceBlock.Length); j++) { float num3 = errorSignal[j]; for (int k = 0; k < _filterLength; k++) { int num4 = _referenceBuffer.Length - 1 - j - k; if (num4 >= 0 && num4 < _referenceBuffer.Length) { _adaptiveFilter[k] += num2 * num3 * _referenceBuffer[num4]; } } } ApplyFilterConstraints(); } private void ApplyFilterConstraints() { float num = 2f; for (int i = 0; i < _adaptiveFilter.Length; i++) { _adaptiveFilter[i] = Mathf.Clamp(_adaptiveFilter[i], 0f - num, num); } } private float[] ApplyNonLinearProcessing(float[] errorSignal, float[] microphoneSignal, float[] referenceSignal) { float[] array = new float[errorSignal.Length]; float errorEnergy = CalculateEnergy(errorSignal); CalculateEnergy(microphoneSignal); float num = CalculateEnergy(referenceSignal); float num2 = EstimateResidualEchoLevel(errorEnergy, num); float num3 = 1f; if (num2 > _nlpThreshold && num > 0.001f) { float num4 = Mathf.Clamp01(num2 / _nlpThreshold); num3 = Mathf.Lerp(1f, _nlpAttenuation, num4); } _nlpGain = _nlpGain * _nlpSmoothingCoeff + num3 * (1f - _nlpSmoothingCoeff); for (int i = 0; i < errorSignal.Length; i++) { array[i] = errorSignal[i] * _nlpGain; } return array; } private float EstimateResidualEchoLevel(float errorEnergy, float referenceEnergy) { if (referenceEnergy < 0.001f) { return 0f; } float num = errorEnergy / (referenceEnergy + 0.001f); _echoPathStrength = _echoPathStrength * (1f - _echoPathAdaptationRate) + num * _echoPathAdaptationRate; return _echoPathStrength; } private float[] ApplyFrequencyDomainProcessing(float[] signal, float[] reference) { if (signal.Length < _frameSize) { return signal; } for (int i = 0; i < _frameSize; i++) { float num = ((i < signal.Length) ? (signal[i] * _windowFunction[i]) : 0f); float num2 = ((i < reference.Length) ? (reference[i] * _windowFunction[i]) : 0f); _microphoneFFT[i] = new Complex(num, 0.0); _referenceFFT[i] = new Complex(num2, 0.0); } for (int j = _frameSize; j < _fftSize; j++) { _microphoneFFT[j] = Complex.Zero; _referenceFFT[j] = Complex.Zero; } SimpleFFT(_microphoneFFT); SimpleFFT(_referenceFFT); ApplySpectralSuppression(_microphoneFFT, _referenceFFT); SimpleIFFT(_microphoneFFT); float[] array = new float[signal.Length]; for (int k = 0; k < array.Length; k++) { array[k] = (float)_microphoneFFT[k].Real * _windowFunction[k]; } return array; } private void ApplySpectralSuppression(Complex[] microphoneSpectrum, Complex[] referenceSpectrum) { int num = _fftSize / 2 + 1; for (int i = 0; i < num && i < microphoneSpectrum.Length; i++) { float num2 = (float)microphoneSpectrum[i].Magnitude; float num3 = (float)referenceSpectrum[i].Magnitude; float num4 = 0f; if (num3 > 0.001f) { num4 = num2 * num3 / (num3 * num3 + 0.001f); } float num5 = 1f - num4 * EchoCancellationStrength; num5 = Mathf.Clamp(num5, 0.1f, 1f); microphoneSpectrum[i] *= (Complex)num5; } } private float CalculateEnergy(float[] signal) { float num = 0f; for (int i = 0; i < signal.Length; i++) { num += signal[i] * signal[i]; } return num / (float)signal.Length; } private float[] CreateHannWindow(int size) { float[] array = new float[size]; for (int i = 0; i < size; i++) { array[i] = 0.5f * (1f - Mathf.Cos(MathF.PI * 2f * (float)i / (float)(size - 1))); } return array; } private static int NextPowerOfTwo(int value) { int num; for (num = 1; num < value; num <<= 1) { } return num; } private void SimpleFFT(Complex[] buffer) { int num = buffer.Length; if (num <= 1) { return; } int i = 1; int num2 = 0; for (; i < num; i++) { int num3 = num >> 1; while ((num2 & num3) != 0) { num2 ^= num3; num3 >>= 1; } num2 ^= num3; if (i < num2) { ref Complex reference = ref buffer[i]; ref Complex reference2 = ref buffer[num2]; Complex complex = buffer[num2]; Complex complex2 = buffer[i]; reference = complex; reference2 = complex2; } } for (int num4 = 2; num4 <= num; num4 <<= 1) { double num5 = Math.PI * 2.0 / (double)num4; Complex complex3 = new Complex(Math.Cos(num5), Math.Sin(num5)); for (int j = 0; j < num; j += num4) { Complex one = Complex.One; for (int k = 0; k < num4 / 2; k++) { Complex complex4 = buffer[j + k]; Complex complex5 = buffer[j + k + num4 / 2] * one; buffer[j + k] = complex4 + complex5; buffer[j + k + num4 / 2] = complex4 - complex5; one *= complex3; } } } } private void SimpleIFFT(Complex[] buffer) { for (int i = 0; i < buffer.Length; i++) { buffer[i] = Complex.Conjugate(buffer[i]); } SimpleFFT(buffer); for (int j = 0; j < buffer.Length; j++) { buffer[j] = Complex.Conjugate(buffer[j]) / (Complex)buffer.Length; } } public void Reset() { Array.Clear(_adaptiveFilter, 0, _adaptiveFilter.Length); Array.Clear(_referenceBuffer, 0, _referenceBuffer.Length); _microphoneBuffer.Clear(); _speakerBuffer.Clear(); _echoPathStrength = 0f; _nlpGain = 1f; _doubleTalkDetector?.Reset(); } public void Dispose() { if (!_disposed) { _doubleTalkDetector?.Dispose(); _disposed = true; } } } public class DoubleTalkDetector : IDisposable { private readonly float[] _micEnergyHistory; private readonly float[] _refEnergyHistory; private readonly int _historySize = 10; private int _historyIndex; private bool _disposed; public DoubleTalkDetector(int sampleRate, int frameSize) { _micEnergyHistory = new float[_historySize]; _refEnergyHistory = new float[_historySize]; } public bool DetectDoubleTalk(float[] microphoneSignal, float[] referenceSignal) { if (_disposed) { return false; } float num = CalculateEnergy(microphoneSignal); float num2 = CalculateEnergy(referenceSignal); _micEnergyHistory[_historyIndex] = num; _refEnergyHistory[_historyIndex] = num2; _historyIndex = (_historyIndex + 1) % _historySize; float num3 = _micEnergyHistory.Average(); float num4 = _refEnergyHistory.Average(); if (num4 < 0.001f) { return false; } return num3 / num4 > 0.5f; } private float CalculateEnergy(float[] signal) { float num = 0f; for (int i = 0; i < signal.Length; i++) { num += signal[i] * signal[i]; } return num / (float)signal.Length; } public void Reset() { Array.Clear(_micEnergyHistory, 0, _micEnergyHistory.Length); Array.Clear(_refEnergyHistory, 0, _refEnergyHistory.Length); _historyIndex = 0; } public void Dispose() { _disposed = true; } } public class AINoiseSuppressionProcessor : IDisposable { private readonly int _sampleRate; private readonly int _frameSize; private readonly int _fftSize; private readonly float _noiseReductionStrength; private readonly NeuralNoiseReducer _neuralReducer; private readonly Complex[] _fftBuffer; private readonly Complex[] _noiseProfile; private readonly float[] _magnitudeSpectrum; private readonly float[] _phaseSpectrum; private readonly float[] _noiseMagnitude; private readonly float[] _cleanMagnitude; private readonly float[] _windowFunction; private readonly float[] _noiseFloorEstimate; private readonly float[] _signalToNoiseRatio; private readonly float _noiseAdaptationRate = 0.01f; private readonly float _signalAdaptationRate = 0.1f; private readonly int _numBands = 8; private float[][] _bandFilters; private float[] _bandGains; private float[] _bandNoiseFloors; private readonly VoiceActivityDetector _vadForNoise; private bool _isLearningNoise; private int _noiseLearnFrames; private readonly int _maxNoiseLearnFrames = 100; private readonly SpectralSubtractor _spectralSubtractor; private readonly WienerFilter _wienerFilter; private readonly ResidualNoiseReducer _residualReducer; private readonly float[] _overlapBuffer; private readonly int _hopSize; private int _frameCounter; private bool _disposed; public float NoiseReductionStrength { get; set; } = 1f; public bool IsEnabled { get; set; } = true; public bool IsLearningNoise => _isLearningNoise; public AINoiseSuppressionProcessor(int sampleRate, int frameSize, float noiseReductionStrength = 0.8f) { _sampleRate = sampleRate; _frameSize = frameSize; _fftSize = NextPowerOfTwo(frameSize * 2); _noiseReductionStrength = Mathf.Clamp01(noiseReductionStrength); _hopSize = frameSize / 2; _fftBuffer = new Complex[_fftSize]; _noiseProfile = new Complex[_fftSize]; _magnitudeSpectrum = new float[_fftSize / 2 + 1]; _phaseSpectrum = new float[_fftSize / 2 + 1]; _noiseMagnitude = new float[_fftSize / 2 + 1]; _cleanMagnitude = new float[_fftSize / 2 + 1]; _noiseFloorEstimate = new float[_fftSize / 2 + 1]; _signalToNoiseRatio = new float[_fftSize / 2 + 1]; _overlapBuffer = new float[_frameSize]; _windowFunction = CreateHannWindow(_frameSize); InitializeMultiBandProcessing(); _neuralReducer = new NeuralNoiseReducer(sampleRate, _fftSize / 2 + 1); _vadForNoise = new VoiceActivityDetector(sampleRate, frameSize); _spectralSubtractor = new SpectralSubtractor(_fftSize / 2 + 1); _wienerFilter = new WienerFilter(_fftSize / 2 + 1); _residualReducer = new ResidualNoiseReducer(_fftSize / 2 + 1); _isLearningNoise = true; _noiseLearnFrames = 0; for (int i = 0; i < _noiseFloorEstimate.Length; i++) { _noiseFloorEstimate[i] = 0.001f; } } private void InitializeMultiBandProcessing() { _bandFilters = new float[_numBands][]; _bandGains = new float[_numBands]; _bandNoiseFloors = new float[_numBands]; for (int i = 0; i < _numBands; i++) { _bandFilters[i] = new float[_fftSize / 2 + 1]; _bandGains[i] = 1f; _bandNoiseFloors[i] = 0.001f; float num = (float)(20.0 * Math.Pow(2.0, (double)i * 10.0 / (double)_numBands)); float num2 = (float)(20.0 * Math.Pow(2.0, (double)(i + 1) * 10.0 / (double)_numBands)); int num3 = (int)(num * (float)_fftSize / (float)_sampleRate); int num4 = (int)(num2 * (float)_fftSize / (float)_sampleRate); for (int j = 0; j < _bandFilters[i].Length; j++) { if (j >= num3 && j <= num4) { float num5 = (float)(j - num3) / (float)(num4 - num3); _bandFilters[i][j] = 0.5f * (1f - Mathf.Cos(MathF.PI * num5)); } else { _bandFilters[i][j] = 0f; } } } } public float[] ProcessAudio(float[] inputAudio) { if (_disposed) { throw new ObjectDisposedException("AINoiseSuppressionProcessor"); } if (!IsEnabled || inputAudio == null || inputAudio.Length == 0) { return inputAudio ?? new float[0]; } float[] array = new float[inputAudio.Length]; for (int i = 0; i < inputAudio.Length; i += _hopSize) { int length = Math.Min(_frameSize, inputAudio.Length - i); float[] frame = ExtractFrame(inputAudio, i, length); float[] frame2 = ProcessFrame(frame); OverlapAdd(array, frame2, i); } _frameCounter++; return array; } private float[] ExtractFrame(float[] input, int startIndex, int length) { float[] array = new float[_frameSize]; for (int i = 0; i < length && startIndex + i < input.Length; i++) { array[i] = input[startIndex + i]; } for (int j = 0; j < _frameSize; j++) { array[j] *= _windowFunction[j]; } return array; } private float[] ProcessFrame(float[] frame) { PrepareFFTBuffer(frame); SimpleFFT(_fftBuffer); ExtractMagnitudeAndPhase(); bool voiceActive = _vadForNoise.DetectVoiceActivity(frame); UpdateNoiseModel(voiceActive); float[] magnitude = ApplyMultiStageNoiseReduction(_magnitudeSpectrum); ReconstructSignal(magnitude, _phaseSpectrum); SimpleIFFT(_fftBuffer); return ExtractOutputFrame(); } private void PrepareFFTBuffer(float[] frame) { for (int i = 0; i < _fftSize; i++) { if (i < frame.Length) { _fftBuffer[i] = new Complex(frame[i], 0.0); } else { _fftBuffer[i] = Complex.Zero; } } } private void ExtractMagnitudeAndPhase() { for (int i = 0; i < _magnitudeSpectrum.Length; i++) { _magnitudeSpectrum[i] = (float)_fftBuffer[i].Magnitude; _phaseSpectrum[i] = (float)_fftBuffer[i].Phase; } } private void UpdateNoiseModel(bool voiceActive) { if (_isLearningNoise && _noiseLearnFrames < _maxNoiseLearnFrames) { if (!voiceActive || _noiseLearnFrames < 20) { for (int i = 0; i < _noiseFloorEstimate.Length; i++) { _noiseFloorEstimate[i] = _noiseFloorEstimate[i] * (1f - _noiseAdaptationRate) + _magnitudeSpectrum[i] * _noiseAdaptationRate; } } _noiseLearnFrames++; if (_noiseLearnFrames >= _maxNoiseLearnFrames) { _isLearningNoise = false; } } else if (!voiceActive) { for (int j = 0; j < _noiseFloorEstimate.Length; j++) { float num = _noiseAdaptationRate * 0.1f; _noiseFloorEstimate[j] = _noiseFloorEstimate[j] * (1f - num) + _magnitudeSpectrum[j] * num; } } for (int k = 0; k < _signalToNoiseRatio.Length; k++) { float num2 = _magnitudeSpectrum[k] / (_noiseFloorEstimate[k] + 1E-06f); if (voiceActive) { _signalToNoiseRatio[k] = _signalToNoiseRatio[k] * (1f - _signalAdaptationRate) + num2 * _signalAdaptationRate; } else { _signalToNoiseRatio[k] = num2; } } } private float[] ApplyMultiStageNoiseReduction(float[] magnitude) { float[] magnitude2 = _spectralSubtractor.Process(magnitude, _noiseFloorEstimate, NoiseReductionStrength); float[] input = _wienerFilter.Process(magnitude2, _signalToNoiseRatio); float[] magnitude3 = _neuralReducer.Process(input, _noiseFloorEstimate); float[] processed = ApplyMultiBandProcessing(magnitude3); return _residualReducer.Process(processed, magnitude, _noiseFloorEstimate); } private float[] ApplyMultiBandProcessing(float[] magnitude) { float[] array = new float[magnitude.Length]; for (int i = 0; i < _numBands; i++) { float num = 0f; float num2 = 0f; int num3 = 0; for (int j = 0; j < magnitude.Length; j++) { if (_bandFilters[i][j] > 0.1f) { num += magnitude[j] * _bandFilters[i][j]; num2 += _noiseFloorEstimate[j] * _bandFilters[i][j]; num3++; } } if (num3 > 0) { num /= (float)num3; num2 /= (float)num3; float snr = num / (num2 + 1E-06f); _bandGains[i] = CalculateBandGain(snr, i); } } for (int k = 0; k < magnitude.Length; k++) { float num4 = 0f; float num5 = 0f; for (int l = 0; l < _numBands; l++) { float num6 = _bandFilters[l][k]; num4 += _bandGains[l] * num6; num5 += num6; } if (num5 > 0f) { array[k] = magnitude[k] * (num4 / num5); } else { array[k] = magnitude[k]; } } return array; } private float CalculateBandGain(float snr, int bandIndex) { float num = 1f; num = ((snr < 1f) ? Mathf.Lerp(0.1f, 1f, snr) : ((!(snr > 10f)) ? Mathf.Lerp(0.5f, 1f, (snr - 1f) / 9f) : 1f)); if (bandIndex < 2) { num = Mathf.Lerp(num, 1f, 0.3f); } else if (bandIndex > 5) { num *= 0.8f; } return Mathf.Clamp(num, 0.05f, 1f); } private void ReconstructSignal(float[] magnitude, float[] phase) { for (int i = 0; i < magnitude.Length && i < _fftBuffer.Length; i++) { float num = magnitude[i] * Mathf.Cos(phase[i]); float num2 = magnitude[i] * Mathf.Sin(phase[i]); _fftBuffer[i] = new Complex(num, num2); } for (int j = magnitude.Length; j < _fftSize; j++) { int num3 = _fftSize - j; if (num3 < magnitude.Length) { _fftBuffer[j] = Complex.Conjugate(_fftBuffer[num3]); } else { _fftBuffer[j] = Complex.Zero; } } } private float[] ExtractOutputFrame() { float[] array = new float[_frameSize]; for (int i = 0; i < _frameSize; i++) { array[i] = (float)_fftBuffer[i].Real * _windowFunction[i]; } return array; } private void OverlapAdd(float[] output, float[] frame, int startIndex) { for (int i = 0; i < frame.Length && startIndex + i < output.Length; i++) { output[startIndex + i] += frame[i]; } } private float[] CreateHannWindow(int size) { float[] array = new float[size]; for (int i = 0; i < size; i++) { array[i] = 0.5f * (1f - Mathf.Cos(MathF.PI * 2f * (float)i / (float)(size - 1))); } return array; } private static int NextPowerOfTwo(int value) { int num; for (num = 1; num < value; num <<= 1) { } return num; } private void SimpleFFT(Complex[] buffer) { int num = buffer.Length; if (num <= 1) { return; } int i = 1; int num2 = 0; for (; i < num; i++) { int num3 = num >> 1; while ((num2 & num3) != 0) { num2 ^= num3; num3 >>= 1; } num2 ^= num3; if (i < num2) { ref Complex reference = ref buffer[i]; ref Complex reference2 = ref buffer[num2]; Complex complex = buffer[num2]; Complex complex2 = buffer[i]; reference = complex; reference2 = complex2; } } for (int num4 = 2; num4 <= num; num4 <<= 1) { double num5 = Math.PI * 2.0 / (double)num4; Complex complex3 = new Complex(Math.Cos(num5), Math.Sin(num5)); for (int j = 0; j < num; j += num4) { Complex one = Complex.One; for (int k = 0; k < num4 / 2; k++) { Complex complex4 = buffer[j + k]; Complex complex5 = buffer[j + k + num4 / 2] * one; buffer[j + k] = complex4 + complex5; buffer[j + k + num4 / 2] = complex4 - complex5; one *= complex3; } } } } private void SimpleIFFT(Complex[] buffer) { for (int i = 0; i < buffer.Length; i++) { buffer[i] = Complex.Conjugate(buffer[i]); } SimpleFFT(buffer); for (int j = 0; j < buffer.Length; j++) { buffer[j] = Complex.Conjugate(buffer[j]) / (Complex)buffer.Length; } } public void Reset() { _isLearningNoise = true; _noiseLearnFrames = 0; Array.Clear(_noiseFloorEstimate, 0, _noiseFloorEstimate.Length); Array.Clear(_overlapBuffer, 0, _overlapBuffer.Length); for (int i = 0; i < _noiseFloorEstimate.Length; i++) { _noiseFloorEstimate[i] = 0.001f; } _neuralReducer?.Reset(); _vadForNoise?.Reset(); _spectralSubtractor?.Reset(); _wienerFilter?.Reset(); _residualReducer?.Reset(); } public void Dispose() { if (!_disposed) { _neuralReducer?.Dispose(); _vadForNoise?.Dispose(); _spectralSubtractor?.Dispose(); _wienerFilter?.Dispose(); _residualReducer?.Dispose(); _disposed = true; } } } public class NeuralNoiseReducer : IDisposable { private readonly int _inputSize; private readonly float[] _weights; private readonly float[] _biases; private bool _disposed; public NeuralNoiseReducer(int sampleRate, int inputSize) { _inputSize = inputSize; _weights = new float[inputSize]; _biases = new float[inputSize]; InitializeWeights(); } private void InitializeWeights() { for (int i = 0; i < _inputSize; i++) { float num = (float)i / (float)_inputSize; if (num > 0.1f && num < 0.7f) { _weights[i] = 1.2f; } else { _weights[i] = 0.8f; } _biases[i] = 0.1f; } } public float[] Process(float[] input, float[] noiseFloor) { if (_disposed) { return input; } float[] array = new float[input.Length]; for (int i = 0; i < Math.Min(input.Length, _inputSize); i++) { float val = (float)Math.Tanh(input[i] / (noiseFloor[i] + 1E-06f) * _weights[i] + _biases[i]); array[i] = input[i] * Math.Max(0f, Math.Min(1f, val)); } return array; } public void Reset() { } public void Dispose() { _disposed = true; } } public class SpectralSubtractor : IDisposable { private readonly int _size; private bool _disposed; public SpectralSubtractor(int size) { _size = size; } public float[] Process(float[] magnitude, float[] noiseFloor, float strength) { if (_disposed) { return magnitude; } float[] array = new float[magnitude.Length]; for (int i = 0; i < magnitude.Length; i++) { float num = magnitude[i] - noiseFloor[i] * strength; array[i] = Mathf.Max(num, magnitude[i] * 0.1f); } return array; } public void Reset() { } public void Dispose() { _disposed = true; } } public class WienerFilter : IDisposable { private readonly int _size; private bool _disposed; public WienerFilter(int size) { _size = size; } public float[] Process(float[] magnitude, float[] snr) { if (_disposed) { return magnitude; } float[] array = new float[magnitude.Length]; for (int i = 0; i < magnitude.Length; i++) { float num = snr[i] / (snr[i] + 1f); array[i] = magnitude[i] * num; } return array; } public void Reset() { } public void Dispose() { _disposed = true; } } public class ResidualNoiseReducer : IDisposable { private readonly int _size; private bool _disposed; public ResidualNoiseReducer(int size) { _size = size; } public float[] Process(float[] processed, float[] original, float[] noiseFloor) { if (_disposed) { return processed; } float[] array = new float[processed.Length]; for (int i = 0; i < processed.Length; i++) { if (processed[i] / (original[i] + 1E-06f) < 0.3f && processed[i] < noiseFloor[i] * 2f) { array[i] = processed[i] * 0.5f; } else { array[i] = processed[i]; } } return array; } public void Reset() { } public void Dispose() { _disposed = true; } } public class VoiceActivityDetector : IDisposable { private readonly int _sampleRate; private readonly int _frameSize; private readonly float[] _energyHistory; private readonly int _historySize = 10; private int _historyIndex; private float _noiseFloor; private bool _disposed; public VoiceActivityDetector(int sampleRate, int frameSize) { _sampleRate = sampleRate; _frameSize = frameSize; _energyHistory = new float[_historySize]; _noiseFloor = 0.001f; } public bool DetectVoiceActivity(float[] frame) { if (_disposed) { return false; } float num = 0f; float num2 = 0f; float num3 = 0f; for (int i = 0; i < frame.Length; i++) { float num4 = Mathf.Abs(frame[i]); num += frame[i] * frame[i]; num2 += (float)i * num4; num3 += num4; } num /= (float)frame.Length; if (num3 > 0f) { num2 /= num3; } _energyHistory[_historyIndex] = num; _historyIndex = (_historyIndex + 1) % _historySize; float num5 = _energyHistory.Min(); _noiseFloor = _noiseFloor * 0.995f + num5 * 0.005f; bool flag = num > _noiseFloor * 8f; float num6 = 150f * (float)frame.Length / (float)_sampleRate; float num7 = 3400f * (float)frame.Length / (float)_sampleRate; bool flag2 = num2 >= num6 && num2 <= num7; int num8 = 0; for (int j = 1; j < frame.Length; j++) { if (frame[j] >= 0f != frame[j - 1] >= 0f) { num8++; } } float num9 = (float)num8 / (float)frame.Length; bool flag3 = num9 > 0.02f && num9 < 0.3f; return flag && flag2 && flag3; } public void Reset() { Array.Clear(_energyHistory, 0, _energyHistory.Length); _historyIndex = 0; _noiseFloor = 0.001f; } public void Dispose() { _disposed = true; } } public class AudioCompressorProcessor : IDisposable { private readonly int _sampleRate; private float _attackTime; private float _releaseTime; private float _threshold; private float _ratio; private float _makeupGain; private float _envelope; private float _attackCoeff; private float _releaseCoeff; private bool _isEnabled; private bool _disposed; public bool IsEnabled { get { return _isEnabled; } set { _isEnabled = value; } } public float Threshold { get; set; } = -20f; public float Ratio { get; set; } = 4f; public float AttackTime { get; set; } = 10f; public float ReleaseTime { get; set; } = 100f; public float MakeupGain { get; set; } public AudioCompressorProcessor(int sampleRate, float threshold = -20f, float ratio = 4f, float attackTime = 10f, float releaseTime = 100f, float makeupGain = 0f) { _sampleRate = sampleRate; _threshold = DecibelToLinear(threshold); _ratio = ratio; _attackTime = attackTime; _releaseTime = releaseTime; _makeupGain = DecibelToLinear(makeupGain); CalculateCoefficients(); _envelope = 0f; _isEnabled = true; } private void CalculateCoefficients() { _attackCoeff = Mathf.Exp(-1f / (_attackTime * 0.001f * (float)_sampleRate)); _releaseCoeff = Mathf.Exp(-1f / (_releaseTime * 0.001f * (float)_sampleRate)); } public float[] ProcessAudio(float[] inputAudio) { if (_disposed) { throw new ObjectDisposedException("AudioCompressorProcessor"); } if (!_isEnabled || inputAudio == null || inputAudio.Length == 0) { return inputAudio ?? new float[0]; } float[] array = new float[inputAudio.Length]; for (int i = 0; i < inputAudio.Length; i++) { float num = Mathf.Abs(inputAudio[i]); float num2 = ((num > _envelope) ? _attackCoeff : _releaseCoeff); _envelope = _envelope * num2 + num * (1f - num2); float num3 = CalculateCompressionGain(_envelope); array[i] = inputAudio[i] * num3 * _makeupGain; } return array; } private float CalculateCompressionGain(float inputLevel) { if (inputLevel <= _threshold) { return 1f; } float num = (inputLevel - _threshold) * (1f - 1f / _ratio); return Mathf.Clamp((inputLevel - num) / inputLevel, 0.001f, 1f); } public void UpdateSettings(float threshold, float ratio, float attackTime, float releaseTime, float makeupGain) { Threshold = threshold; Ratio = ratio; AttackTime = attackTime; ReleaseTime = releaseTime; MakeupGain = makeupGain; _threshold = DecibelToLinear(threshold); _ratio = ratio; _attackTime = attackTime; _releaseTime = releaseTime; _makeupGain = DecibelToLinear(makeupGain); CalculateCoefficients(); } public void Reset() { _envelope = 0f; } private static float DecibelToLinear(float decibel) { return Mathf.Pow(10f, decibel / 20f); } private static float LinearToDecibel(float linear) { return 20f * Mathf.Log10(Mathf.Max(linear, 0.0001f)); } public void Dispose() { if (!_disposed) { _disposed = true; } } } public class SpectralSubtractionProcessor : IDisposable { private readonly int _sampleRate; private readonly int _channels; private readonly int _fftSize; private readonly float[] _noiseSpectrum; private readonly Complex[] _fftBuffer; private readonly float[] _magnitudeBuffer; private readonly float[] _phaseBuffer; private readonly float[] _windowFunction; private readonly float _alpha = 2f; private readonly float _beta = 0.01f; private bool _noiseEstimated; private int _frameCount; private bool _disposed; public SpectralSubtractionProcessor(int sampleRate, int channels, int fftSize = 1024) { _sampleRate = sampleRate; _channels = channels; _fftSize = fftSize; _noiseSpectrum = new float[_fftSize / 2 + 1]; _fftBuffer = new Complex[_fftSize]; _magnitudeBuffer = new float[_fftSize / 2 + 1]; _phaseBuffer = new float[_fftSize / 2 + 1]; _windowFunction = CreateHannWindow(_fftSize); } public void ProcessAudio(float[] data, int offset, int length) { if (_disposed) { throw new ObjectDisposedException("SpectralSubtractionProcessor"); } int num = _fftSize / 4; for (int i = 0; i < length - _fftSize; i += num) { ProcessFrame(data, offset + i); } } private void ProcessFrame(float[] data, int offset) { for (int i = 0; i < _fftSize; i++) { if (offset + i < data.Length) { _fftBuffer[i] = new Complex(data[offset + i] * _windowFunction[i], 0.0); } else { _fftBuffer[i] = Complex.Zero; } } SimpleFFT(_fftBuffer); for (int j = 0; j < _fftSize / 2 + 1; j++) { _magnitudeBuffer[j] = (float)_fftBuffer[j].Magnitude; _phaseBuffer[j] = (float)Math.Atan2(_fftBuffer[j].Imaginary, _fftBuffer[j].Real); } if (!_noiseEstimated && _frameCount < 10) { for (int k = 0; k < _magnitudeBuffer.Length; k++) { _noiseSpectrum[k] = (_noiseSpectrum[k] * (float)_frameCount + _magnitudeBuffer[k]) / (float)(_frameCount + 1); } _frameCount++; if (_frameCount >= 10) { _noiseEstimated = true; } } if (_noiseEstimated) { for (int l = 0; l < _magnitudeBuffer.Length; l++) { float val = _magnitudeBuffer[l] - _alpha * _noiseSpectrum[l]; float val2 = _beta * _magnitudeBuffer[l]; _magnitudeBuffer[l] = Math.Max(val, val2); } } for (int m = 0; m < _fftSize / 2 + 1; m++) { _fftBuffer[m] = Complex.FromPolarCoordinates(_magnitudeBuffer[m], _phaseBuffer[m]); if (m > 0 && m < _fftSize / 2) { _fftBuffer[_fftSize - m] = Complex.Conjugate(_fftBuffer[m]); } } SimpleIFFT(_fftBuffer); for (int n = 0; n < _fftSize && offset + n < data.Length; n++) { data[offset + n] = (float)_fftBuffer[n].Real * _windowFunction[n] * 0.5f; } } private float[] CreateHannWindow(int size) { float[] array = new float[size]; for (int i = 0; i < size; i++) { array[i] = 0.5f * (1f - Mathf.Cos(MathF.PI * 2f * (float)i / (float)(size - 1))); } return array; } private void SimpleFFT(Complex[] buffer) { int num = buffer.Length; if (num <= 1) { return; } int i = 1; int num2 = 0; for (; i < num; i++) { int num3 = num >> 1; while ((num2 & num3) != 0) { num2 ^= num3; num3 >>= 1; } num2 ^= num3; if (i < num2) { ref Complex reference = ref buffer[i]; ref Complex reference2 = ref buffer[num2]; Complex complex = buffer[num2]; Complex complex2 = buffer[i]; reference = complex; reference2 = complex2; } } for (int num4 = 2; num4 <= num; num4 <<= 1) { double num5 = Math.PI * 2.0 / (double)num4; Complex complex3 = new Complex(Math.Cos(num5), Math.Sin(num5)); for (int j = 0; j < num; j += num4) { Complex one = Complex.One; for (int k = 0; k < num4 / 2; k++) { Complex complex4 = buffer[j + k]; Complex complex5 = buffer[j + k + num4 / 2] * one; buffer[j + k] = complex4 + complex5; buffer[j + k + num4 / 2] = complex4 - complex5; one *= complex3; } } } } private void SimpleIFFT(Complex[] buffer) { for (int i = 0; i < buffer.Length; i++) { buffer[i] = Complex.Conjugate(buffer[i]); } SimpleFFT(buffer); for (int j = 0; j < buffer.Length; j++) { buffer[j] = Complex.Conjugate(buffer[j]) / (Complex)buffer.Length; } } public void Dispose() { if (!_disposed) { _disposed = true; } } } public class VoiceDuckingProcessor : IDisposable { private readonly int _sampleRate; private readonly int _frameSize; private readonly float _attackTime; private readonly float _releaseTime; private readonly float _threshold; private readonly float _ratio; private readonly float _makeupGain; private float _envelope; private readonly float _attackCoeff; private readonly float _releaseCoeff; private readonly float[] _energyHistory; private readonly int _energyHistorySize = 10; private int _energyHistoryIndex; private float _noiseFloor; private readonly float _noiseFloorAdaptationRate = 0.001f; private readonly float[] _spectralCentroidHistory; private readonly int _spectralHistorySize = 5; private int _spectralHistoryIndex; private bool _isDucking; private float _duckingGain; private readonly float _duckingLevel; private readonly float _duckingSmoothingTime; private readonly float _duckingSmoothingCoeff; private readonly float _voiceFreqMin = 300f; private readonly float _voiceFreqMax = 3400f; private readonly float[] _frequencyBins; private readonly int _fftSize = 512; private bool _disposed; public bool IsDucking => _isDucking; public float CurrentDuckingGain => _duckingGain; public float NoiseFloor => _noiseFloor; public VoiceDuckingProcessor(int sampleRate, int frameSize, float duckingLevel = 0.3f, float threshold = -30f, float ratio = 4f, float attackTime = 0.003f, float releaseTime = 0.1f) { _sampleRate = sampleRate; _frameSize = frameSize; _threshold = DecibelToLinear(threshold); _ratio = ratio; _attackTime = attackTime; _releaseTime = releaseTime; _makeupGain = 1f; _duckingLevel = Mathf.Clamp01(duckingLevel); _duckingSmoothingTime = 0.05f; _attackCoeff = Mathf.Exp(-1f / (_attackTime * (float)_sampleRate)); _releaseCoeff = Mathf.Exp(-1f / (_releaseTime * (float)_sampleRate)); _duckingSmoothingCoeff = Mathf.Exp(-1f / (_duckingSmoothingTime * (float)_sampleRate)); _energyHistory = new float[_energyHistorySize]; _spectralCentroidHistory = new float[_spectralHistorySize]; _noiseFloor = 0.001f; _envelope = 0f; _duckingGain = 1f; _isDucking = false; _frequencyBins = new float[_fftSize / 2 + 1]; } public void ProcessGameAudio(bool voiceDetected) { if (_disposed) { throw new ObjectDisposedException("VoiceDuckingProcessor"); } UpdateDuckingState(voiceDetected); } public float[] ProcessAudio(float[] inputAudio, float[] gameAudio, bool voiceDetected) { if (_disposed) { throw new ObjectDisposedException("VoiceDuckingProcessor"); } if (inputAudio == null || gameAudio == null) { return gameAudio ?? new float[0]; } int num = Math.Min(inputAudio.Length, gameAudio.Length); float[] array = new float[num]; bool flag = AnalyzeVoiceActivity(inputAudio); UpdateDuckingState(flag || voiceDetected); for (int i = 0; i < num; i++) { float num2 = (_isDucking ? _duckingLevel : 1f); float num3 = ((num2 < _envelope) ? _attackCoeff : _releaseCoeff); _envelope = _envelope * num3 + num2 * (1f - num3); array[i] = gameAudio[i] * _envelope * _makeupGain; } return array; } private bool AnalyzeVoiceActivity(float[] audioData) { if (audioData == null || audioData.Length == 0) { return false; } float num = CalculateRMSEnergy(audioData); UpdateNoiseFloor(num); _energyHistory[_energyHistoryIndex] = num; _energyHistoryIndex = (_energyHistoryIndex + 1) % _energyHistorySize; float num2 = CalculateSpectralCentroid(audioData); _spectralCentroidHistory[_spectralHistoryIndex] = num2; _spectralHistoryIndex = (_spectralHistoryIndex + 1) % _spectralHistorySize; bool num3 = num > _noiseFloor * 3f; bool flag = IsVoiceLikeSpectrum(num2); bool flag2 = IsSustainedActivity(); return num3 && flag && flag2; } private float CalculateRMSEnergy(float[] audioData) { float num = 0f; for (int i = 0; i < audioData.Length; i++) { num += audioData[i] * audioData[i]; } return Mathf.Sqrt(num / (float)audioData.Length); } private void UpdateNoiseFloor(float currentEnergy) { if (currentEnergy < _noiseFloor * 2f || _noiseFloor == 0f) { _noiseFloor = _noiseFloor * (1f - _noiseFloorAdaptationRate) + currentEnergy * _noiseFloorAdaptationRate; } _noiseFloor = Mathf.Max(_noiseFloor, 0.0001f); } private float CalculateSpectralCentroid(float[] audioData) { if (audioData.Length < _fftSize) { return 0f; } float num = 0f; float num2 = 0f; int num3 = (int)(_voiceFreqMin * (float)audioData.Length / (float)_sampleRate); int val = (int)(_voiceFreqMax * (float)audioData.Length / (float)_sampleRate); for (int i = num3; i < Math.Min(val, audioData.Length); i++) { float num4 = Mathf.Abs(audioData[i]); float num5 = (float)i * (float)_sampleRate / (float)audioData.Length; num += num5 * num4; num2 += num4; } if (!(num2 > 0f)) { return 0f; } return num / num2; } private bool IsVoiceLikeSpectrum(float spectralCentroid) { if (spectralCentroid >= _voiceFreqMin) { return spectralCentroid <= _voiceFreqMax; } return false; } private bool IsSustainedActivity() { int num = 0; _energyHistory.Average(); for (int i = 0; i < _energyHistorySize; i++) { if (_energyHistory[i] > _noiseFloor * 2f) { num++; } } return (float)num >= (float)_energyHistorySize * 0.3f; } private void UpdateDuckingState(bool voiceActive) { if (voiceActive && !_isDucking) { _isDucking = true; } else if (!voiceActive && _isDucking && !HasRecentVoiceActivity()) { _isDucking = false; } } private bool HasRecentVoiceActivity() { int num = 0; int num2 = Math.Min(3, _energyHistorySize); for (int i = 0; i < num2; i++) { int num3 = (_energyHistoryIndex - 1 - i + _energyHistorySize) % _energyHistorySize; if (_energyHistory[num3] > _noiseFloor * 3f) { num++; } } return num > 0; } public float ProcessSample(float inputSample, float gameAudioSample, bool voiceDetected) { if (_disposed) { throw new ObjectDisposedException("VoiceDuckingProcessor"); } float num = ((_isDucking || voiceDetected) ? _duckingLevel : 1f); _duckingGain = _duckingGain * _duckingSmoothingCoeff + num * (1f - _duckingSmoothingCoeff); return gameAudioSample * _duckingGain; } public void SetDuckingLevel(float level) { Mathf.Clamp01(level); } private static float DecibelToLinear(float decibel) { return Mathf.Pow(10f, decibel / 20f); } private static float LinearToDecibel(float linear) { return 20f * Mathf.Log10(Mathf.Max(linear, 0.0001f)); } public void Dispose() { if (!_disposed) { _disposed = true; } } } public class AdaptiveNoiseFloorEstimator : IDisposable { private readonly int _sampleRate; private readonly float _adaptationRate; private readonly float _minNoiseFloor; private readonly float _maxNoiseFloor; private readonly int _windowSize; private readonly float[] _energyHistory; private int _historyIndex; private float _currentNoiseFloor; private float _longTermAverage; private float _shortTermAverage; private bool _disposed; private int _frameCount; public AdaptiveNoiseFloorEstimator(int sampleRate, float adaptationRate = 0.01f) { _sampleRate = sampleRate; _adaptationRate = adaptationRate; _minNoiseFloor = -60f; _maxNoiseFloor = -20f; _windowSize = Math.Max(1, sampleRate / 100); _energyHistory = new float[100]; _historyIndex = 0; _currentNoiseFloor = -40f; _longTermAverage = 0f; _shortTermAverage = 0f; } public float EstimateNoiseFloor(float[] data, int offset, int length) { if (_disposed) { throw new ObjectDisposedException("AdaptiveNoiseFloorEstimator"); } float num = CalculateRMSEnergy(data, offset, length); float num2 = 20f * Mathf.Log10(Mathf.Max(num, 1E-10f)); _energyHistory[_historyIndex] = num2; _historyIndex = (_historyIndex + 1) % _energyHistory.Length; _frameCount++; UpdateAverages(num2); if (_frameCount > _energyHistory.Length) { float num3 = float.MaxValue; float num4 = 0f; int num5 = Math.Min(_frameCount, _energyHistory.Length); for (int i = 0; i < num5; i++) { num3 = Math.Min(num3, _energyHistory[i]); num4 += _energyHistory[i]; } num4 /= (float)num5; float num6 = CalculateEnergyVariance(num4, num5); float num7 = Mathf.Clamp(num3 + num6 * 0.5f, _minNoiseFloor, _maxNoiseFloor); _currentNoiseFloor = Mathf.Lerp(_currentNoiseFloor, num7, _adaptationRate); if (DetectVoiceActivity(num2, num6)) { _currentNoiseFloor = Mathf.Lerp(_currentNoiseFloor, num7, _adaptationRate * 0.1f); } } return _currentNoiseFloor; } private float CalculateRMSEnergy(float[] data, int offset, int length) { float num = 0f; int num2 = 0; for (int i = offset; i < offset + length && i < data.Length; i++) { num += data[i] * data[i]; num2++; } if (num2 <= 0) { return 0f; } return Mathf.Sqrt(num / (float)num2); } private void UpdateAverages(float energyDb) { _shortTermAverage = _shortTermAverage * 0.9f + energyDb * 0.1f; _longTermAverage = _longTermAverage * 0.99f + energyDb * 0.01f; } private float CalculateEnergyVariance(float avgEnergy, int validSamples) { float num = 0f; for (int i = 0; i < validSamples; i++) { float num2 = _energyHistory[i] - avgEnergy; num += num2 * num2; } if (validSamples <= 1) { return 0f; } return Mathf.Sqrt(num / (float)(validSamples - 1)); } private bool DetectVoiceActivity(float currentEnergyDb, float energyVariance) { float num = _currentNoiseFloor + 6f; bool num2 = currentEnergyDb > num; bool flag = energyVariance > 3f; bool flag2 = _shortTermAverage > _longTermAverage + 3f; if (num2) { return flag || flag2; } return false; } public float GetCurrentNoiseFloor() { return _currentNoiseFloor; } public void Reset() { _historyIndex = 0; _frameCount = 0; _currentNoiseFloor = -40f; _longTermAverage = 0f; _shortTermAverage = 0f; Array.Clear(_energyHistory, 0, _energyHistory.Length); } public void Dispose() { if (!_disposed) { _disposed = true; } } } public class CircularBuffer { private readonly float[] _buffer; private int _writeIndex; private int _readIndex; private int _count; private readonly object _lock = new object(); public int Capacity { get; } public int Count { get { lock (_lock) { return _count; } } } public CircularBuffer(int capacity) { if (capacity <= 0) { throw new ArgumentException("Capacity must be positive", "capacity"); } Capacity = capacity; _buffer = new float[capacity]; _writeIndex = 0; _readIndex = 0; _count = 0; } public void Write(float[] data) { if (data == null || data.Length == 0) { return; } lock (_lock) { foreach (float num in data) { _buffer[_writeIndex] = num; _writeIndex = (_writeIndex + 1) % Capacity; if (_count < Capacity) { _count++; } else { _readIndex = (_readIndex + 1) % Capacity; } } } } public int Read(float[] buffer, int offset, int count) { if (buffer == null) { throw new ArgumentNullException("buffer"); } if (offset < 0 || offset >= buffer.Length) { throw new ArgumentOutOfRangeException("offset"); } if (count < 0 || offset + count > buffer.Length) { throw new ArgumentOutOfRangeException("count"); } lock (_lock) { int num = Math.Min(count, _count); for (int i = 0; i < num; i++) { buffer[offset + i] = _buffer[_readIndex]; _readIndex = (_readIndex + 1) % Capacity; _count--; } return num; } } public void Clear() { lock (_lock) { _writeIndex = 0; _readIndex = 0; _count = 0; Array.Clear(_buffer, 0, _buffer.Length); } } public float[] ToArray() { lock (_lock) { float[] array = new float[_count]; int num = _readIndex; for (int i = 0; i < _count; i++) { array[i] = _buffer[num]; num = (num + 1) % Capacity; } return array; } } } public class FrequencyDomainLoopDetector : IDisposable { private readonly int _sampleRate; private readonly int _fftSize; private readonly int _maxBufferSize; private readonly Complex[] _inputFFT; private readonly Complex[] _outputFFT; private readonly float[] _inputMagnitude; private readonly float[] _outputMagnitude; private readonly float[] _correlationHistory; private readonly float[] _windowFunction; private readonly Queue<float[]> _inputHistory; private readonly Queue<float[]> _outputHistory; private readonly int _historyLength; private int _frameCount; private bool _disposed; private int[] _frequencyBands; private readonly float[] _bandCorrelations; private readonly float _voiceFreqMin = 300f; private readonly float _voiceFreqMax = 3400f; private float _adaptiveThreshold; private readonly float _thresholdAdaptationRate = 0.05f; public FrequencyDomainLoopDetector(int sampleRate, int fftSize = 1024, int maxBufferSize = 48000) { _sampleRate = sampleRate; _fftSize = fftSize; _maxBufferSize = maxBufferSize; _inputFFT = new Complex[_fftSize]; _outputFFT = new Complex[_fftSize]; _inputMagnitude = new float[_fftSize / 2 + 1]; _outputMagnitude = new float[_fftSize / 2 + 1]; _correlationHistory = new float[10]; _windowFunction = CreateHannWindow(_fftSize); _historyLength = 5; _inputHistory = new Queue<float[]>(_historyLength); _outputHistory = new Queue<float[]>(_historyLength); _frameCount = 0; _adaptiveThreshold = 0.3f; InitializeFrequencyBands(); _bandCorrelations = new float[_frequencyBands.Length - 1]; } private void InitializeFrequencyBands() { List<float> source = new List<float> { 0f, 100f, 300f, 800f, 1500f, 3000f, 6000f, 12000f, _sampleRate / 2 }; _frequencyBands = source.Select((float f) => (int)(f * (float)_fftSize / (float)_sampleRate)).ToArray(); } public bool DetectLoop(float[] inputData, float[] outputData, float threshold) { if (_disposed) { throw new ObjectDisposedException("FrequencyDomainLoopDetector"); } StoreFrameHistory(inputData, outputData); if (_inputHistory.Count < 2) { return false; } float num = 0f; bool flag = false; float[][] array = _inputHistory.ToArray(); float[][] array2 = _outputHistory.ToArray(); for (int i = 0; i < array.Length - 1; i++) { for (int j = 0; j < array2.Length; j++) { float num2 = AnalyzeFrequencyCorrelation(array[i], array2[j]); num = Math.Max(num, num2); if (num2 > _adaptiveThreshold && ValidateLoopInFrequencyBands(array[i], array2[j])) { flag = true; } } } UpdateAdaptiveThreshold(num); UpdateCorrelationHistory(num); if (!flag) { flag = DetectSustainedCorrelation(); } return flag; } private void StoreFrameHistory(float[] inputData, float[] outputData) { float[] item = ConvertToMono(inputData); float[] item2 = ConvertToMono(outputData); _inputHistory.Enqueue(item); _outputHistory.Enqueue(item2); while (_inputHistory.Count > _historyLength) { _inputHistory.Dequeue(); _outputHistory.Dequeue(); } _frameCount++; } private float[] ConvertToMono(float[] data) { int num = Math.Min(_fftSize, data.Length); float[] array = new float[num]; for (int i = 0; i < num; i++) { array[i] = ((i < data.Length) ? data[i] : 0f); } return array; } private float AnalyzeFrequencyCorrelation(float[] inputFrame, float[] outputFrame) { for (int i = 0; i < _fftSize; i++) { float num = ((i < inputFrame.Length) ? (inputFrame[i] * _windowFunction[i]) : 0f); float num2 = ((i < outputFrame.Length) ? (outputFrame[i] * _windowFunction[i]) : 0f); _inputFFT[i] = new Complex(num, 0.0); _outputFFT[i] = new Complex(num2, 0.0); } SimpleFFT(_inputFFT); SimpleFFT(_outputFFT); for (int j = 0; j < _inputMagnitude.Length; j++) { _inputMagnitude[j] = (float)_inputFFT[j].Magnitude; _outputMagnitude[j] = (float)_outputFFT[j].Magnitude; } return CalculateSpectralCorrelation(_inputMagnitude, _outputMagnitude); } private float CalculateSpectralCorrelation(float[] spectrum1, float[] spectrum2) { float num = 0f; float num2 = 0f; float num3 = 0f; int num4 = (int)(_voiceFreqMin * (float)_fftSize / (float)_sampleRate); int val = (int)(_voiceFreqMax * (float)_fftSize / (float)_sampleRate); for (int i = num4; i < Math.Min(val, spectrum1.Length); i++) { num += spectrum1[i] * spectrum2[i]; num2 += spectrum1[i] * spectrum1[i]; num3 += spectrum2[i] * spectrum2[i]; } if (num2 > 0f && num3 > 0f) { return num / Mathf.Sqrt(num2 * num3); } return 0f; } private bool ValidateLoopInFrequencyBands(float[] inputFrame, float[] outputFrame) { AnalyzeFrequencyCorrelation(inputFrame, outputFrame); for (int i = 0; i < _frequencyBands.Length - 1; i++) { int num = _frequencyBands[i]; int val = _frequencyBands[i + 1]; float num2 = 0f; float num3 = 0f; float num4 = 0f; for (int j = num; j < Math.Min(val, _inputMagnitude.Length); j++) { num2 += _inputMagnitude[j] * _outputMagnitude[j]; num3 += _inputMagnitude[j] * _inputMagnitude[j]; num4 += _outputMagnitude[j] * _outputMagnitude[j]; } if (num3 > 0f && num4 > 0f) { _bandCorrelations[i] = num2 / Mathf.Sqrt(num3 * num4); } else { _bandCorrelations[i] = 0f; } } int num5 = 0; for (int k = 0; k < _bandCorrelations.Length; k++) { if (_bandCorrelations[k] > 0.4f) { num5++; } } return num5 >= 2; } private void UpdateAdaptiveThreshold(float currentCorrelation) { float num = 0.3f; float num2 = _correlationHistory.Where((float c) => c > 0f).DefaultIfEmpty(0f).Average(); if (num2 < 0.1f) { num = 0.25f; } else if (num2 > 0.5f) { num = 0.4f; } _adaptiveThreshold = Mathf.Lerp(_adaptiveThreshold, num, _thresholdAdaptationRate); } private void UpdateCorrelationHistory(float correlation) { for (int num = _correlationHistory.Length - 1; num > 0; num--) { _correlationHistory[num] = _correlationHistory[num - 1]; } _correlationHistory[0] = correlation; } private bool DetectSustainedCorrelation() { int num = 0; float num2 = _adaptiveThreshold * 0.8f; for (int i = 0; i < Math.Min(5, _correlationHistory.Length); i++) { if (_correlationHistory[i] > num2) { num++; } } return num >= 3; } private float[] CreateHannWindow(int size) { float[] array = new float[size]; for (int i = 0; i < size; i++) { array[i] = 0.5f * (1f - Mathf.Cos(MathF.PI * 2f * (float)i / (float)(size - 1))); } return array; } private void SimpleFFT(Complex[] buffer) { int num = buffer.Length; if (num <= 1) { return; } int i = 1; int num2 = 0; for (; i < num; i++) { int num3 = num >> 1; while ((num2 & num3) != 0) { num2 ^= num3; num3 >>= 1; } num2 ^= num3; if (i < num2) { ref Complex reference = ref buffer[i]; ref Complex reference2 = ref buffer[num2]; Complex complex = buffer[num2]; Complex complex2 = buffer[i]; reference = complex; reference2 = complex2; } } for (int num4 = 2; num4 <= num; num4 <<= 1) { double num5 = Math.PI * 2.0 / (double)num4; Complex complex3 = new Complex(Math.Cos(num5), Math.Sin(num5)); for (int j = 0; j < num; j += num4) { Complex one = Complex.One; for (int k = 0; k < num4 / 2; k++) { Complex complex4 = buffer[j + k]; Complex complex5 = buffer[j + k + num4 / 2] * one; buffer[j + k] = complex4 + complex5; buffer[j + k + num4 / 2] = complex4 - complex5; one *= complex3; } } } } public void Dispose() { if (!_disposed) { _disposed = true; } } } [BepInPlugin("com.xenoveni.lethalmic.Static", "LethalMic (Static)", "2.0.0")] public class LethalMicStatic : BaseUnityPlugin { [HarmonyPatch(typeof(StartOfRound), "Start")] public static class StartOfRound_Start_Patch { public static void Postfix() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) try { GetLogger().LogInfo((object)"Game started - initializing audio"); ManualLogSource logger = GetLogger(); Scene activeScene = SceneManager.GetActiveScene(); logger.LogInfo((object)("Scene name: " + ((Scene)(ref activeScene)).name)); StartRecording(); } catch (Exception ex) { GetLogger().LogError((object)$"Error in game start patch: {ex}"); GetLogger().LogError((object)("Stack trace: " + ex.StackTrace)); errorCount++; } } } [HarmonyPatch(typeof(StartOfRound), "OnDestroy")] public static class StartOfRound_OnDestroy_Patch { public static void Prefix() { try { GetLogger().LogInfo((object)"Game quitting - stopping audio"); GetLogger().LogInfo((object)$"Total audio frames processed: {audioFrameCount}"); GetLogger().LogInfo((object)$"Total errors encountered: {errorCount}"); StopRecording(); } catch (Exception ex) { GetLogger().LogError((object)$"Error in game quit patch: {ex}"); GetLogger().LogError((object)("Stack trace: " + ex.StackTrace)); errorCount++; } } } public enum EchoSuppressionMode { Headphones, StereoMix, WasapiLoopback } private static ManualLogSource Logger; private static Harmony HarmonyInstance; private static ConfigFile ConfigFile; private static bool IsInitialized = false; private static DateTime LastLogTime = DateTime.MinValue; private static int LogCounter = 0; private static string selectedDevice; private static bool isRecording = false; private static float currentMicrophoneLevel = -60f; private static float peakMicLevel = -60f; private static bool voiceDetected = false; private static float noiseFloor = -60f; private static float cpuUsage = 0f; private static int audioFrameCount = 0; private static int errorCount = 0; private static ConfigEntry<bool> EnableMod; private static ConfigEntry<float> MicrophoneGain; private static ConfigEntry<string> InputDevice; private static ConfigEntry<bool> NoiseGate; private static ConfigEntry<float> NoiseGateThreshold; private static ConfigEntry<bool> DebugLogging; private static ConfigEntry<bool> Compression; private static ConfigEntry<float> CompressionRatio; private static ConfigEntry<float> AttackTime; private static ConfigEntry<float> ReleaseTime; private static ConfigEntry<EchoSuppressionMode> EchoMode; private static GameObject uiObject; private static LethalMicUI uiInstance; private static GameObject updaterObject; private static DateTime _lastSummaryLog = DateTime.MinValue; private static HashSet<string> _arrayCopyErrors = new HashSet<string>(); private static DateTime _lastArrayCopyLog = DateTime.MinValue; private static DateTime _lastAudioLog = DateTime.MinValue; private void Awake() { //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Expected O, but got Unknown try { Logger = Logger.CreateLogSource("LethalMic"); if (Logger == null) { Debug.LogError((object)"[LethalMic] Failed to create logger!"); return; } GetLogger().LogInfo((object)"Initializing LethalMic static class..."); ConfigFile = ((BaseUnityPlugin)this).Config; EnableMod = ConfigFile.Bind<bool>("General", "EnableMod", true, "Enable/disable the LethalMic mod"); MicrophoneGain = ConfigFile.Bind<float>("Audio", "MicrophoneGain", 1f, "Microphone input gain (0.0 to 5.0)"); InputDevice = ConfigFile.Bind<string>("Audio", "InputDevice", "", "Preferred input device (empty for default)"); NoiseGate = ConfigFile.Bind<bool>("Audio", "NoiseGate", true, "Enable noise gate"); NoiseGateThreshold = ConfigFile.Bind<float>("Audio", "NoiseGateThreshold", 0.05f, "Noise gate threshold (0.0 to 1.0)"); DebugLogging = ConfigFile.Bind<bool>("Debug", "DebugLogging", false, "Enable debug logging"); Compression = ConfigFile.Bind<bool>("Audio", "Compression", true, "Enable audio compression"); CompressionRatio = ConfigFile.Bind<float>("Audio", "CompressionRatio", 10f, "Audio compression ratio (1:1 to 20:1)"); AttackTime = ConfigFile.Bind<float>("Audio", "AttackTime", 2f, "Compressor attack time in milliseconds (0-100)"); ReleaseTime = ConfigFile.Bind<float>("Audio", "ReleaseTime", 50f, "Compressor release time in milliseconds (0-1000)"); EchoMode = ConfigFile.Bind<EchoSuppressionMode>("Audio", "EchoSuppressionMode", EchoSuppressionMode.Headphones, "Echo/Noise Suppression Mode: Headphones, StereoMix, WasapiLoopback"); GetLogger().LogInfo((object)$"Configuration loaded - Enabled: {EnableMod.Value}, Gain: {MicrophoneGain.Value}"); InitializeAudio(); HarmonyInstance = new Harmony("com.xenoveni.lethalmic"); GetLogger().LogInfo((object)"Applying Harmony patches..."); HarmonyInstance.PatchAll(); GetLogger().LogInfo((object)"Harmony patches applied successfully"); VoiceChatPatch.Initialize(Logger); InitializeUI(); IsInitialized = true; GetLogger().LogInfo((object)"Successfully initialized with static implementation"); GetLogger().LogInfo((object)$"Initialization completed at {DateTime.Now}"); } catch (Exception arg) { Debug.LogError((object)$"[LethalMic] Error in Awake: {arg}"); if (Logger != null) { Logger.LogError((object)$"Error in Awake: {arg}"); } } } private static void InitializeAudio() { if (!EnableMod.Value) { GetLogger().LogInfo((object)"Mod disabled in configuration"); return; } try { GetLogger().LogInfo((object)"Initializing audio system..."); StaticAudioManager.Initialize(Logger); string[] devices = Microphone.devices; GetLogger().LogInfo((object)$"Found {devices.Length} microphone devices"); if (devices.Length == 0) { GetLogger().LogError((object)"No microphone devices found. Please check your system settings and ensure a microphone is connected."); if ((Object)(object)uiInstance != (Object)null) { uiInstance.UpdateMicStatus("None", "No Devices", 0f); } return; } string[] array = devices; foreach (string text in array) { GetLogger().LogInfo((object)("Available device: " + text)); } if (string.IsNullOrEmpty(InputDevice.Value)) { selectedDevice = devices[0]; GetLogger().LogInfo((object)("No device selected in config, using default device: " + selectedDevice)); } else if (devices.Contains(InputDevice.Value)) { selectedDevice = InputDevice.Value; GetLogger().LogInfo((object)("Using configured device: " + selectedDevice)); } else { GetLogger().LogWarning((object)("Configured device '" + InputDevice.Value + "' not found, falling back to default device: " + devices[0])); selectedDevice = devices[0]; } StaticAudioManager.StartRecording(); GetLogger().LogInfo((object)("Successfully initialized audio system with device: " + selectedDevice)); if ((Object)(object)uiInstance != (Object)null) { uiInstance.UpdateMicStatus(selectedDevice, "Connected", 0f); } } catch (Exception ex) { GetLogger().LogError((object)$"Error initializing audio system: {ex}"); GetLogger().LogError((object)("Stack trace: " + ex.StackTrace)); errorCount++; if ((Object)(object)uiInstance != (Object)null) { uiInstance.UpdateMicStatus("Error", "Initialization Failed", 0f); } } } private static void InitializeUI() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown try { GetLogger().LogInfo((object)"Initializing UI..."); uiObject = new GameObject("LethalMicUI"); ((Object)uiObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)uiObject); uiInstance = uiObject.AddComponent<LethalMicUI>(); uiInstance.Initialize(Logger, ConfigFile); if ((Object)(object)updaterObject == (Object)null) { updaterObject = new GameObject("LethalMicUpdater"); ((Object)updaterObject).hideFlags = (HideFlags)61; Object.DontDestroyOnLoad((Object)(object)updaterObject); updaterObject.AddComponent<LethalMicUpdater>(); } GetLogger().LogInfo((object)"UI initialized successfully"); } catch (Exception arg) { GetLogger().LogError((object)$"Failed to initialize UI: {arg}"); } } public static void ToggleUI() { try { ManualLogSource logger = GetLogger(); if (logger != null) { logger.LogInfo((object)"[UI] ToggleUI called - attempting to toggle UI visibility"); } if ((Object)(object)uiInstance != (Object)null) { ManualLogSource logger2 = GetLogger(); if (logger2 != null) { logger2.LogInfo((object)"[UI] uiInstance is not null, calling ToggleVisibility()"); } uiInstance.ToggleVisibility(); ManualLogSource logger3 = GetLogger(); if (logger3 != null) { logger3.LogInfo((object)$"[UI] UI visibility is now: {uiInstance.IsVisible}"); } } else { ManualLogSource logger4 = GetLogger(); if (logger4 != null) { logger4.LogWarning((object)"[UI] ToggleUI called but UI instance is null"); } InitializeUI(); } } catch (Exception arg) { ManualLogSource logger5 = GetLogger(); if (logger5 != null) { logger5.LogError((object)$"[UI] Error in ToggleUI: {arg}"); } } } private static void LogDiagnostic(string message, LogLevel level = 16) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0082: Unknown result type (might be due to invalid IL or missing references) try { if (Logger == null) { Debug.Log((object)("[LethalMic] " + message)); } else { if ((int)level == 32 && DebugLogging != null && !DebugLogging.Value) { return; } DateTime now = DateTime.Now; if ((now - LastLogTime).TotalMilliseconds < 100.0) { LogCounter++; if (LogCounter > 100) { return; } } else { LogCounter = 0; LastLogTime = now; } Logger.Log(level, (object)$"[{now:HH:mm:ss.fff}] {message}"); } } catch (Exception arg) { Debug.LogError((object)$"[LethalMic] Error in LogDiagnostic: {arg}"); } } public static void StartRecording() { if (!EnableMod.Value) { GetLogger().LogInfo((object)"StartRecording skipped - mod disabled"); return; } try { GetLogger().LogInfo((object)"Starting microphone recording..."); StaticAudioManager.StartRecording(); isRecording = true; GetLogger().LogInfo((object)"Microphone recording started successfully"); } catch (Exception ex) { GetLogger().LogError((object)$"Failed to start recording: {ex}"); GetLogger().LogError((object)("Stack trace: " + ex.StackTrace)); errorCount++; } } public static void StopRecording() { if (!isRecording) { GetLogger().LogInfo((object)"StopRecording skipped - not recording"); return; } try { GetLogger().LogInfo((object)"Stopping microphone recording..."); StaticAudioManager.StopRecording(); isRecording = false; GetLogger().LogInfo((object)"Microphone recording stopped"); } catch (Exception ex) { GetLogger().LogError((object)$"Failed to stop recording: {ex}"); GetLogger().LogError((object)("Stack trace: " + ex.StackTrace)); errorCount++; } } public static void UpdateAudio() { if (!isRecording) { return; } try { StaticAudioManager.ProcessAudio(); currentMicrophoneLevel = StaticAudioManager.GetCurrentMicrophoneLevel(); peakMicLevel = StaticAudioManager.GetPeakMicrophoneLevel(); voiceDetected = StaticAudioManager.IsVoiceDetected(); noiseFloor = StaticAudioManager.GetNoiseFloor(); cpuUsage = StaticAudioManager.GetCPUUsage(); audioFrameCount++; } catch (Exception arg) { GetLogger().LogError((object)$"Error in UpdateAudio: {arg}"); errorCount++; } } public static float GetCurrentMicrophoneLevel() { return currentMicrophoneLevel; } public static bool IsVoiceDetected() { return voiceDetected; } public static float GetNoiseFloor() { return noiseFloor; } public static float GetCPUUsage() { return cpuUsage; } public static int GetErrorCount() { return errorCount; } public static int GetAudioFrameCount() { return audioFrameCount; } public static bool IsUIVisible() { if ((Object)(object)uiInstance != (Object)null) { return uiInstance.IsVisible; } return false; } public static void Cleanup() { try { GetLogger().LogInfo((object)"Cleaning up LethalMicStatic..."); StaticAudioManager.Cleanup(); if ((Object)(object)uiInstance != (Object)null) { Object.Destroy((Object)(object)((Component)uiInstance).gameObject); uiInstance = null; } GetLogger().LogInfo((object)"LethalMicStatic cleaned up"); } catch (Exception arg) { GetLogger().LogError((object)$"Error during cleanup: {arg}"); } } private static ManualLogSource GetLogger() { return Logger; } public static float GetMicrophoneGain() { return MicrophoneGain?.Value ?? 1f; } public static void SetMicrophoneGain(float value) { if (MicrophoneGain != null) { MicrophoneGain.Value = value; StaticAudioManager.UpdateProcessorSettings(); } } public static float GetNoiseGateThreshold() { return NoiseGateThreshold?.Value ?? 0.05f; } public static void SetNoiseGateThreshold(float value) { if (NoiseGateThreshold != null) { NoiseGateThreshold.Value = value; StaticAudioManager.UpdateProcessorSettings(); } } public static bool GetNoiseGateEnabled() { return NoiseGate?.Value ?? true; } public static void SetNoiseGateEnabled(bool value) { if (NoiseGate != null) { NoiseGate.Value = value; StaticAudioManager.UpdateProcessorSettings(); } } public static void SetInputDevice(string deviceName) { if (InputDevice != null) { InputDevice.Value = deviceName; } selectedDevice = deviceName; StaticAudioManager.SetInputDevice(deviceName); } public static string GetInputDevice() { return selectedDevice; } public static bool GetCompressionEnabled() { return Compression?.Value ?? true; } public static void SetCompressionEnabled(bool value) { if (Compression != null) { Compression.Value = value; StaticAudioManager.UpdateProcessorSettings(); } } public static float GetCompressionRatio() { return CompressionRatio?.Value ?? 10f; } public static void SetCompressionRatio(float value) { if (CompressionRatio != null) { CompressionRatio.Value = value; StaticAudioManager.UpdateProcessorSettings(); } } public static float GetAttackTime() { return AttackTime?.Value ?? 2f; } public static void SetAttackTime(float value) { if (AttackTime != null) { AttackTime.Value = value; StaticAudioManager.UpdateProcessorSettings(); } } public static float GetReleaseTime() { return ReleaseTime?.Value ?? 50f; } public static void SetReleaseTime(float value) { if (ReleaseTime != null) { ReleaseTime.Value = value; StaticAudioManager.UpdateProcessorSettings(); } } public static LethalMicUI GetUIIInstance() { return uiInstance; } private static void UpdateProcessorSettings() { switch (EchoMode.Value) { case EchoSuppressionMode.Headphones: StaticAudioManager.SetAggressiveSuppression(); break; case EchoSuppressionMode.StereoMix: StaticAudioManager.SetStereoMixSuppression(); break; case EchoSuppressionMode.WasapiLoopback: StaticAudioManager.SetWasapiSuppression(); break; } } public static EchoSuppressionMode GetEchoSuppressionMode() { return EchoMode?.Value ?? EchoSuppressionMode.Headphones; } public static void SetEchoSuppressionMode(EchoSuppressionMode mode) { if (EchoMode != null) { EchoMode.Value = mode; UpdateProcessorSettings(); } } } public class LethalMicUpdater : MonoBehaviour { private void Update() { LethalMicStatic.UpdateAudio(); } } public class LethalMicInputActions : LcInputActions { public static readonly LethalMicInputActions Instance = new LethalMicInputActions(); public InputAction ToggleUI => ((LcInputActions)this).Asset["toggleui"]; public InputAction QuickMute => ((LcInputActions)this).Asset["quickmute"]; public InputAction PushToTalk => ((LcInputActions)this).Asset["pushtotalk"]; public LethalMicInputActions() { Logger.CreateLogSource("LethalMicInputActions").LogInfo((object)"[INPUT] LethalMicInputActions instance created"); } public override void CreateInputActions(in InputActionMapBuilder builder) { builder.NewActionBinding().WithActionId("toggleui").WithActionType((InputActionType)1) .WithKeyboardControl((KeyboardControl)33) .WithBindingName("Toggle UI") .Finish(); Logger.CreateLogSource("LethalMicInputActions").LogInfo((object)"[INPUT] ToggleUI action bound to M key"); builder.NewActionBinding().WithActionId("quickmute").WithActionType((InputActionType)1) .WithKeyboardControl((KeyboardControl)106) .WithBindingName("Quick Mute") .Finish(); builder.NewActionBinding().WithActionId("pushtotalk").WithActionType((InputActionType)1) .WithKeyboardControl((KeyboardControl)107) .WithBindingName("Push to Talk") .Finish(); } } public class PerformanceOptimizer : IDisposable { private readonly int _sampleRate; private readonly int _channels; private bool _disposed; private readonly Stopwatch _performanceTimer = new Stopwatch(); private readonly Queue<float> _cpuUsageHistory = new Queue<float>(); private const int MAX_HISTORY_SIZE = 100; private bool _rnnoiseDllAvailable; private bool _opusLibraryOptimized; private readonly Dictionary<int, byte[]> _opusBufferPool = new Dictionary<int, byte[]>(); private readonly object _bufferPoolLock = new object(); public bool IsRNNoiseAvailable => _rnnoiseDllAvailable; public bool IsOpusOptimized => _opusLibraryOptimized; public PerformanceOptimizer(int sampleRate, int channels) { _sampleRate = sampleRate; _channels = channels; InitializeExternalLibraries(); OptimizeCodecSettings(); } private void InitializeExternalLibraries() { try { IntPtr intPtr = rnnoise_create(IntPtr.Zero); if (intPtr != IntPtr.Zero) { rnnoise_destroy(intPtr); _rnnoiseDllAvailable = true; Debug.Log((object)"RNNoise library successfully loaded"); } } catch (DllNotFoundException) { Debug.LogWarning((object)"RNNoise library not found. Noise suppression will use fallback algorithms."); _rnnoiseDllAvailable = false; } catch (Exception ex2) { Debug.LogWarning((object)("RNNoise initialization failed: " + ex2.Message)); _rnnoiseDllAvailable = false; } } private void OptimizeCodecSettings() { try { int[] array = new int[5] { 120, 240, 480, 960, 1920 }; lock (_bufferPoolLock) { int[] array2 = array; foreach (int num in array2) { int num2 = num * _channels * 4; _opusBufferPool[num] = new byte[num2]; } } _opusLibraryOptimized = true; Debug.Log((object)"Opus codec buffers optimized"); } catch (Exception ex) { Debug.LogWarning((object)("Opus optimization failed: " + ex.Message)); _opusLibraryOptimized = false; } } public void StartPerformanceMonitoring() { _performanceTimer.Start(); } public float StopPerformanceMonitoring() { _performanceTimer.Stop(); float num = (float)_performanceTimer.Elapsed.TotalMilliseconds; _cpuUsageHistory.Enqueue(num); if (_cpuUsageHistory.Count > 100) { _cpuUsageHistory.Dequeue(); } _performanceTimer.Reset(); return num; } public float GetAverageCpuUsage() { if (_cpuUsageHistory.Count == 0) { return 0f; } float num = 0f; foreach (float item in _cpuUsageHistory) { num += item; } return num / (float)_cpuUsageHistory.Count; } public byte[] GetOptimizedOpusBuffer(int frameSize) { lock (_bufferPoolLock) { if (_opusBufferPool.TryGetValue(frameSize, out var value)) { return value; } byte[] array = new byte[frameSize * _channels * 4]; _opusBufferPool[frameSize] = array; return array; } } public PerformanceRecommendation GetPerformanceRecommendation() { float averageCpuUsage = GetAverageCpuUsage(); if (averageCpuUsage > 10f) { return new PerformanceRecommendation { RecommendedFFTSize = 512, RecommendedQuality = 1, DisableAIProcessing = true, Message = "High CPU usage detected. Consider reducing processing quality." }; } if (averageCpuUsage > 5f) { return new PerformanceRecommendation { RecommendedFFTSize = 1024, RecommendedQuality = 2, DisableAIProcessing = false, Message = "Moderate CPU usage. Current settings are acceptable." }; } return new PerformanceRecommendation { RecommendedFFTSize = 2048, RecommendedQuality = 3, DisableAIProcessing = false, Message = "Low CPU usage. You can increase quality settings." }; } public void OptimizeForLowEndSystem() { Debug.Log((object)"Applying low-end system optimizations"); lock (_bufferPoolLock) { _opusBufferPool.Clear(); _opusBufferPool[480] = new byte[480 * _channels * 2]; } GC.Collect();