Decompiled source of VoiceCompression v1.0.2

plugins/VoiceCompression/VoiceCompression.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using VoiceCompression.Audio;
using VoiceCompression.Dsp;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("RED")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Incoming voice compression with native R.E.P.O. audio settings.")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2")]
[assembly: AssemblyProduct("VoiceCompression")]
[assembly: AssemblyTitle("VoiceCompression")]
[assembly: AssemblyVersion("1.0.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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 VoiceCompression
{
	public sealed class CompressorConfiguration
	{
		private static CompressorSettings _current = new CompressorSettings(enabled: false);

		public static CompressorConfiguration Instance { get; private set; }

		public static CompressorSettings Current => Volatile.Read(in _current);

		public ConfigEntry<bool> Enabled { get; }

		public ConfigEntry<float> ThresholdDb { get; }

		public ConfigEntry<float> Ratio { get; }

		public ConfigEntry<float> AttackMs { get; }

		public ConfigEntry<float> ReleaseMs { get; }

		public ConfigEntry<float> MakeupGainDb { get; }

		public ConfigEntry<float> KneeDb { get; }

		public ConfigEntry<float> OutputCeilingDb { get; }

		public CompressorConfiguration(ConfigFile config)
		{
			Enabled = config.Bind<bool>("Incoming Voice", "Enabled", true, "Compress other players' incoming voices. Your microphone, music, and sound effects are unaffected.");
			ThresholdDb = Bind(config, "ThresholdDb", -18f, -60f, 0f, "Level where compression starts, in dBFS. Lower values compress more speech.");
			Ratio = Bind(config, "Ratio", 4f, 1f, 20f, "Compression ratio above the threshold. 4 means a 4 dB rise becomes 1 dB; 1 disables gain reduction.");
			AttackMs = Bind(config, "AttackMs", 5f, 0.1f, 100f, "How quickly the compressor responds to loud speech, in milliseconds.");
			ReleaseMs = Bind(config, "ReleaseMs", 150f, 10f, 1000f, "How quickly gain recovers after loud speech, in milliseconds.");
			MakeupGainDb = Bind(config, "MakeupGainDb", 3f, 0f, 24f, "Gain applied after compression, in dB. Also raises quiet speech and background noise.");
			KneeDb = Bind(config, "KneeDb", 6f, 0f, 24f, "Width of the soft transition around the threshold, in dB. 0 selects a hard knee.");
			OutputCeilingDb = Bind(config, "OutputCeilingDb", -1f, -12f, 0f, "Peak ceiling in dBFS at each voice filter output, before game volume and mixer processing. This is not a master-output limiter.");
			Enabled.SettingChanged += OnSettingChanged;
			ThresholdDb.SettingChanged += OnSettingChanged;
			Ratio.SettingChanged += OnSettingChanged;
			AttackMs.SettingChanged += OnSettingChanged;
			ReleaseMs.SettingChanged += OnSettingChanged;
			MakeupGainDb.SettingChanged += OnSettingChanged;
			KneeDb.SettingChanged += OnSettingChanged;
			OutputCeilingDb.SettingChanged += OnSettingChanged;
			Instance = this;
			Publish();
		}

		private static ConfigEntry<float> Bind(ConfigFile config, string key, float value, float min, float max, string description)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			return config.Bind<float>("Incoming Voice", key, value, new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>()));
		}

		private void OnSettingChanged(object sender, EventArgs args)
		{
			Publish();
		}

		private void Publish()
		{
			Volatile.Write(ref _current, new CompressorSettings(Enabled.Value, ThresholdDb.Value, Ratio.Value, AttackMs.Value, ReleaseMs.Value, MakeupGainDb.Value, KneeDb.Value, OutputCeilingDb.Value));
		}

		internal static void Bypass()
		{
			Volatile.Write(ref _current, new CompressorSettings(enabled: false));
		}
	}
	[BepInPlugin("redos.voicecompression", "Voice Compression", "1.0.2")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "redos.voicecompression";

		public const string Name = "Voice Compression";

		public const string Version = "1.0.2";

		private Harmony _harmony;

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			new CompressorConfiguration(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("redos.voicecompression");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			IncomingVoiceCompression.AttachExisting();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Voice Compression 1.0.2 loaded. Incoming voices only; controls are in Settings > Audio.");
		}

		private void OnDestroy()
		{
			CompressorConfiguration.Bypass();
			IncomingVoiceCompression.RemoveAll();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
}
namespace VoiceCompression.UI
{
	[HarmonyPatch(typeof(MenuPage), "Start")]
	internal static class AudioPagePatch
	{
		private static void Postfix(MenuPage __instance)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)__instance.menuPageIndex == 5 && !Object.op_Implicit((Object)(object)((Component)__instance).GetComponent<NativeAudioSettings>()))
			{
				((Component)__instance).gameObject.AddComponent<NativeAudioSettings>();
			}
		}
	}
	[HarmonyPatch(typeof(MenuPageSettingsPage), "ResetSettings")]
	internal static class AudioDefaultsPatch
	{
		private static void Prefix(MenuPageSettingsPage __instance)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			CompressorConfiguration instance = CompressorConfiguration.Instance;
			if ((int)__instance.settingType == 0 && instance != null)
			{
				Reset<bool>(instance.Enabled);
				Reset<float>(instance.ThresholdDb);
				Reset<float>(instance.Ratio);
				Reset<float>(instance.AttackMs);
				Reset<float>(instance.ReleaseMs);
				Reset<float>(instance.MakeupGainDb);
				Reset<float>(instance.KneeDb);
				Reset<float>(instance.OutputCeilingDb);
			}
		}

		private static void Reset<T>(ConfigEntry<T> entry)
		{
			entry.Value = (T)((ConfigEntryBase)entry).DefaultValue;
		}
	}
	internal sealed class NativeAudioSettings : MonoBehaviour
	{
		private sealed class SliderBinding
		{
			public readonly MenuSlider Slider;

			public readonly int Segments;

			public readonly Func<int> Read;

			public readonly Action<int> Write;

			public int PreviousIndex = -1;

			public SliderBinding(MenuSlider slider, int segments, Func<int> read, Action<int> write)
			{
				Slider = slider;
				Segments = segments;
				Read = read;
				Write = write;
			}

			public void Refresh()
			{
				PreviousIndex = Read();
				Slider.SetBar((float)PreviousIndex / (float)Segments);
				Slider.SetBarScaleInstant();
			}
		}

		private static readonly ManualLogSource Log = Logger.CreateLogSource("VoiceCompression.AudioSettings");

		private static readonly FieldInfo CurrentValue = AccessTools.Field(typeof(MenuSlider), "currentValue");

		private static readonly FieldInfo SettingsValue = AccessTools.Field(typeof(MenuSlider), "settingsValue");

		private static readonly MethodInfo RecalculateScrollHeight = AccessTools.Method(typeof(MenuScrollBox), "RecalculateScrollHeight", (Type[])null, (Type[])null);

		private const string ControlPrefix = "VoiceCompression - ";

		private readonly List<SliderBinding> bindings = new List<SliderBinding>();

		private readonly List<GameObject> createdObjects = new List<GameObject>();

		private readonly Dictionary<GameObject, Vector3> intendedPositions = new Dictionary<GameObject, Vector3>();

		private GameObject staging;

		private MenuPage page;

		private MenuScrollBox scrollBox;

		private bool ready;

		private IEnumerator Start()
		{
			yield return null;
			if (Prepare())
			{
				yield return null;
				if (Activate())
				{
					yield return null;
					Finish();
				}
			}
		}

		private bool Prepare()
		{
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Expected O, but got Unknown
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Expected O, but got Unknown
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0381: Unknown result type (might be due to invalid IL or missing references)
			//IL_0388: Expected O, but got Unknown
			//IL_038c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0391: Unknown result type (might be due to invalid IL or missing references)
			//IL_039c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				page = ((Component)this).GetComponent<MenuPage>();
				scrollBox = ((IEnumerable<MenuScrollBox>)((Component)this).GetComponentsInChildren<MenuScrollBox>(true)).FirstOrDefault((Func<MenuScrollBox, bool>)((MenuScrollBox box) => Object.op_Implicit((Object)(object)box.scroller) && (Object)(object)((Component)box).GetComponentInParent<MenuPage>() == (Object)(object)page));
				if (!Object.op_Implicit((Object)(object)scrollBox) || !Object.op_Implicit((Object)(object)scrollBox.scroller) || CompressorConfiguration.Instance == null)
				{
					throw new InvalidOperationException("The native Audio scroll box or compressor configuration is unavailable.");
				}
				if (CurrentValue == null || SettingsValue == null || RecalculateScrollHeight == null)
				{
					throw new MissingMemberException("The game's native slider/scroll API has changed.");
				}
				MenuSlider val = ((IEnumerable<MenuSlider>)((Component)scrollBox.scroller).GetComponentsInChildren<MenuSlider>(true)).FirstOrDefault((Func<MenuSlider, bool>)((MenuSlider slider) => Object.op_Implicit((Object)(object)((Component)slider).GetComponent<MenuSetting>()) && !Object.op_Implicit((Object)(object)((Component)slider).GetComponent<MenuSliderPlayerMicGain>()) && !Object.op_Implicit((Object)(object)((Component)slider).GetComponent<MenuSliderMicrophone>()) && !Object.op_Implicit((Object)(object)((Component)slider).GetComponent<MenuInputPercentSetting>()) && Object.op_Implicit((Object)(object)slider.elementNameText) && Object.op_Implicit((Object)(object)slider.segmentText) && Object.op_Implicit((Object)(object)slider.segmentMaskText) && Object.op_Implicit((Object)(object)slider.barSize) && Object.op_Implicit((Object)(object)slider.settingsBar) && Object.op_Implicit((Object)(object)slider.extraBar)));
				if (!Object.op_Implicit((Object)(object)val))
				{
					throw new InvalidOperationException("No compatible native Audio volume slider was found.");
				}
				staging = new GameObject("VoiceCompression - Staging", new Type[1] { typeof(RectTransform) });
				staging.SetActive(false);
				staging.transform.SetParent(((Component)this).transform, false);
				float num = 0f;
				Rect rect;
				foreach (Transform item in (Transform)scrollBox.scroller)
				{
					Transform val2 = item;
					RectTransform val3 = (RectTransform)(object)((val2 is RectTransform) ? val2 : null);
					if (val3 != null && !((Object)val2).name.StartsWith("VoiceCompression - ", StringComparison.Ordinal))
					{
						float num2 = num;
						float y = ((Transform)val3).localPosition.y;
						rect = val3.rect;
						num = Mathf.Min(num2, y - ((Rect)(ref rect)).height * val3.pivot.y);
					}
				}
				float x = ((Component)val).transform.localPosition.x;
				rect = ((RectTransform)((Component)val).transform).rect;
				float num3 = Mathf.Max(((Rect)(ref rect)).height + 12f, 42f);
				float num4 = num - 40f;
				AddHeading(val, x, num4);
				num4 -= 44f;
				CompressorConfiguration instance = CompressorConfiguration.Instance;
				AddToggle(val, "Voice compression", instance.Enabled, x, num4);
				num4 -= num3;
				AddSlider(val, "Threshold", instance.ThresholdDb, -60f, 0f, 1f, "0' dB'", x, num4);
				num4 -= num3;
				AddSlider(val, "Ratio", instance.Ratio, 1f, 20f, 0.1f, "0.0':1'", x, num4);
				num4 -= num3;
				AddSlider(val, "Attack", instance.AttackMs, 0.1f, 100f, 0.1f, "0.0' ms'", x, num4);
				num4 -= num3;
				AddSlider(val, "Release", instance.ReleaseMs, 10f, 1000f, 10f, "0' ms'", x, num4);
				num4 -= num3;
				AddSlider(val, "Makeup gain", instance.MakeupGainDb, 0f, 24f, 0.5f, "0.0' dB'", x, num4);
				num4 -= num3;
				AddSlider(val, "Soft knee", instance.KneeDb, 0f, 24f, 1f, "0' dB'", x, num4);
				num4 -= num3;
				AddSlider(val, "Output ceiling", instance.OutputCeilingDb, -12f, 0f, 0.5f, "0.0' dB'", x, num4);
				RectTransform val4 = (RectTransform)((Component)val).transform;
				float num5 = num4;
				rect = val4.rect;
				num4 = num5 - (((Rect)(ref rect)).height * val4.pivot.y + 8f);
				AddGuide(val, x, num4);
				return true;
			}
			catch (Exception exception)
			{
				Fail(exception);
				return false;
			}
		}

		private void AddHeading(MenuSlider template, float x, float y)
		{
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(((Component)template.elementNameText).gameObject, staging.transform, false);
			((Object)val).name = "VoiceCompression - Heading";
			createdObjects.Add(val);
			val.SetActive(false);
			StripLocalization(val);
			TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).text = "VOICE COMPRESSION\n<size=70%>INCOMING VOICES ONLY</size>";
			((TMP_Text)component).enableAutoSizing = false;
			((TMP_Text)component).fontSize = Mathf.Min(((TMP_Text)component).fontSize, 22f);
			((TMP_Text)component).alignment = (TextAlignmentOptions)1025;
			((TMP_Text)component).isRightToLeftText = false;
			((Graphic)component).raycastTarget = false;
			RectTransform rectTransform = ((TMP_Text)component).rectTransform;
			Vector2 anchorMin = (rectTransform.anchorMax = Vector2.zero);
			rectTransform.anchorMin = anchorMin;
			rectTransform.pivot = Vector2.zero;
			Rect rect = ((RectTransform)((Component)template).transform).rect;
			rectTransform.sizeDelta = new Vector2(Mathf.Max(((Rect)(ref rect)).width, 280f), 42f);
			((Transform)rectTransform).localPosition = new Vector3(x, y, 0f);
			((Transform)rectTransform).localScale = Vector3.one;
			intendedPositions[val] = ((Transform)rectTransform).localPosition;
		}

		private void AddGuide(MenuSlider template, float x, float y)
		{
			string[] array = new string[13]
			{
				"<b>VOICE COMPRESSION GUIDE</b>\nTurn Voice compression ON and start with the defaults. Changes save automatically.", "<b>Threshold: -18 dB</b>\nCompression starts above this level. More negative means more compression.", "<b>Ratio: 4:1</b>\nHow strongly loud voices are reduced. Higher means stronger compression.", "<b>Attack: 5 ms</b>\nHow quickly it reacts to loud speech. Lower catches sudden peaks faster.", "<b>Release: 150 ms</b>\nHow quickly volume recovers after loud speech.", "<b>Makeup gain: +3 dB</b>\nRaises the compressed voice, helping quiet speech become audible. Also raises background noise.", "<b>Soft knee: 6 dB</b>\nMakes compression engage gradually for smoother sound.", "<b>Output ceiling: -1 dB</b>\nCaps each processed voice's peaks before the game's volume and mixer effects.", "<b>ADJUST BY EAR</b>\n<b>Loud teammates overpower everyone</b>\nLower Threshold to -24 dB, then try Ratio 6:1.", "<b>Quiet voices need more volume</b>\nIncrease Makeup gain gradually to +4 to +6 dB.",
				"<b>Volume swells between words</b>\nIncrease Release to 250 ms.", "<b>Voices sound too flattened</b>\nReduce Ratio to 2:1 or 3:1.", "Incoming microphone voices only. Your microphone, music, and sound effects stay unchanged."
			};
			for (int i = 0; i < array.Length; i++)
			{
				y -= AddGuideBlock(template, x, y, i, array[i]) + 4f;
			}
		}

		private float AddGuideBlock(MenuSlider template, float x, float y, int index, string content)
		{
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(((Component)template.elementNameText).gameObject, staging.transform, false);
			((Object)val).name = "VoiceCompression - Setup tips " + index;
			val.SetActive(false);
			createdObjects.Add(val);
			StripLocalization(val);
			TextMeshProUGUI component = val.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).text = content;
			((TMP_Text)component).richText = true;
			((TMP_Text)component).fontStyle = (FontStyles)0;
			((TMP_Text)component).enableAutoSizing = false;
			((TMP_Text)component).fontSize = Mathf.Min(((TMP_Text)template.elementNameText).fontSize, 12f);
			((TMP_Text)component).lineSpacing = 0f;
			((TMP_Text)component).paragraphSpacing = 0f;
			((TMP_Text)component).alignment = (TextAlignmentOptions)257;
			((TMP_Text)component).textWrappingMode = (TextWrappingModes)1;
			((TMP_Text)component).overflowMode = (TextOverflowModes)0;
			((TMP_Text)component).isRightToLeftText = false;
			((Graphic)component).raycastTarget = false;
			((TMP_Text)component).margin = Vector4.zero;
			RectTransform rectTransform = ((TMP_Text)component).rectTransform;
			Vector2 anchorMin = (rectTransform.anchorMax = Vector2.zero);
			rectTransform.anchorMin = anchorMin;
			rectTransform.pivot = new Vector2(0f, 1f);
			Rect rect = ((RectTransform)((Component)template).transform).rect;
			float num = Mathf.Max(((Rect)(ref rect)).width - 8f, 1f);
			float num2 = Mathf.Ceil(((TMP_Text)component).GetPreferredValues(((TMP_Text)component).text, num, float.PositiveInfinity).y) + 2f;
			rectTransform.sizeDelta = new Vector2(num, num2);
			((Transform)rectTransform).localPosition = new Vector3(x, y, 0f);
			((Transform)rectTransform).localScale = Vector3.one;
			intendedPositions[val] = ((Transform)rectTransform).localPosition;
			MenuSelectableElement obj = val.AddComponent<MenuSelectableElement>();
			((Selectable)obj).transition = (Transition)0;
			((Selectable)obj).targetGraphic = null;
			val.AddComponent<MenuElementHover>();
			return num2;
		}

		private void AddToggle(MenuSlider template, string label, ConfigEntry<bool> entry, float x, float y)
		{
			AddControl(template, label, new string[2] { "OFF", "ON" }, () => entry.Value ? 1 : 0, delegate(int index)
			{
				entry.Value = index != 0;
			}, x, y);
		}

		private void AddSlider(MenuSlider template, string label, ConfigEntry<float> entry, float defaultMin, float defaultMax, float step, string format, float x, float y)
		{
			AcceptableValueRange<float> val = ((ConfigEntryBase)entry).Description.AcceptableValues as AcceptableValueRange<float>;
			float min = val?.MinValue ?? defaultMin;
			float num = val?.MaxValue ?? defaultMax;
			int segments = Mathf.Max(1, Mathf.RoundToInt((num - min) / step));
			float actualStep = (num - min) / (float)segments;
			string[] array = new string[segments + 1];
			for (int i = 0; i <= segments; i++)
			{
				array[i] = (min + actualStep * (float)i).ToString(format, CultureInfo.InvariantCulture);
			}
			AddControl(template, label, array, () => Mathf.Clamp(Mathf.RoundToInt((entry.Value - min) / actualStep), 0, segments), delegate(int index)
			{
				entry.Value = min + actualStep * (float)index;
			}, x, y);
		}

		private void AddControl(MenuSlider template, string label, string[] options, Func<int> read, Action<int> write, float x, float y)
		{
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0162: Unknown result type (might be due to invalid IL or missing references)
			//IL_0169: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Expected O, but got Unknown
			//IL_0179: Expected O, but got Unknown
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Expected O, but got Unknown
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Expected O, but got Unknown
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0217: Expected O, but got Unknown
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Expected O, but got Unknown
			//IL_0232: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Expected O, but got Unknown
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_0248: Expected O, but got Unknown
			//IL_0253: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = Object.Instantiate<GameObject>(((Component)template).gameObject, staging.transform, false);
			((Object)val).name = "VoiceCompression - " + label;
			val.SetActive(false);
			createdObjects.Add(val);
			StripLocalization(val);
			MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val2 in componentsInChildren)
			{
				if (val2 is MenuSetting || val2 is MenuInputPercentSetting || val2 is MenuSliderPlayerMicGain)
				{
					((Behaviour)val2).enabled = false;
					Object.Destroy((Object)(object)val2);
				}
			}
			MenuSlider component = val.GetComponent<MenuSlider>();
			component.elementName = label;
			((TMP_Text)component.elementNameText).text = label;
			((TMP_Text)component.elementNameText).isRightToLeftText = false;
			((TMP_Text)component.elementNameText).enableAutoSizing = true;
			((TMP_Text)component.elementNameText).fontSizeMin = 12f;
			component.hasCustomOptions = true;
			component.hasCustomValues = false;
			component.hasBar = true;
			component.wrapAround = false;
			component.startValue = 0;
			component.endValue = options.Length - 1;
			component.buttonSegmentJump = (component.pointerSegmentJump = 1);
			component.stringAtStartOfValue = (component.stringAtEndOfValue = string.Empty);
			component.customOptions = new List<CustomOption>(options.Length);
			foreach (string customOptionText in options)
			{
				component.customOptions.Add(new CustomOption
				{
					customOptionText = customOptionText,
					localizedCustomOptionText = null,
					rightToLeft = false,
					maxTextLength = 0,
					onOption = new UnityEvent()
				});
			}
			component.onChange = new UnityEvent();
			SettingsValue.SetValue(component, (float)read() / 100f);
			Button[] array = (from button in val.GetComponentsInChildren<Button>(true)
				orderby ((Component)button).transform.localPosition.x
				select button).ToArray();
			if (array.Length != 2)
			{
				throw new InvalidOperationException("The native Audio slider arrow layout has changed.");
			}
			array[0].onClick = new ButtonClickedEvent();
			((UnityEvent)array[0].onClick).AddListener(new UnityAction(component.OnDecrease));
			array[1].onClick = new ButtonClickedEvent();
			((UnityEvent)array[1].onClick).AddListener(new UnityAction(component.OnIncrease));
			RectTransform val3 = (RectTransform)val.transform;
			((Transform)val3).localPosition = new Vector3(x, y, ((Component)template).transform.localPosition.z);
			((Transform)val3).localScale = ((Component)template).transform.localScale;
			intendedPositions[val] = ((Transform)val3).localPosition;
			bindings.Add(new SliderBinding(component, options.Length - 1, read, write));
		}

		private static void StripLocalization(GameObject root)
		{
			LocalizationChangedEvent[] componentsInChildren = root.GetComponentsInChildren<LocalizationChangedEvent>(true);
			foreach (LocalizationChangedEvent obj in componentsInChildren)
			{
				((Behaviour)obj).enabled = false;
				obj.onLocalizationChanged = new UnityEvent<string>();
				obj.onTextDirectionalityChanged = new UnityEvent<bool>();
				Object.Destroy((Object)(object)obj);
			}
		}

		private bool Activate()
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				foreach (GameObject createdObject in createdObjects)
				{
					createdObject.transform.SetParent((Transform)(object)scrollBox.scroller, false);
					createdObject.transform.localPosition = intendedPositions[createdObject];
					createdObject.SetActive(true);
				}
				Object.Destroy((Object)(object)staging);
				return true;
			}
			catch (Exception exception)
			{
				Fail(exception);
				return false;
			}
		}

		private void Finish()
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			try
			{
				foreach (SliderBinding binding in bindings)
				{
					binding.Refresh();
					binding.Slider.onChange.AddListener((UnityAction)delegate
					{
						if (ready)
						{
							int num = (int)CurrentValue.GetValue(binding.Slider);
							binding.Write(num);
							binding.PreviousIndex = num;
						}
					});
				}
				RecalculateScrollHeight.Invoke(scrollBox, new object[1] { true });
				page.WireVerticalNavigation(2f);
				ready = true;
				Log.LogDebug((object)"Added voice compression controls to the native Audio settings page.");
			}
			catch (Exception exception)
			{
				Fail(exception);
			}
		}

		private void Update()
		{
			if (!ready)
			{
				return;
			}
			foreach (SliderBinding binding in bindings)
			{
				if (Object.op_Implicit((Object)(object)binding.Slider) && binding.Read() != binding.PreviousIndex)
				{
					binding.Refresh();
				}
			}
		}

		private void Fail(Exception exception)
		{
			ready = false;
			foreach (GameObject createdObject in createdObjects)
			{
				if (Object.op_Implicit((Object)(object)createdObject))
				{
					createdObject.SetActive(false);
					Object.Destroy((Object)(object)createdObject);
				}
			}
			if (Object.op_Implicit((Object)(object)staging))
			{
				Object.Destroy((Object)(object)staging);
			}
			((Behaviour)this).enabled = false;
			Log.LogError((object)$"Could not add voice compression to the native Audio settings page: {exception}");
		}
	}
}
namespace VoiceCompression.Dsp
{
	public sealed class CompressorSettings
	{
		public static readonly CompressorSettings Default = new CompressorSettings();

