using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Dissonance.Audio.Capture;
using HarmonyLib;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Microsoft.CodeAnalysis;
using NAudio.Wave;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("BigTalk")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Voice effects on the outgoing microphone for Big Walk")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BigTalk")]
[assembly: AssemblyTitle("BigTalk")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[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 BigTalk
{
internal static class Dsp
{
public static float SoftClip(float x)
{
if (x > 1f)
{
return 2f / 3f;
}
if (x < -1f)
{
return -2f / 3f;
}
return x - x * x * x / 3f;
}
public static float Clamp(float v, float lo, float hi)
{
if (!(v < lo))
{
if (!(v > hi))
{
return v;
}
return hi;
}
return lo;
}
public static float Semitones(float n)
{
return (float)Math.Pow(2.0, (double)n / 12.0);
}
}
internal sealed class DelayLine
{
private float[] _buf;
private int _write;
private int _capacity;
public void Ensure(float seconds, int rate)
{
int num = Math.Max(64, (int)(seconds * (float)rate) + 4);
if (_buf == null || _capacity < num)
{
_buf = new float[num];
_capacity = num;
_write = 0;
}
}
public void Reset()
{
if (_buf != null)
{
Array.Clear(_buf, 0, _buf.Length);
_write = 0;
}
}
public void Write(float sample)
{
_buf[_write] = sample;
if (++_write >= _capacity)
{
_write = 0;
}
}
public float Read(float delaySamples)
{
if (delaySamples < 1f)
{
delaySamples = 1f;
}
if (delaySamples > (float)(_capacity - 2))
{
delaySamples = _capacity - 2;
}
float num;
for (num = (float)_write - delaySamples; num < 0f; num += (float)_capacity)
{
}
while (num >= (float)_capacity)
{
num -= (float)_capacity;
}
int num2 = (int)num;
if (num2 >= _capacity)
{
num2 = 0;
}
float num3 = num - (float)num2;
int num4 = num2 + 1;
if (num4 >= _capacity)
{
num4 = 0;
}
return _buf[num2] * (1f - num3) + _buf[num4] * num3;
}
}
internal enum BiquadKind
{
LowPass,
HighPass
}
internal sealed class Biquad
{
private float _a1;
private float _a2;
private float _b0;
private float _b1;
private float _b2;
private float _z1;
private float _z2;
private BiquadKind _kind = (BiquadKind)(-1);
private float _freq = -1f;
private float _q = -1f;
private int _rate = -1;
public void Set(BiquadKind kind, float freq, float q, int rate)
{
if (kind != _kind || !(Math.Abs(freq - _freq) < 0.01f) || !(Math.Abs(q - _q) < 0.001f) || rate != _rate)
{
_kind = kind;
_freq = freq;
_q = q;
_rate = rate;
float num = (float)(Math.PI * 2.0 * (double)Dsp.Clamp(freq, 20f, (float)rate * 0.45f) / (double)rate);
float num2 = (float)Math.Cos(num);
float num3 = (float)(Math.Sin(num) / (2.0 * (double)Math.Max(0.05f, q)));
if (kind == BiquadKind.HighPass)
{
_b0 = (1f + num2) / 2f;
_b1 = 0f - (1f + num2);
_b2 = _b0;
}
else
{
_b0 = (1f - num2) / 2f;
_b1 = 1f - num2;
_b2 = _b0;
}
float num4 = 1f + num3;
_a1 = -2f * num2;
_a2 = 1f - num3;
_b0 /= num4;
_b1 /= num4;
_b2 /= num4;
_a1 /= num4;
_a2 /= num4;
}
}
public void Reset()
{
_z1 = 0f;
_z2 = 0f;
}
public float Process(float x)
{
float num = _b0 * x + _z1;
_z1 = _b1 * x - _a1 * num + _z2;
_z2 = _b2 * x - _a2 * num;
return num;
}
}
internal sealed class Lfo
{
private double _phase;
public void Reset()
{
_phase = 0.0;
}
public float Next(float hz, int rate)
{
_phase += Math.PI * 2.0 * (double)hz / (double)rate;
if (_phase > Math.PI * 2.0)
{
_phase -= Math.PI * 2.0;
}
return (float)Math.Sin(_phase);
}
}
public enum EffectId
{
Robot,
Megaphone,
GameBoy,
Echo,
Wobble,
Choir,
Stutter,
Pitch
}
internal interface IEffect
{
void Process(float[] buf, int n, int rate, Params p);
void Reset();
void OnEngage();
}
internal abstract class Effect : IEffect
{
public abstract void Process(float[] buf, int n, int rate, Params p);
public abstract void Reset();
public virtual void OnEngage()
{
}
}
internal sealed class RobotEffect : Effect
{
private readonly Lfo _lfo = new Lfo();
public override void Reset()
{
_lfo.Reset();
}
public override void Process(float[] buf, int n, int rate, Params p)
{
for (int i = 0; i < n; i++)
{
buf[i] *= _lfo.Next(p.RobotHz, rate);
}
}
}
internal sealed class MegaphoneEffect : Effect
{
private readonly Biquad _hp = new Biquad();
private readonly Biquad _lp = new Biquad();
private readonly DelayLine _slap = new DelayLine();
public override void Reset()
{
_hp.Reset();
_lp.Reset();
_slap.Reset();
}
public override void Process(float[] buf, int n, int rate, Params p)
{
_hp.Set(BiquadKind.HighPass, p.MegLow, 1.2f, rate);
_lp.Set(BiquadKind.LowPass, p.MegHigh, 1.2f, rate);
_slap.Ensure(0.3f, rate);
float delaySamples = p.MegSlapMs * 0.001f * (float)rate;
for (int i = 0; i < n; i++)
{
float num = _lp.Process(_hp.Process(buf[i]));
num = Dsp.SoftClip(num * (1f + p.MegDrive * 12f)) * 1.4f;
float num2 = _slap.Read(delaySamples);
_slap.Write(num);
buf[i] = num + num2 * 0.35f;
}
}
}
internal sealed class GameBoyEffect : Effect
{
private float _held;
private int _counter;
public override void Reset()
{
_held = 0f;
_counter = 0;
}
public override void Process(float[] buf, int n, int rate, Params p)
{
float num = (float)Math.Pow(2.0, Dsp.Clamp(p.CrushBits, 1f, 16f)) * 0.5f;
int counter = Math.Max(1, (int)p.CrushRate);
for (int i = 0; i < n; i++)
{
if (_counter <= 0)
{
_held = (float)Math.Round(buf[i] * num) / num;
_counter = counter;
}
_counter--;
buf[i] = _held;
}
}
}
internal sealed class EchoEffect : Effect
{
private readonly DelayLine _line = new DelayLine();
public override void Reset()
{
_line.Reset();
}
public override void Process(float[] buf, int n, int rate, Params p)
{
_line.Ensure(2.5f, rate);
float delaySamples = p.EchoMs * 0.001f * (float)rate;
float num = Dsp.Clamp(p.EchoFeedback, 0f, 0.95f);
for (int i = 0; i < n; i++)
{
float num2 = _line.Read(delaySamples);
_line.Write(buf[i] + num2 * num);
buf[i] += num2;
}
}
}
internal sealed class WobbleEffect : Effect
{
private readonly DelayLine _line = new DelayLine();
private readonly Lfo _lfo = new Lfo();
public override void Reset()
{
_line.Reset();
_lfo.Reset();
}
public override void Process(float[] buf, int n, int rate, Params p)
{
_line.Ensure(0.2f, rate);
float num = 0.03f * (float)rate;
float num2 = p.WobbleDepthMs * 0.001f * (float)rate;
for (int i = 0; i < n; i++)
{
_line.Write(buf[i]);
buf[i] = _line.Read(num + num2 * _lfo.Next(p.WobbleHz, rate));
}
}
}
internal sealed class ChoirEffect : Effect
{
private const int MaxVoices = 4;
private readonly DelayLine _line = new DelayLine();
private readonly Lfo[] _lfos = new Lfo[4];
public ChoirEffect()
{
for (int i = 0; i < 4; i++)
{
_lfos[i] = new Lfo();
}
}
public override void Reset()
{
_line.Reset();
Lfo[] lfos = _lfos;
for (int i = 0; i < lfos.Length; i++)
{
lfos[i].Reset();
}
}
public override void Process(float[] buf, int n, int rate, Params p)
{
_line.Ensure(0.2f, rate);
int num = Math.Max(1, Math.Min(4, p.ChoirVoices));
float num2 = p.ChoirDepthMs * 0.001f * (float)rate;
float num3 = 1f / (float)Math.Sqrt(num + 1);
for (int i = 0; i < n; i++)
{
float num4 = buf[i];
_line.Write(num4);
float num5 = num4;
for (int j = 0; j < num; j++)
{
float num6 = (0.012f + 0.007f * (float)j) * (float)rate;
float hz = p.ChoirRateHz * (1f + 0.37f * (float)j);
num5 += _line.Read(num6 + num2 * _lfos[j].Next(hz, rate));
}
buf[i] = num5 * num3;
}
}
}
internal sealed class StutterEffect : Effect
{
private float[] _slice = new float[1];
private int _length;
private int _fill;
private int _read;
private int _played;
private bool _capturing = true;
public override void Reset()
{
_fill = 0;
_read = 0;
_played = 0;
_capturing = true;
}
public override void OnEngage()
{
Reset();
}
public override void Process(float[] buf, int n, int rate, Params p)
{
int num = Math.Max(64, (int)(p.StutterSliceMs * 0.001f * (float)rate));
if (_slice.Length < num)
{
_slice = new float[num];
Reset();
}
_length = num;
int num2 = Math.Max(1, p.StutterRepeats);
for (int i = 0; i < n; i++)
{
if (_capturing)
{
_slice[_fill++] = buf[i];
if (_fill >= _length)
{
_capturing = false;
_read = 0;
_played = 0;
}
continue;
}
buf[i] = _slice[_read++];
if (_read >= _length)
{
_read = 0;
if (++_played >= num2 - 1)
{
_capturing = true;
_fill = 0;
}
}
}
}
}
internal sealed class PitchEffect : Effect
{
private readonly PhaseVocoder _vocoder = new PhaseVocoder();
public override void Reset()
{
_vocoder.Reset();
}
public override void Process(float[] buf, int n, int rate, Params p)
{
_vocoder.Shift = Dsp.Clamp(Dsp.Semitones(p.PitchSemitones), 0.25f, 4f);
_vocoder.Process(buf, n, rate);
}
}
internal static class Fft
{
public static void Transform(float[] buf, int n, int sign)
{
for (int i = 2; i < 2 * n - 2; i += 2)
{
int num = 0;
for (int num2 = 2; num2 < 2 * n; num2 <<= 1)
{
if ((i & num2) != 0)
{
num++;
}
num <<= 1;
}
if (i < num)
{
ref float reference = ref buf[i];
ref float reference2 = ref buf[num];
float num3 = buf[num];
float num4 = buf[i];
reference = num3;
reference2 = num4;
reference = ref buf[i + 1];
ref float reference3 = ref buf[num + 1];
num4 = buf[num + 1];
num3 = buf[i + 1];
reference = num4;
reference3 = num3;
}
}
int num5 = (int)(Math.Log(n) / Math.Log(2.0) + 0.5);
int j = 0;
int num6 = 2;
for (; j < num5; j++)
{
num6 <<= 1;
int num7 = num6 >> 1;
float num8 = 1f;
float num9 = 0f;
float num10 = (float)(Math.PI / (double)(num7 >> 1));
float num11 = (float)Math.Cos(num10);
float num12 = (float)sign * (float)Math.Sin(num10);
for (int k = 0; k < num7; k += 2)
{
int num13 = k;
int num14 = k + 1;
int num15 = k + num7;
int num16 = k + num7 + 1;
for (int l = k; l < 2 * n; l += num6)
{
float num17 = buf[num15] * num8 - buf[num16] * num9;
float num18 = buf[num15] * num9 + buf[num16] * num8;
buf[num15] = buf[num13] - num17;
buf[num16] = buf[num14] - num18;
buf[num13] += num17;
buf[num14] += num18;
num13 += num6;
num14 += num6;
num15 += num6;
num16 += num6;
}
float num19 = num8 * num11 - num9 * num12;
num9 = num8 * num12 + num9 * num11;
num8 = num19;
}
}
}
}
internal abstract class StftEngine
{
private const float TargetWindowMs = 21f;
private const int MinWindow = 256;
private const int MaxWindow = 2048;
protected const int Osamp = 4;
protected float[] Magn;
protected float[] Phase;
private float[] _inFifo;
private float[] _outFifo;
private float[] _work;
private float[] _outAccum;
private float[] _window;
private int _rover;
private int _latency;
protected int N { get; private set; }
protected int Rate { get; private set; }
protected int StepSize => N / 4;
protected float FreqPerBin => (float)Rate / (float)N;
protected void EnsureRate(int rate)
{
if (rate != Rate || _inFifo == null)
{
Rate = rate;
int num = (int)((float)rate * 21f / 1000f);
int num2 = 256;
while (num2 < num && num2 < 2048)
{
num2 <<= 1;
}
N = Math.Min(2048, Math.Max(256, num2));
_inFifo = new float[N];
_outFifo = new float[N];
_work = new float[2 * N];
_outAccum = new float[2 * N];
_window = new float[N];
Magn = new float[N / 2 + 1];
Phase = new float[N / 2 + 1];
for (int i = 0; i < N; i++)
{
_window[i] = -0.5f * (float)Math.Cos(Math.PI * 2.0 * (double)i / (double)N) + 0.5f;
}
_latency = N - StepSize;
_rover = 0;
Allocate();
Reset();
}
}
protected virtual void Allocate()
{
}
public virtual void Reset()
{
if (_inFifo != null)
{
Array.Clear(_inFifo, 0, _inFifo.Length);
Array.Clear(_outFifo, 0, _outFifo.Length);
Array.Clear(_work, 0, _work.Length);
Array.Clear(_outAccum, 0, _outAccum.Length);
_rover = 0;
}
}
public void Process(float[] buf, int count, int rate)
{
EnsureRate(rate);
if (_rover == 0)
{
_rover = _latency;
}
for (int i = 0; i < count; i++)
{
_inFifo[_rover] = buf[i];
buf[i] = _outFifo[_rover - _latency];
_rover++;
if (_rover >= N)
{
_rover = _latency;
Block();
}
}
}
private void Block()
{
for (int i = 0; i < N; i++)
{
_work[2 * i] = _inFifo[i] * _window[i];
_work[2 * i + 1] = 0f;
}
Fft.Transform(_work, N, -1);
for (int j = 0; j <= N / 2; j++)
{
float num = _work[2 * j];
float num2 = _work[2 * j + 1];
Magn[j] = 2f * (float)Math.Sqrt(num * num + num2 * num2);
Phase[j] = (float)Math.Atan2(num2, num);
}
Spectrum();
for (int k = 0; k <= N / 2; k++)
{
_work[2 * k] = Magn[k] * (float)Math.Cos(Phase[k]);
_work[2 * k + 1] = Magn[k] * (float)Math.Sin(Phase[k]);
}
for (int l = N + 2; l < 2 * N; l++)
{
_work[l] = 0f;
}
Fft.Transform(_work, N, 1);
float num3 = 2f / (float)(N / 2 * 4) / 1.5f;
for (int m = 0; m < N; m++)
{
_outAccum[m] += num3 * _window[m] * _work[2 * m];
}
Array.Copy(_outAccum, _outFifo, StepSize);
Array.Copy(_outAccum, StepSize, _outAccum, 0, N);
Array.Clear(_outAccum, N, _outAccum.Length - N);
Array.Copy(_inFifo, StepSize, _inFifo, 0, _latency);
}
protected abstract void Spectrum();
}
internal static class VoiceMonitor
{
private const int Chunk = 1024;
private const int Chunks = 48;
private const int ClipSamples = 49152;
private const int MaxChunksPerFrame = 24;
private static readonly float[] Staging = new float[1024];
private static GameObject _host;
private static AudioSource _source;
private static AudioClip _clip;
private static Il2CppStructArray<float> _transfer;
private static long _writeHead;
private static int _clipRate;
private static bool _running;
private static int _resyncs;
private static string _error;
public static int Resyncs => _resyncs;
public static string Error => _error;
public static bool Running => _running;
public static float BufferedMs
{
get
{
if (_clipRate <= 0)
{
return 0f;
}
return (float)Lead() * 1000f / (float)_clipRate;
}
}
public static void Attach(GameObject host)
{
_host = host;
}
public static void Tick(bool wanted, float volume, float leadMs)
{
//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
//IL_00c8: Expected O, but got Unknown
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Expected O, but got Unknown
bool flag = default(bool);
try
{
if (!wanted)
{
if (_running)
{
Stop();
}
}
else
{
if (!_running && !Start())
{
return;
}
int lastRate = Rack.LastRate;
if (lastRate > 0 && lastRate != _clipRate)
{
ManualLogSource log = Plugin.Log;
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(31, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("voice monitor re-rating ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(_clipRate);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" -> ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(lastRate);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" Hz");
}
log.LogInfo(val);
Stop();
if (!Start())
{
return;
}
}
_source.volume = Mathf.Clamp01(volume);
Feed(leadMs);
}
}
catch (Exception ex)
{
_error = ex.Message;
ManualLogSource log2 = Plugin.Log;
BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(40, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("voice monitor failed, switching it off: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex);
}
log2.LogError(val2);
Stop();
Plugin.Monitor.Value = false;
}
}
private static bool Start()
{
//IL_0136: Unknown result type (might be due to invalid IL or missing references)
//IL_013c: Expected O, but got Unknown
if ((Object)(object)_host == (Object)null)
{
return false;
}
int num = ((Rack.LastRate > 0) ? Rack.LastRate : 48000);
if ((Object)(object)_clip == (Object)null || _clipRate != num)
{
_clipRate = num;
_clip = AudioClip.Create("BigTalkMonitor", 49152, 1, num, false);
_transfer = new Il2CppStructArray<float>(1024L);
}
if ((Object)(object)_source == (Object)null)
{
_source = _host.AddComponent<AudioSource>();
_source.spatialBlend = 0f;
_source.bypassEffects = true;
_source.bypassListenerEffects = true;
_source.bypassReverbZones = true;
_source.priority = 0;
_source.playOnAwake = false;
}
_source.clip = _clip;
_source.loop = true;
MonitorBus.Clear();
MonitorBus.Active = true;
_clip.SetData(new Il2CppStructArray<float>(49152L), 0);
_source.Play();
_writeHead = _source.timeSamples;
_error = null;
_running = true;
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(23, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("voice monitor on at ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" Hz");
}
log.LogInfo(val);
return true;
}
private static void Stop()
{
MonitorBus.Active = false;
MonitorBus.Clear();
if ((Object)(object)_source != (Object)null)
{
_source.Stop();
}
_running = false;
}
private static int Lead()
{
if ((Object)(object)_source == (Object)null)
{
return 0;
}
int num = (int)(_writeHead % 49152) - _source.timeSamples;
if (num < 0)
{
num += 49152;
}
return num;
}
private static void Feed(float leadMs)
{
int num = Mathf.Clamp((int)(leadMs * 0.001f * (float)_clipRate), 2048, 24576);
int num2 = Lead();
if (num2 > 47104 || num2 < 1024)
{
_writeHead = _source.timeSamples;
num2 = 0;
_resyncs++;
MonitorBus.Clear();
}
if (MonitorBus.Available > 49152)
{
MonitorBus.Clear();
_resyncs++;
}
for (int i = 0; i < 24; i++)
{
if (num2 >= num)
{
break;
}
MonitorBus.Pop(Staging, 1024);
for (int j = 0; j < 1024; j++)
{
((Il2CppArrayBase<float>)(object)_transfer)[j] = Staging[j];
}
_clip.SetData(_transfer, (int)(_writeHead % 49152));
_writeHead += 1024L;
num2 += 1024;
}
}
}
internal static class MonitorBus
{
private const int Capacity = 96000;
private static readonly float[] Ring = new float[96000];
private static volatile int _write;
private static volatile int _read;
private static volatile int _rate;
public static volatile bool Active;
private static float _peak;
private static int _pushes;
public static int Rate => _rate;
public static float Peak => _peak;
public static int Pushes => _pushes;
public static int Available
{
get
{
int num = _write - _read;
if (num >= 0)
{
return num;
}
return num + 96000;
}
}
public static void Push(float[] src, int count, int rate)
{
if (!Active)
{
return;
}
_rate = rate;
_pushes++;
int num = _write;
int num2 = _read - num - 1;
if (num2 < 0)
{
num2 += 96000;
}
if (count > num2)
{
count = num2;
}
float num3 = _peak * 0.85f;
for (int i = 0; i < count; i++)
{
float num4 = src[i];
Ring[num] = num4;
float num5 = ((num4 < 0f) ? (0f - num4) : num4);
if (num5 > num3)
{
num3 = num5;
}
if (++num >= 96000)
{
num = 0;
}
}
_peak = num3;
_write = num;
}
public static int Pop(float[] dst, int count)
{
int write = _write;
int num = _read;
int num2 = write - num;
if (num2 < 0)
{
num2 += 96000;
}
int num3 = Math.Min(count, num2);
for (int i = 0; i < num3; i++)
{
dst[i] = Ring[num];
if (++num >= 96000)
{
num = 0;
}
}
for (int j = num3; j < count; j++)
{
dst[j] = 0f;
}
_read = num;
return num3;
}
public static void Clear()
{
_read = _write;
}
}
[BepInPlugin("deck.bigwalk.bigtalk", "Big Talk", "1.0.0")]
public class Plugin : BasePlugin
{
public const string Guid = "deck.bigwalk.bigtalk";
public const string Version = "1.0.0";
public static ManualLogSource Log;
public static ConfigFile Cfg;
public static string PatchError;
public static volatile bool SettingsDirty = true;
public static ConfigEntry<string> PanelKeys;
public static ConfigEntry<string> EffectKeys;
public static ConfigEntry<bool> PushToEffect;
public static ConfigEntry<bool> FreeCursor;
public static ConfigEntry<bool> LogKeys;
public static ConfigEntry<bool> Enabled;
public static ConfigEntry<EffectId> Effect;
public static ConfigEntry<TapPoint> Tap;
public static ConfigEntry<float> Mix;
public static ConfigEntry<float> OutputGain;
public static ConfigEntry<float> PanelScale;
public static ConfigEntry<bool> ShowDiagnostics;
public static ConfigEntry<bool> Monitor;
public static ConfigEntry<bool> MonitorDry;
public static ConfigEntry<float> MonitorVolume;
public static ConfigEntry<float> MonitorLatencyMs;
public static ConfigEntry<string> MonitorKeys;
public static ConfigEntry<float> RobotHz;
public static ConfigEntry<float> MegLow;
public static ConfigEntry<float> MegHigh;
public static ConfigEntry<float> MegDrive;
public static ConfigEntry<float> MegSlapMs;
public static ConfigEntry<float> CrushBits;
public static ConfigEntry<float> CrushRate;
public static ConfigEntry<float> EchoMs;
public static ConfigEntry<float> EchoFeedback;
public static ConfigEntry<float> WobbleHz;
public static ConfigEntry<float> WobbleDepthMs;
public static ConfigEntry<int> ChoirVoices;
public static ConfigEntry<float> ChoirDepthMs;
public static ConfigEntry<float> ChoirRateHz;
public static ConfigEntry<float> StutterSliceMs;
public static ConfigEntry<int> StutterRepeats;
public static ConfigEntry<float> PitchSemitones;
public override void Load()
{
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_0063: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Expected O, but got Unknown
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_007d: Expected O, but got Unknown
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_0098: Expected O, but got Unknown
Log = ((BasePlugin)this).Log;
Cfg = ((BasePlugin)this).Config;
((BasePlugin)this).Config.SaveOnConfigSet = false;
Bind();
((BasePlugin)this).Config.SettingChanged += OnSettingChanged;
Patch();
ClassInjector.RegisterTypeInIl2Cpp<BigTalkRuntime>();
GameObject val = new GameObject("BigTalkRuntime")
{
hideFlags = (HideFlags)61
};
Object.DontDestroyOnLoad((Object)val);
val.AddComponent<BigTalkRuntime>();
VoiceMonitor.Attach(val);
((BasePlugin)this).Config.Save();
ManualLogSource log = Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(59, 2, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Big Talk loaded. [");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(PanelKeys.Value);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("] opens the panel, ");
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(EffectKeys.Value);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("] applies the effect.");
}
log.LogInfo(val2);
}
private static void OnSettingChanged(object sender, EventArgs args)
{
SettingsDirty = true;
}
private void Bind()
{
PanelKeys = ((BasePlugin)this).Config.Bind<string>("Input", "PanelKeys", "Slash", "Comma-separated keys that open and close the panel. Any KeyCode name works. Note that Steam eats the function keys on some setups, so prefer ordinary ones.");
EffectKeys = ((BasePlugin)this).Config.Bind<string>("Input", "EffectKeys", "RightShift", "Comma-separated keys that apply the effect. Whether this is held or tapped is set by PushToEffect.");
PushToEffect = ((BasePlugin)this).Config.Bind<bool>("Input", "PushToEffect", true, "Hold the effect key to apply the effect, and drop back to your real voice on release. Turn this off to make the key a toggle instead, which is also how you leave an effect on permanently. Holding is the better default: most of these are funnier as punctuation than as a whole evening.");
FreeCursor = ((BasePlugin)this).Config.Bind<bool>("Input", "FreeCursor", true, "Release the mouse cursor while the panel is open so the buttons can be clicked. The game still reads the mouse for looking, so expect the camera to turn as you aim.");
LogKeys = ((BasePlugin)this).Config.Bind<bool>("Debug", "LogKeys", false, "Log every key and pad button press, to find one this machine actually delivers.");
Enabled = ((BasePlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master switch. With this off the tap still runs but leaves every sample alone.");
Effect = ((BasePlugin)this).Config.Bind<EffectId>("General", "Effect", EffectId.Pitch, "Which effect the key applies. One at a time.");
Tap = ((BasePlugin)this).Config.Bind<TapPoint>("General", "Tap", TapPoint.Subscribers, "Where the samples are modified. Subscribers is upstream of everything, so the effect goes out to other players and also shows up in the game's own mic meter. Encoder is the transmit path only: others hear the effect, anything local sees your real voice. If one of them never fires — the panel shows a frame count for both — use the other.");
Mix = ((BasePlugin)this).Config.Bind<float>("General", "Mix", 1f, "How much of the effect to blend against your untouched voice. 1 is fully wet.");
OutputGain = ((BasePlugin)this).Config.Bind<float>("General", "OutputGain", 1f, "Level trim after the effect, before the hard ceiling. Several effects lose or gain loudness and this is the correction.");
PanelScale = ((BasePlugin)this).Config.Bind<float>("General", "PanelScale", 1.6f, "How much to magnify the panel. Unity's debug UI draws at a fixed pixel size, which is unreadable on a handheld screen.");
ShowDiagnostics = ((BasePlugin)this).Config.Bind<bool>("General", "ShowDiagnostics", false, "Show the tap picker, frame counters and monitor internals at the bottom of the panel. Only needed if something is not working.");
Monitor = ((BasePlugin)this).Config.Bind<bool>("Monitor", "Enabled", false, "Play your own processed voice back locally, so the mod can be tested without a second player. WEAR HEADPHONES: on speakers this is a feedback loop, and the echo canceller will be fighting you the whole time.");
MonitorDry = ((BasePlugin)this).Config.Bind<bool>("Monitor", "MonitorDry", false, "Monitor your untouched voice instead of the processed one. The quick A/B: turn it on and off while talking to hear exactly what the effect is doing.");
MonitorVolume = ((BasePlugin)this).Config.Bind<float>("Monitor", "Volume", 0.8f, "Monitor loudness. Does not affect what is transmitted. Default: 0.8");
MonitorLatencyMs = ((BasePlugin)this).Config.Bind<float>("Monitor", "LatencyMs", 150f, "How much audio to keep buffered ahead of the speaker. Lower is more responsive and more likely to stutter on a frame hitch. Default: 150");
MonitorKeys = ((BasePlugin)this).Config.Bind<string>("Monitor", "MonitorKeys", "", "Optional comma-separated keys that toggle the monitor without opening the panel. Empty by default because every convenient key is already taken by something.");
RobotHz = ((BasePlugin)this).Config.Bind<float>("Robot", "Frequency", 45f, "Ring modulator frequency in Hz. Low is a lumbering robot, high is a wasp. Default: 45");
MegLow = ((BasePlugin)this).Config.Bind<float>("Megaphone", "LowCut", 550f, "High-pass corner in Hz. Default: 550");
MegHigh = ((BasePlugin)this).Config.Bind<float>("Megaphone", "HighCut", 3800f, "Low-pass corner in Hz. Default: 3800");
MegDrive = ((BasePlugin)this).Config.Bind<float>("Megaphone", "Drive", 0.5f, "How hard it is overdriven. Default: 0.5");
MegSlapMs = ((BasePlugin)this).Config.Bind<float>("Megaphone", "SlapMs", 55f, "Single echo behind the voice, in milliseconds, which is what puts it outdoors. Default: 55");
CrushBits = ((BasePlugin)this).Config.Bind<float>("GameBoy", "Bits", 5f, "Bit depth to quantise to. Default: 5");
CrushRate = ((BasePlugin)this).Config.Bind<float>("GameBoy", "HoldSamples", 5f, "Samples to hold each value for. Higher is a lower sample rate and more aliasing. Default: 5");
EchoMs = ((BasePlugin)this).Config.Bind<float>("Echo", "DelayMs", 260f, "Time between repeats. Default: 260");
EchoFeedback = ((BasePlugin)this).Config.Bind<float>("Echo", "Feedback", 0.45f, "How much of each repeat feeds the next. Capped below 1 so it cannot run away. Default: 0.45");
WobbleHz = ((BasePlugin)this).Config.Bind<float>("Wobble", "RateHz", 5.5f, "Wobbles per second. Default: 5.5");
WobbleDepthMs = ((BasePlugin)this).Config.Bind<float>("Wobble", "DepthMs", 2.5f, "How far the pitch swings, as delay modulation in ms. Default: 2.5");
ChoirVoices = ((BasePlugin)this).Config.Bind<int>("Choir", "Voices", 3, "Extra copies of you, 1 to 4. Default: 3");
ChoirDepthMs = ((BasePlugin)this).Config.Bind<float>("Choir", "DepthMs", 3.5f, "Modulation depth per copy. Default: 3.5");
ChoirRateHz = ((BasePlugin)this).Config.Bind<float>("Choir", "RateHz", 0.6f, "Base modulation rate; each copy runs at a different multiple of it. Default: 0.6");
StutterSliceMs = ((BasePlugin)this).Config.Bind<float>("Stutter", "SliceMs", 90f, "Length of the captured slice. Default: 90");
StutterRepeats = ((BasePlugin)this).Config.Bind<int>("Stutter", "Repeats", 3, "How many times each slice plays before the input is let through again. Default: 3");
PitchSemitones = ((BasePlugin)this).Config.Bind<float>("Pitch", "Semitones", 5f, "Whole-spectrum shift. Positive is Chipmunk, negative is Giant. Beyond about five either way the codec starts to struggle. Default: 5");
}
private void Patch()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Expected O, but got Unknown
try
{
Harmony val = new Harmony("deck.bigwalk.bigtalk");
val.PatchAll(typeof(VoiceTap.SubscriberTap));
val.PatchAll(typeof(VoiceTap.EncoderTap));
Log.LogInfo((object)"voice taps patched");
}
catch (Exception ex)
{
PatchError = ex.Message;
ManualLogSource log = Log;
bool flag = default(bool);
BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(66, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("could not patch the voice pipeline, so no effect will be applied: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex);
}
log.LogError(val2);
}
}
}
public class BigTalkRuntime : MonoBehaviour
{
private static KeyCode[] _allKeys;
private bool _open;
private bool _toggled;
private bool _inputDead;
private CursorLockMode _lockWas;
private bool _cursorWas;
public BigTalkRuntime(IntPtr ptr)
: base(ptr)
{
}
private void Update()
{
//IL_0116: Unknown result type (might be due to invalid IL or missing references)
//IL_011c: Expected O, but got Unknown
if (Plugin.SettingsDirty)
{
Plugin.SettingsDirty = false;
Publish();
}
VoiceMonitor.Tick(Plugin.Monitor.Value, Plugin.MonitorVolume.Value, Plugin.MonitorLatencyMs.Value);
if (_inputDead)
{
return;
}
try
{
if (Plugin.LogKeys.Value)
{
ReportKeys();
}
if (KeyList.AnyDown(Plugin.PanelKeys.Value))
{
_open = !_open;
Cursor(_open);
}
if (KeyList.AnyDown(Plugin.MonitorKeys.Value))
{
Plugin.Monitor.Value = !Plugin.Monitor.Value;
}
if (Plugin.PushToEffect.Value)
{
_toggled = false;
Rack.Engaged = KeyList.AnyHeld(Plugin.EffectKeys.Value);
return;
}
if (KeyList.AnyDown(Plugin.EffectKeys.Value))
{
_toggled = !_toggled;
}
Rack.Engaged = _toggled;
}
catch (Exception ex)
{
_inputDead = true;
Rack.Engaged = false;
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(62, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("keyboard input is unavailable, so the effect key cannot work: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
}
log.LogError(val);
}
}
private static void Publish()
{
VoiceTap.Active = Plugin.Tap.Value;
Rack.Publish(new Params
{
Enabled = Plugin.Enabled.Value,
Effect = Plugin.Effect.Value,
Mix = Plugin.Mix.Value,
OutputGain = Plugin.OutputGain.Value,
MonitorDry = Plugin.MonitorDry.Value,
RobotHz = Plugin.RobotHz.Value,
MegLow = Plugin.MegLow.Value,
MegHigh = Plugin.MegHigh.Value,
MegDrive = Plugin.MegDrive.Value,
MegSlapMs = Plugin.MegSlapMs.Value,
CrushBits = Plugin.CrushBits.Value,
CrushRate = Plugin.CrushRate.Value,
EchoMs = Plugin.EchoMs.Value,
EchoFeedback = Plugin.EchoFeedback.Value,
WobbleHz = Plugin.WobbleHz.Value,
WobbleDepthMs = Plugin.WobbleDepthMs.Value,
ChoirVoices = Plugin.ChoirVoices.Value,
ChoirDepthMs = Plugin.ChoirDepthMs.Value,
ChoirRateHz = Plugin.ChoirRateHz.Value,
StutterSliceMs = Plugin.StutterSliceMs.Value,
StutterRepeats = Plugin.StutterRepeats.Value,
PitchSemitones = Plugin.PitchSemitones.Value
});
}
private unsafe static void ReportKeys()
{
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Invalid comparison between Unknown and I4
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Expected O, but got Unknown
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Invalid comparison between Unknown and I4
if (!Input.anyKeyDown)
{
return;
}
if (_allKeys == null)
{
_allKeys = (KeyCode[])Enum.GetValues(typeof(KeyCode));
}
string text = null;
KeyCode[] allKeys = _allKeys;
for (int i = 0; i < allKeys.Length; i++)
{
KeyCode val = allKeys[i];
if ((int)val != 0 && ((int)val < 323 || (int)val > 329) && Input.GetKeyDown(val))
{
text = ((text == null) ? ((object)(*(KeyCode*)(&val))/*cast due to .constrained prefix*/).ToString() : (text + ", " + ((object)(*(KeyCode*)(&val))/*cast due to .constrained prefix*/).ToString()));
}
}
if (text != null)
{
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(10, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("key down: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(text);
}
log.LogInfo(val2);
}
}
private void Cursor(bool free)
{
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
if (Plugin.FreeCursor.Value)
{
if (free)
{
_lockWas = Cursor.lockState;
_cursorWas = Cursor.visible;
}
else
{
Cursor.lockState = _lockWas;
Cursor.visible = _cursorWas;
}
}
}
private void OnGUI()
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Expected O, but got Unknown
if (!_open)
{
return;
}
if (Plugin.FreeCursor.Value)
{
Cursor.lockState = (CursorLockMode)0;
Cursor.visible = true;
}
try
{
VoicePanel.Draw();
}
catch (Exception ex)
{
_open = false;
Cursor(free: false);
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(29, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("the panel threw, closing it: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
}
log.LogError(val);
}
}
private void OnDestroy()
{
Rack.Engaged = false;
MonitorBus.Active = false;
}
}
internal static class KeyList
{
private static readonly Dictionary<string, KeyCode[]> Parsed = new Dictionary<string, KeyCode[]>();
public static bool AnyDown(string spec)
{
KeyCode[] array = Parse(spec);
for (int i = 0; i < array.Length; i++)
{
if (Input.GetKeyDown(array[i]))
{
return true;
}
}
return false;
}
public static bool AnyHeld(string spec)
{
KeyCode[] array = Parse(spec);
for (int i = 0; i < array.Length; i++)
{
if (Input.GetKey(array[i]))
{
return true;
}
}
return false;
}
private static KeyCode[] Parse(string spec)
{
//IL_0068: Unknown result type (might be due to invalid IL or missing references)
//IL_006f: Expected O, but got Unknown
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
if (spec == null)
{
spec = string.Empty;
}
if (Parsed.TryGetValue(spec, out var value))
{
return value;
}
List<KeyCode> list = new List<KeyCode>();
string[] array = spec.Split(',');
bool flag = default(bool);
for (int i = 0; i < array.Length; i++)
{
string text = array[i].Trim();
if (text.Length == 0)
{
continue;
}
if (Enum.TryParse<KeyCode>(text, ignoreCase: true, out KeyCode result) && (int)result != 0)
{
list.Add(result);
continue;
}
ManualLogSource log = Plugin.Log;
BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(33, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text);
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' is not a key name, ignoring it");
}
log.LogWarning(val);
}
KeyCode[] array2 = list.ToArray();
Parsed[spec] = array2;
return array2;
}
}
internal sealed class Params
{
public bool Enabled = true;
public EffectId Effect = EffectId.Pitch;
public float Mix = 1f;
public float OutputGain = 1f;
public bool MonitorDry;
public float RobotHz = 45f;
public float MegLow = 550f;
public float MegHigh = 3800f;
public float MegDrive = 0.5f;
public float MegSlapMs = 55f;
public float CrushBits = 5f;
public float CrushRate = 5f;
public float EchoMs = 260f;
public float EchoFeedback = 0.45f;
public float WobbleHz = 5.5f;
public float WobbleDepthMs = 2.5f;
public int ChoirVoices = 3;
public float ChoirDepthMs = 3.5f;
public float ChoirRateHz = 0.6f;
public float StutterSliceMs = 90f;
public int StutterRepeats = 3;
public float PitchSemitones = 5f;
}
internal static class Rack
{
internal enum Need
{
Nothing,
DryOnly,
Full
}
private const int ScratchSize = 4096;
private static readonly IEffect[] Effects = new IEffect[8]
{
new RobotEffect(),
new MegaphoneEffect(),
new GameBoyEffect(),
new EchoEffect(),
new WobbleEffect(),
new ChoirEffect(),
new StutterEffect(),
new PitchEffect()
};
public static readonly string[] Names = new string[8] { "Robot", "Megaphone", "Game Boy", "Canyon echo", "Drunk wobble", "Choir of me", "Stutter", "Chipmunk & Giant" };
private static volatile Params _current = new Params();
private static volatile bool _engaged;
private static float[] _dry = new float[4096];
private static EffectId _running = EffectId.Robot;
private static bool _wasEngaged;
private static long _frames;
private static int _lastRate;
private static int _lastSize;
private static float _lastPeak;
public static Params Current => _current;
public static bool Engaged
{
get
{
return _engaged;
}
set
{
_engaged = value;
}
}
public static long Frames => Interlocked.Read(in _frames);
public static int LastRate => _lastRate;
public static int LastSize => _lastSize;
public static float LastPeak => _lastPeak;
internal static Need Wanted
{
get
{
if (_current.Enabled && _engaged)
{
return Need.Full;
}
if (!MonitorBus.Active)
{
return Need.Nothing;
}
return Need.DryOnly;
}
}
public static void Publish(Params p)
{
_current = p;
}
public static void Note(int n, int rate)
{
Interlocked.Increment(ref _frames);
_lastRate = rate;
_lastSize = n;
_lastPeak = 0f;
if (_wasEngaged)
{
Effects[(int)_running].Reset();
_wasEngaged = false;
}
}
public static void Render(float[] buf, int n, int rate)
{
Interlocked.Increment(ref _frames);
_lastRate = rate;
_lastSize = n;
Params current = _current;
if (!current.Enabled || !_engaged)
{
if (_wasEngaged)
{
Effects[(int)_running].Reset();
_wasEngaged = false;
}
MonitorBus.Push(buf, n, rate);
return;
}
if (_running != current.Effect)
{
Effects[(int)_running].Reset();
_running = current.Effect;
Effects[(int)_running].Reset();
}
if (!_wasEngaged)
{
Effects[(int)_running].Reset();
Effects[(int)_running].OnEngage();
_wasEngaged = true;
}
if (_dry.Length < n)
{
_dry = new float[n];
}
Array.Copy(buf, _dry, n);
Effects[(int)_running].Process(buf, n, rate, current);
float num = Dsp.Clamp(current.Mix, 0f, 1f);
float num2 = Dsp.Clamp(current.OutputGain, 0f, 4f);
float num3 = 0f;
for (int i = 0; i < n; i++)
{
float num4 = (_dry[i] * (1f - num) + buf[i] * num) * num2;
if (num4 > 1f)
{
num4 = 1f;
}
else if (num4 < -1f)
{
num4 = -1f;
}
float num5 = ((num4 < 0f) ? (0f - num4) : num4);
if (num5 > num3)
{
num3 = num5;
}
buf[i] = num4;
}
_lastPeak = num3;
MonitorBus.Push(current.MonitorDry ? _dry : buf, n, rate);
}
public static void ResetAll()
{
IEffect[] effects = Effects;
for (int i = 0; i < effects.Length; i++)
{
effects[i].Reset();
}
_wasEngaged = false;
}
}
internal sealed class PhaseVocoder : StftEngine
{
public float Shift = 1f;
private float[] _lastPhase;
private float[] _sumPhase;
private float[] _anaMagn;
private float[] _anaFreq;
private float[] _synMagn;
private float[] _synFreq;
protected override void Allocate()
{
int num = base.N / 2 + 1;
_lastPhase = new float[num];
_sumPhase = new float[num];
_anaMagn = new float[num];
_anaFreq = new float[num];
_synMagn = new float[num];
_synFreq = new float[num];
}
public override void Reset()
{
base.Reset();
if (_lastPhase != null)
{
Array.Clear(_lastPhase, 0, _lastPhase.Length);
Array.Clear(_sumPhase, 0, _sumPhase.Length);
}
}
protected override void Spectrum()
{
int num = base.N / 2 + 1;
float num2 = (float)(Math.PI * 2.0 * (double)base.StepSize / (double)base.N);
float freqPerBin = base.FreqPerBin;
for (int i = 0; i < num; i++)
{
float num3 = Phase[i];
float num4 = num3 - _lastPhase[i];
_lastPhase[i] = num3;
num4 -= (float)i * num2;
int num5 = (int)((double)num4 / Math.PI);
num5 = ((num5 < 0) ? (num5 - (num5 & 1)) : (num5 + (num5 & 1)));
num4 -= (float)Math.PI * (float)num5;
num4 = 4f * num4 / ((float)Math.PI * 2f);
_anaMagn[i] = Magn[i];
_anaFreq[i] = (float)i * freqPerBin + num4 * freqPerBin;
}
Array.Clear(_synMagn, 0, num);
Array.Clear(_synFreq, 0, num);
for (int j = 0; j < num; j++)
{
int num6 = (int)((float)j * Shift);
if (num6 < num)
{
_synMagn[num6] += _anaMagn[j];
_synFreq[num6] = _anaFreq[j] * Shift;
}
}
for (int k = 0; k < num; k++)
{
float num7 = _synFreq[k];
num7 -= (float)k * freqPerBin;
num7 /= freqPerBin;
num7 = (float)Math.PI * 2f * num7 / 4f;
num7 += (float)k * num2;
_sumPhase[k] += num7;
Magn[k] = _synMagn[k];
Phase[k] = _sumPhase[k];
}
}
}
internal static class VoicePanel
{
private const float Width = 400f;
private static Vector2 _scroll;
private static string _status = "";
private static bool _ready;
private static float _scale = 1.6f;
private static float _height = 600f;
private static EffectId _effect;
private static bool _monitorOn;
private static bool _monitorDry;
private static bool _showDiag;
private static bool _engaged;
private static bool _monitorFailed;
private static bool _resynced;
private static bool _patchFailed;
private static bool _tapDead;
private static readonly string[] Meters = BuildMeters();
private static readonly string[] Formats = new string[4] { "0", "0.#", "0.##", "0.###" };
private static string[] BuildMeters()
{
string[] array = new string[13];
for (int i = 0; i < array.Length; i++)
{
array[i] = "[" + new string('|', i) + new string('.', 12 - i) + "]";
}
return array;
}
private static void Snapshot()
{
_ready = true;
float num = Mathf.Clamp(Plugin.PanelScale.Value, 0.5f, 4f);
float num2 = Mathf.Max(0.5f, (float)Screen.width / 440f);
_scale = Mathf.Min(num, num2);
_height = Mathf.Min((float)Screen.height / _scale - 40f, 640f);
_effect = Plugin.Effect.Value;
_monitorOn = Plugin.Monitor.Value;
_monitorDry = Plugin.MonitorDry.Value;
_showDiag = Plugin.ShowDiagnostics.Value;
_engaged = Rack.Engaged;
_monitorFailed = VoiceMonitor.Error != null;
_resynced = VoiceMonitor.Resyncs > 0;
_patchFailed = Plugin.PatchError != null;
_tapDead = VoiceTap.Dead;
}
public static void Draw()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Invalid comparison between Unknown and I4
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_0054: Unknown result type (might be due to invalid IL or missing references)
//IL_0078: 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_009b: Unknown result type (might be due to invalid IL or missing references)
//IL_0125: Unknown result type (might be due to invalid IL or missing references)
if ((int)Event.current.type == 8)
{
Snapshot();
}
else if (!_ready)
{
return;
}
Matrix4x4 matrix = GUI.matrix;
GUI.matrix = Matrix4x4.Scale(new Vector3(_scale, _scale, 1f));
GUILayout.BeginArea(new Rect(20f, 20f, 400f, _height), GUI.skin.box);
GUILayout.Label("BIG TALK — what everyone else hears", (Il2CppReferenceArray<GUILayoutOption>)null);
Status();
_scroll = GUILayout.BeginScrollView(_scroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(_height - 92f) });
Picker();
Controls();
Trigger();
MonitorSection();
Master();
PanelSettings();
Diagnostics();
GUILayout.EndScrollView();
GUILayout.Space(4f);
GUILayout.BeginHorizontal((Il2CppReferenceArray<GUILayoutOption>)null);
if (GUILayout.Button("Save to file", (Il2CppReferenceArray<GUILayoutOption>)null))
{
Save();
}
if (GUILayout.Button("Reload file", (Il2CppReferenceArray<GUILayoutOption>)null))
{
Reload();
}
GUILayout.EndHorizontal();
GUILayout.Label((_status.Length > 0) ? _status : "changes apply now, and last the session", (Il2CppReferenceArray<GUILayoutOption>)null);
GUILayout.EndArea();
GUI.matrix = matrix;
}
private static void Status()
{
if (_patchFailed)
{
GUILayout.Label("PATCH FAILED — no effect can be applied. See diagnostics.", (Il2CppReferenceArray<GUILayoutOption>)null);
}
else if (_tapDead)
{
GUILayout.Label("the tap hit too many errors and is off until restart", (Il2CppReferenceArray<GUILayoutOption>)null);
}
else if (_engaged)
{
GUILayout.Label("ON — " + Rack.Names[(int)_effect] + " " + Meter(Rack.LastPeak), (Il2CppReferenceArray<GUILayoutOption>)null);
}
else
{
GUILayout.Label("off — press [" + Plugin.EffectKeys.Value + "] to use it", (Il2CppReferenceArray<GUILayoutOption>)null);
}
}
private static string Meter(float peak)
{
return Meters[Mathf.Clamp(Mathf.RoundToInt(peak * 12f), 0, 12)];
}
private static void Picker()
{
GUILayout.Label("— Effect: " + Rack.Names[(int)_effect] + " —", (Il2CppReferenceArray<GUILayoutOption>)null);
for (int i = 0; i < Rack.Names.Length; i += 2)
{
GUILayout.BeginHorizontal((Il2CppReferenceArray<GUILayoutOption>)null);
Pick((EffectId)i);
if (i + 1 < Rack.Names.Length)
{
Pick((EffectId)(i + 1));
}
GUILayout.EndHorizontal();
}
}
private static void Pick(EffectId id)
{
bool flag = _effect == id;
string text = Rack.Names[(int)id];
if (!(!GUILayout.Button(flag ? ("[" + text + "]") : text, (Il2CppReferenceArray<GUILayoutOption>)null) || flag))
{
Plugin.Effect.Value = id;
_status = "effect: " + text;
}
}
private static void Controls()
{
GUILayout.Space(8f);
switch (_effect)
{
case EffectId.Robot:
GUILayout.Label("Ring modulation. Low is lumbering, high is a wasp.", (Il2CppReferenceArray<GUILayoutOption>)null);
Slider(Plugin.RobotHz, "Frequency", 5f, 300f, "Hz", 1);
break;
case EffectId.Megaphone:
GUILayout.Label("Narrow and overdriven, with a slap back to put it outdoors.", (Il2CppReferenceArray<GUILayoutOption>)null);
Slider(Plugin.MegLow, "Low cut", 200f, 1200f, "Hz", 0);
Slider(Plugin.MegHigh, "High cut", 1500f, 6000f, "Hz", 0);
Slider(Plugin.MegDrive, "Drive", 0f, 1f, "", 2);
Slider(Plugin.MegSlapMs, "Slap", 10f, 200f, "ms", 0);
break;
case EffectId.GameBoy:
GUILayout.Label("Bit depth and sample rate reduction. The aliasing is the point.", (Il2CppReferenceArray<GUILayoutOption>)null);
Slider(Plugin.CrushBits, "Bit depth", 1f, 12f, " bits", 1);
Slider(Plugin.CrushRate, "Hold", 1f, 24f, " samples", 0);
break;
case EffectId.Echo:
GUILayout.Label("Feedback delay. The one that survives the codec best.", (Il2CppReferenceArray<GUILayoutOption>)null);
Slider(Plugin.EchoMs, "Delay", 40f, 1200f, "ms", 0);
Slider(Plugin.EchoFeedback, "Feedback", 0f, 0.95f, "", 2);
break;
case EffectId.Wobble:
GUILayout.Label("Vibrato. The pitch really moves rather than flanging.", (Il2CppReferenceArray<GUILayoutOption>)null);
Slider(Plugin.WobbleHz, "Rate", 0.2f, 14f, "Hz", 2);
Slider(Plugin.WobbleDepthMs, "Depth", 0.2f, 12f, "ms", 2);
break;
case EffectId.Choir:
GUILayout.Label("Copies of you at different rates. Detune is depth times rate.", (Il2CppReferenceArray<GUILayoutOption>)null);
IntSlider(Plugin.ChoirVoices, "Copies", 1, 4);
Slider(Plugin.ChoirDepthMs, "Depth", 0.5f, 12f, "ms", 2);
Slider(Plugin.ChoirRateHz, "Rate", 0.05f, 4f, "Hz", 2);
break;
case EffectId.Stutter:
GUILayout.Label("Repeats a slice. Best held, not toggled — it eats sentences.", (Il2CppReferenceArray<GUILayoutOption>)null);
Slider(Plugin.StutterSliceMs, "Slice", 20f, 400f, "ms", 0);
IntSlider(Plugin.StutterRepeats, "Repeats", 2, 8);
break;
case EffectId.Pitch:
GUILayout.Label("Positive is Chipmunk, negative is Giant. Past 5 the codec", (Il2CppReferenceArray<GUILayoutOption>)null);
GUILayout.Label("starts to struggle, so small moves travel better.", (Il2CppReferenceArray<GUILayoutOption>)null);
Slider(Plugin.PitchSemitones, "Shift", -12f, 12f, " semitones", 1);
break;
}
}
private static void Trigger()
{
GUILayout.Space(8f);
GUILayout.Label("— Key: " + Plugin.EffectKeys.Value + " —", (Il2CppReferenceArray<GUILayoutOption>)null);
Toggle(Plugin.PushToEffect, "Hold to apply (rather than tap to toggle)");
GUILayout.Label("Untick to toggle, which is also how you leave an effect on.", (Il2CppReferenceArray<GUILayoutOption>)null);
GUILayout.Label("Keys are set in the config file — typing here drives the game too.", (Il2CppReferenceArray<GUILayoutOption>)null);
}
private static void MonitorSection()
{
GUILayout.Space(8f);
GUILayout.Label("— Monitor (hear yourself) —", (Il2CppReferenceArray<GUILayoutOption>)null);
Toggle(Plugin.Monitor, "Play my processed voice back to me");
if (!_monitorOn)
{
GUILayout.Label(" headphones only — on speakers this feeds back", (Il2CppReferenceArray<GUILayoutOption>)null);
return;
}
if (GUILayout.Button(_monitorDry ? "hearing: REAL VOICE" : "hearing: EFFECT", (Il2CppReferenceArray<GUILayoutOption>)null))
{
Plugin.MonitorDry.Value = !_monitorDry;
_status = ((!_monitorDry) ? "monitoring your real voice" : "monitoring the effect");
}
if (!_monitorDry && !_engaged)
{
GUILayout.Label(" not engaged — press the effect key", (Il2CppReferenceArray<GUILayoutOption>)null);
}
Slider(Plugin.MonitorVolume, "Monitor level", 0f, 1f, "", 2);
if (_monitorFailed)
{
GUILayout.Label(" error: " + VoiceMonitor.Error, (Il2CppReferenceArray<GUILayoutOption>)null);
}
else
{
GUILayout.Label(" you hear this before Opus, so the real thing is rougher", (Il2CppReferenceArray<GUILayoutOption>)null);
}
}
private static void Master()
{
GUILayout.Space(8f);
GUILayout.Label("— Levels —", (Il2CppReferenceArray<GUILayoutOption>)null);
Slider(Plugin.Mix, "Blend with real voice", 0f, 1f, "", 2);
Slider(Plugin.OutputGain, "Output level", 0f, 3f, "x", 2);
Toggle(Plugin.Enabled, "Effects enabled at all");
}
private static void PanelSettings()
{
GUILayout.Space(8f);
GUILayout.Label("— Panel —", (Il2CppReferenceArray<GUILayoutOption>)null);
Slider(Plugin.PanelScale, "Panel size", 0.5f, 4f, "x", 2);
Toggle(Plugin.FreeCursor, "Free the cursor while the panel is open");
}
private static void Diagnostics()
{
GUILayout.Space(8f);
Toggle(Plugin.ShowDiagnostics, "Show diagnostics");
if (!_showDiag)
{
return;
}
if (_patchFailed)
{
GUILayout.Label("patch error: " + Plugin.PatchError, (Il2CppReferenceArray<GUILayoutOption>)null);
}
GUILayout.Space(4f);
GUILayout.Label($"tap: {Plugin.Tap.Value}", (Il2CppReferenceArray<GUILayoutOption>)null);
GUILayout.BeginHorizontal((Il2CppReferenceArray<GUILayoutOption>)null);
TapPick(TapPoint.Subscribers, "Subscribers");
TapPick(TapPoint.Encoder, "Encoder");
GUILayout.EndHorizontal();
GUILayout.Label($"frames: subs {VoiceTap.SubscriberFrames} / enc {VoiceTap.EncoderFrames}", (Il2CppReferenceArray<GUILayoutOption>)null);
GUILayout.Label("a counter stuck at zero means this build inlined that tap", (Il2CppReferenceArray<GUILayoutOption>)null);
int lastRate = Rack.LastRate;
GUILayout.Label((lastRate > 0) ? $"capture: {lastRate} Hz, {Rack.LastSize} samples {Meter(Rack.LastPeak)}" : "capture: no audio seen yet", (Il2CppReferenceArray<GUILayoutOption>)null);
if (_monitorOn)
{
GUILayout.Label($"monitor: {VoiceMonitor.BufferedMs:F0} ms buffered, {VoiceMonitor.Resyncs} resyncs", (Il2CppReferenceArray<GUILayoutOption>)null);
GUILayout.Label($"monitor fed {MonitorBus.Pushes} blocks {Meter(MonitorBus.Peak)}", (Il2CppReferenceArray<GUILayoutOption>)null);
if (_resynced)
{
GUILayout.Label("resyncs are audible skips in the monitor only", (Il2CppReferenceArray<GUILayoutOption>)null);
}
}
Slider(Plugin.MonitorLatencyMs, "Monitor buffer", 40f, 400f, "ms", 0);
Toggle(Plugin.LogKeys, "Log every key press, to find one that works");
}
private static void TapPick(TapPoint point, string label)
{
bool flag = Plugin.Tap.Value == point;
if (!(!GUILayout.Button(flag ? ("[" + label + "]") : label, (Il2CppReferenceArray<GUILayoutOption>)null) || flag))
{
Plugin.Tap.Value = point;
Rack.ResetAll();
_status = "tap: " + label;
}
}
private static void Slider(ConfigEntry<float> entry, string label, float min, float max, string unit, int digits)
{
GUILayout.BeginHorizontal((Il2CppReferenceArray<GUILayoutOption>)null);
GUILayout.Label(label + ": " + entry.Value.ToString(Formats[digits]) + unit, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) });
float num = GUILayout.HorizontalSlider(entry.Value, min, max, Array.Empty<GUILayoutOption>());
float num2 = Mathf.Pow(10f, (float)digits);
num = Mathf.Round(num * num2) / num2;
if (!Mathf.Approximately(num, entry.Value))
{
entry.Value = num;
}
float num3 = Default(entry);
bool flag = !Mathf.Approximately(entry.Value, num3);
if (GUILayout.Button(flag ? "↺" : "·", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) }) && flag)
{
entry.Value = num3;
_status = label + " back to default";
}
GUILayout.EndHorizontal();
}
private static void IntSlider(ConfigEntry<int> entry, string label, int min, int max)
{
GUILayout.BeginHorizontal((Il2CppReferenceArray<GUILayoutOption>)null);
GUILayout.Label($"{label}: {entry.Value}", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(170f) });
int num = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)entry.Value, (float)min, (float)max, Array.Empty<GUILayoutOption>()));
if (num != entry.Value)
{
entry.Value = num;
}
GUILayout.EndHorizontal();
}
private static float Default(ConfigEntry<float> entry)
{
try
{
return Convert.ToSingle(((ConfigEntryBase)entry).DefaultValue);
}
catch (Exception)
{
return entry.Value;
}
}
private static void Toggle(ConfigEntry<bool> entry, string label)
{
bool flag = GUILayout.Toggle(entry.Value, " " + label, Array.Empty<GUILayoutOption>());
if (flag != entry.Value)
{
entry.Value = flag;
}
}
private static void Save()
{
try
{
Plugin.Cfg.Save();
_status = "written to deck.bigwalk.bigtalk.cfg";
Plugin.Log.LogInfo((object)"settings saved from the panel");
}
catch (Exception ex)
{
_status = "could not save: " + ex.Message;
}
}
private static void Reload()
{
try
{
Plugin.Cfg.Reload();
_status = "reloaded from the file, losing this session's changes";
Plugin.Log.LogInfo((object)"settings reloaded from the file");
}
catch (Exception ex)
{
_status = "could not reload: " + ex.Message;
}
}
}
public enum TapPoint
{
Subscribers,
Encoder
}
internal static class VoiceTap
{
[HarmonyPatch(typeof(BasePreprocessingPipeline), "SendSamplesToSubscribers")]
internal static class SubscriberTap
{
[HarmonyPrefix]
private static void Prefix(BasePreprocessingPipeline __instance, Il2CppStructArray<float> buffer)
{
Interlocked.Increment(ref _subscriberFrames);
if (!_dead && Active == TapPoint.Subscribers && buffer != null)
{
Apply((Il2CppArrayBase<float>)(object)buffer, 0, ((Il2CppArrayBase<float>)(object)buffer).Length, RateOf(__instance));
}
}
}
[HarmonyPatch(typeof(EncoderPipeline), "ReceiveMicrophoneData")]
internal static class EncoderTap
{
[HarmonyPrefix]
private static void Prefix(ArraySegment<float> inputSamples, WaveFormat format)
{
//IL_007e: Unknown result type (might be due to invalid IL or missing references)
//IL_0084: Expected O, but got Unknown
Interlocked.Increment(ref _encoderFrames);
if (_dead || Active != TapPoint.Encoder)
{
return;
}
try
{
Il2CppArrayBase<float> array = inputSamples.Array;
if (array != null)
{
Apply(rate: _cachedRate = ((format != null && format.SampleRate > 0) ? format.SampleRate : _cachedRate), buffer: array, offset: inputSamples.Offset, count: inputSamples.Count);
}
}
catch (Exception ex)
{
if (++_failures < 10)
{
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(39, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val).AppendLiteral("encoder tap could not read its buffer: ");
((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex);
}
log.LogError(val);
}
else
{
_dead = true;
Plugin.Log.LogError((object)"encoder tap has failed too often and is now off for the session; voice chat itself is unaffected. Restart the game to try again.");
}
}
}
}
private const int FailureLimit = 10;
private const int DefaultRate = 48000;
private static float[] _scratch = new float[4096];
private static int _failures;
private static bool _dead;
private static int _cachedRate = 48000;
private static int _rateAge;
private static long _subscriberFrames;
private static long _encoderFrames;
public static volatile TapPoint Active = TapPoint.Subscribers;
public static long SubscriberFrames => Interlocked.Read(in _subscriberFrames);
public static long EncoderFrames => Interlocked.Read(in _encoderFrames);
public static bool Dead => _dead;
private static void Apply(Il2CppArrayBase<float> buffer, int offset, int count, int rate)
{
//IL_0115: Unknown result type (might be due to invalid IL or missing references)
//IL_011c: Expected O, but got Unknown
if (_dead || buffer == null || count <= 0)
{
return;
}
int rate2 = ((rate > 0) ? rate : _cachedRate);
Rack.Need wanted = Rack.Wanted;
if (wanted == Rack.Need.Nothing)
{
Rack.Note(count, rate2);
return;
}
try
{
if (_scratch.Length < count)
{
_scratch = new float[count];
}
if (buffer is Il2CppStructArray<float> val)
{
Span<float> span = val.AsSpan();
span.Slice(offset, count).CopyTo(_scratch.AsSpan(0, count));
Rack.Render(_scratch, count, rate2);
if (wanted == Rack.Need.Full)
{
_scratch.AsSpan(0, count).CopyTo(span.Slice(offset, count));
}
return;
}
for (int i = 0; i < count; i++)
{
_scratch[i] = buffer[offset + i];
}
Rack.Render(_scratch, count, rate2);
if (wanted == Rack.Need.Full)
{
for (int j = 0; j < count; j++)
{
buffer[offset + j] = _scratch[j];
}
}
}
catch (Exception ex)
{
if (++_failures < 10)
{
ManualLogSource log = Plugin.Log;
bool flag = default(bool);
BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(44, 1, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("voice tap failed, leaving this frame alone: ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex);
}
log.LogError(val2);
}
else
{
_dead = true;
Plugin.Log.LogError((object)"voice tap has failed too often and is now off for the session; voice chat itself is unaffected. Restart the game to try again.");
}
}
}
private static int RateOf(BasePreprocessingPipeline pipeline)
{
if (--_rateAge > 0)
{
return _cachedRate;
}
_rateAge = 100;
try
{
WaveFormat outputFormat = pipeline.OutputFormat;
if (outputFormat != null && outputFormat.SampleRate > 0)
{
_cachedRate = outputFormat.SampleRate;
}
}
catch (Exception)
{
}
return _cachedRate;
}
}
}