Decompiled source of LethalMic v1.5.3
LethalMic.dll
Decompiled 2 weeks 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();