		public bool Enabled { get; }

		public float ThresholdDb { get; }

		public float Ratio { get; }

		public float AttackMs { get; }

		public float ReleaseMs { get; }

		public float MakeupGainDb { get; }

		public float KneeDb { get; }

		public float OutputCeilingDb { get; }

		public CompressorSettings(bool enabled = true, float thresholdDb = -18f, float ratio = 4f, float attackMs = 5f, float releaseMs = 150f, float makeupGainDb = 3f, float kneeDb = 6f, float outputCeilingDb = -1f)
		{
			Enabled = enabled;
			ThresholdDb = Sanitize(thresholdDb, -60f, 0f, -18f);
			Ratio = Sanitize(ratio, 1f, 20f, 4f);
			AttackMs = Sanitize(attackMs, 0.1f, 100f, 5f);
			ReleaseMs = Sanitize(releaseMs, 10f, 1000f, 150f);
			MakeupGainDb = Sanitize(makeupGainDb, 0f, 24f, 3f);
			KneeDb = Sanitize(kneeDb, 0f, 24f, 6f);
			OutputCeilingDb = Sanitize(outputCeilingDb, -12f, 0f, -1f);
		}

		private static float Sanitize(float value, float min, float max, float fallback)
		{
			if (float.IsNaN(value) || float.IsInfinity(value))
			{
				return fallback;
			}
			return Math.Max(min, Math.Min(max, value));
		}
	}
	public sealed class VoiceCompressor
	{
		private double _envelope;

		private double _attackCoefficient;

		private double _releaseCoefficient;

		private double _ceiling;

		private double _slope;

		private double _makeupGain;

		private double _kneeStart;

		private int _sampleRate;

		private int _channels;

		private CompressorSettings? _settings;

		public void Process(float[] data, int channels, int sampleRate, CompressorSettings settings)
		{
			if (data == null)
			{
				throw new ArgumentNullException("data");
			}
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			if (channels <= 0)
			{
				throw new ArgumentOutOfRangeException("channels");
			}
			if (sampleRate <= 0)
			{
				throw new ArgumentOutOfRangeException("sampleRate");
			}
			if (data.Length % channels != 0)
			{
				throw new ArgumentException("The buffer must contain complete interleaved frames.", "data");
			}
			if (!settings.Enabled)
			{
				Reset();
				return;
			}
			if (_channels != channels || _sampleRate != sampleRate)
			{
				_envelope = 0.0;
				_channels = channels;
			}
			if (_sampleRate != sampleRate || _settings != settings)
			{
				_sampleRate = sampleRate;
				_settings = settings;
				_attackCoefficient = Math.Exp(-1.0 / ((double)((float)sampleRate * settings.AttackMs) * 0.001));
				_releaseCoefficient = Math.Exp(-1.0 / ((double)((float)sampleRate * settings.ReleaseMs) * 0.001));
				_ceiling = Math.Pow(10.0, (double)settings.OutputCeilingDb / 20.0);
				_slope = 1.0 / (double)settings.Ratio - 1.0;
				_makeupGain = Math.Pow(10.0, (double)settings.MakeupGainDb / 20.0);
				_kneeStart = Math.Pow(10.0, ((double)settings.ThresholdDb - (double)settings.KneeDb * 0.5) / 20.0);
			}
			for (int i = 0; i < data.Length; i += channels)
			{
				double num = 0.0;
				for (int j = 0; j < channels; j++)
				{
					int num2 = i + j;
					float num3 = data[num2];
					if (float.IsNaN(num3) || float.IsInfinity(num3))
					{
						data[num2] = 0f;
					}
					else
					{
						num = Math.Max(num, Math.Abs((double)num3));
					}
				}
				double num4 = ((num > _envelope) ? _attackCoefficient : _releaseCoefficient);
				_envelope = num + num4 * (_envelope - num);
				if (_envelope < 1E-12)
				{
					_envelope = 0.0;
				}
				double num5 = 0.0;
				if (_envelope > _kneeStart && _slope != 0.0)
				{
					double num6 = 20.0 * Math.Log10(_envelope) - (double)settings.ThresholdDb;
					double num7 = (double)settings.KneeDb * 0.5;
					if (settings.KneeDb > 0f && num6 > 0.0 - num7 && num6 < num7)
					{
						double num8 = num6 + num7;
						num5 = _slope * num8 * num8 / (2.0 * (double)settings.KneeDb);
					}
					else if (num6 >= num7)
					{
						num5 = _slope * num6;
					}
				}
				double num9 = ((num5 == 0.0) ? _makeupGain : (Math.Pow(10.0, num5 / 20.0) * _makeupGain));
				if (num > 0.0)
				{
					num9 = Math.Min(num9, _ceiling / num);
				}
				for (int k = 0; k < channels; k++)
				{
					int num10 = i + k;
					data[num10] = (float)((double)data[num10] * num9);
				}
			}
		}

		public void Reset()
		{
			_envelope = 0.0;
			_sampleRate = 0;
			_channels = 0;
			_settings = null;
		}
	}
}
namespace VoiceCompression.Audio
{
	internal static class IncomingVoiceCompression
	{
		private static readonly HashSet<IncomingVoiceFilter> Filters = new HashSet<IncomingVoiceFilter>();

		internal static void AttachExisting()
		{
			PlayerVoiceChat[] array = Object.FindObjectsByType<PlayerVoiceChat>((FindObjectsInactive)1, (FindObjectsSortMode)0);
			foreach (PlayerVoiceChat val in array)
			{
				if (((Behaviour)val).isActiveAndEnabled)
				{
					Attach(val);
				}
			}
		}

		internal static void Attach(PlayerVoiceChat voice)
		{
			PhotonView component = ((Component)voice).GetComponent<PhotonView>();
			AudioSource component2 = ((Component)voice).GetComponent<AudioSource>();
			if (!((Object)(object)component == (Object)null) && !component.IsMine && !((Object)(object)component2 == (Object)null))
			{
				IncomingVoiceFilter incomingVoiceFilter = ((Component)component2).GetComponent<IncomingVoiceFilter>();
				if ((Object)(object)incomingVoiceFilter == (Object)null)
				{
					incomingVoiceFilter = ((Component)component2).gameObject.AddComponent<IncomingVoiceFilter>();
				}
				incomingVoiceFilter.Initialize(component2, component);
				Filters.Add(incomingVoiceFilter);
			}
		}

		internal static void Unregister(IncomingVoiceFilter filter)
		{
			Filters.Remove(filter);
		}

		internal static void RemoveAll()
		{
			foreach (IncomingVoiceFilter filter in Filters)
			{
				if ((Object)(object)filter != (Object)null)
				{
					((Behaviour)filter).enabled = false;
					Object.Destroy((Object)(object)filter);
				}
			}
			Filters.Clear();
		}
	}
	[HarmonyPatch(typeof(PlayerVoiceChat), "Start")]
	internal static class IncomingVoiceStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix(PlayerVoiceChat __instance)
		{
			IncomingVoiceCompression.Attach(__instance);
		}
	}
	[DisallowMultipleComponent]
	public sealed class IncomingVoiceFilter : MonoBehaviour
	{
		private readonly VoiceCompressor compressor = new VoiceCompressor();

		private AudioSource source;

		private PhotonView owner;

		private AudioClip previousClip;

		private int outputSampleRate;

		private int resetVersion;

		private int processedResetVersion;

		private int processingAllowed;

		internal void Initialize(AudioSource audioSource, PhotonView photonView)
		{
			source = audioSource;
			owner = photonView;
			previousClip = source.clip;
			UpdateOutputSampleRate();
			Interlocked.Increment(ref resetVersion);
			Volatile.Write(ref processingAllowed, 1);
		}

		private void OnEnable()
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Expected O, but got Unknown
			UpdateOutputSampleRate();
			AudioSettings.OnAudioConfigurationChanged += new AudioConfigurationChangeHandler(OnAudioConfigurationChanged);
			Interlocked.Increment(ref resetVersion);
			if ((Object)(object)source != (Object)null && (Object)(object)owner != (Object)null && !owner.IsMine)
			{
				Volatile.Write(ref processingAllowed, 1);
			}
		}

		private void OnDisable()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			Volatile.Write(ref processingAllowed, 0);
			AudioSettings.OnAudioConfigurationChanged -= new AudioConfigurationChangeHandler(OnAudioConfigurationChanged);
			Interlocked.Increment(ref resetVersion);
		}

		private void OnDestroy()
		{
			IncomingVoiceCompression.Unregister(this);
		}

		private void Update()
		{
			if ((Object)(object)source == (Object)null || (Object)(object)owner == (Object)null || owner.IsMine)
			{
				Volatile.Write(ref processingAllowed, 0);
				((Behaviour)this).enabled = false;
				return;
			}
			AudioClip clip = source.clip;
			if ((Object)(object)clip != (Object)(object)previousClip)
			{
				previousClip = clip;
				Interlocked.Increment(ref resetVersion);
			}
		}

		private void OnAudioConfigurationChanged(bool deviceWasChanged)
		{
			UpdateOutputSampleRate();
			Interlocked.Increment(ref resetVersion);
		}

		private void UpdateOutputSampleRate()
		{
			Volatile.Write(ref outputSampleRate, AudioSettings.outputSampleRate);
		}

		private void OnAudioFilterRead(float[] data, int channels)
		{
			int num = Volatile.Read(in resetVersion);
			if (num != processedResetVersion)
			{
				compressor.Reset();
				processedResetVersion = num;
			}
			if (Volatile.Read(in processingAllowed) != 0)
			{
				int num2 = Volatile.Read(in outputSampleRate);
				if (num2 > 0 && data != null && channels > 0 && data.Length % channels == 0)
				{
					compressor.Process(data, channels, num2, CompressorConfiguration.Current);
				}
			}
		}
	}
}