using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Voice;
using Photon.Voice.Unity;
using UnityEngine;
using UnityEngine.Networking;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("sasnews")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+32d474e6f962f181dcdbc32bc77786a062d91c21")]
[assembly: AssemblyProduct("super_soundboard")]
[assembly: AssemblyTitle("super_soundboard")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
internal sealed class NullableAttribute : Attribute
{
public readonly byte[] NullableFlags;
public NullableAttribute(byte P_0)
{
NullableFlags = new byte[1] { P_0 };
}
public NullableAttribute(byte[] P_0)
{
NullableFlags = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
internal sealed class NullableContextAttribute : Attribute
{
public readonly byte Flag;
public NullableContextAttribute(byte P_0)
{
Flag = P_0;
}
}
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace super_soundboard
{
public class AudioStreamAdapter : Stream
{
private readonly CircularBuffer _buffer;
private readonly AutoResetEvent _dataAvailable = new AutoResetEvent(initialState: false);
private bool _isClosed = false;
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => _buffer.Count;
public override long Position
{
get
{
return 0L;
}
set
{
throw new NotSupportedException();
}
}
public AudioStreamAdapter(int bufferSize)
{
_buffer = new CircularBuffer(bufferSize);
}
public override void Flush()
{
}
public override int Read(byte[] buffer, int offset, int count)
{
int num = 0;
while (num < count)
{
if (_isClosed && _buffer.Count == 0)
{
return num;
}
int num2 = _buffer.Read(buffer, offset + num, count - num);
if (num2 == 0)
{
if (_isClosed)
{
return num;
}
_dataAvailable.WaitOne(100);
}
else
{
num += num2;
}
}
return num;
}
public override void Write(byte[] buffer, int offset, int count)
{
if (!_isClosed)
{
_buffer.Write(buffer, offset, count);
_dataAvailable.Set();
}
}
public void Write(float[] samples)
{
byte[] array = new byte[samples.Length * 2];
for (int i = 0; i < samples.Length; i++)
{
float num = samples[i];
if (num > 1f)
{
num = 1f;
}
if (num < -1f)
{
num = -1f;
}
short num2 = (short)(num * 32767f);
array[i * 2] = (byte)((uint)num2 & 0xFFu);
array[i * 2 + 1] = (byte)((uint)(num2 >> 8) & 0xFFu);
}
Write(array, 0, array.Length);
}
public override void Close()
{
_isClosed = true;
_dataAvailable.Set();
base.Close();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
}
public class CircularBuffer
{
private readonly byte[] _buffer;
private int _head;
private int _tail;
private int _count;
private readonly object _lock = new object();
public int Count
{
get
{
lock (_lock)
{
return _count;
}
}
}
public CircularBuffer(int size)
{
_buffer = new byte[size];
}
public int Read(byte[] buffer, int offset, int count)
{
lock (_lock)
{
if (_count == 0)
{
return 0;
}
int num = Math.Min(count, _count);
int num2 = Math.Min(_buffer.Length - _head, num);
Array.Copy(_buffer, _head, buffer, offset, num2);
Array.Copy(_buffer, 0, buffer, offset + num2, num - num2);
_head = (_head + num) % _buffer.Length;
_count -= num;
return num;
}
}
public void Write(byte[] buffer, int offset, int count)
{
lock (_lock)
{
if (count <= _buffer.Length - _count)
{
int num = Math.Min(_buffer.Length - _tail, count);
Array.Copy(buffer, offset, _buffer, _tail, num);
Array.Copy(buffer, offset + num, _buffer, 0, count - num);
_tail = (_tail + count) % _buffer.Length;
_count += count;
}
}
}
}
[HarmonyPatch(typeof(PlayerController))]
internal static class ExamplePlayerControllerPatch
{
[HarmonyPrefix]
[HarmonyPatch("Start")]
private static void Start_Prefix(PlayerController __instance)
{
super_soundboard.Logger.LogDebug((object)$"{__instance} Start Prefix");
}
[HarmonyPostfix]
[HarmonyPatch("Start")]
private static void Start_Postfix(PlayerController __instance)
{
super_soundboard.Logger.LogDebug((object)$"{__instance} Start Postfix");
}
}
public class SoundboardLoopback
{
private int _readIdx;
private int _writeIdx;
private int _samplesAvailable;
private readonly float[] _samples;
private readonly object _lock = new object();
public AudioClip Clip { get; }
public int SamplesAvailable
{
get
{
lock (_lock)
{
return _samplesAvailable;
}
}
}
public SoundboardLoopback()
{
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Expected O, but got Unknown
//IL_0055: Expected O, but got Unknown
_samples = new float[24000];
Clip = AudioClip.Create("SoundboardLoopback", _samples.Length, 1, 48000, true, new PCMReaderCallback(OnRead), new PCMSetPositionCallback(OnSetPosition));
}
public void Push(float[] buffer)
{
lock (_lock)
{
foreach (float num in buffer)
{
_samples[_writeIdx++] = num;
if (_writeIdx >= _samples.Length)
{
_writeIdx = 0;
}
if (_samplesAvailable < _samples.Length)
{
_samplesAvailable++;
}
else
{
_readIdx = (_readIdx + 1) % _samples.Length;
}
}
}
}
private void OnRead(float[] outSamples)
{
lock (_lock)
{
Array.Clear(outSamples, 0, outSamples.Length);
int num = Math.Min(outSamples.Length, _samplesAvailable);
for (int i = 0; i < num; i++)
{
outSamples[i] = _samples[_readIdx++];
if (_readIdx >= _samples.Length)
{
_readIdx = 0;
}
}
_samplesAvailable -= num;
}
}
private void OnSetPosition(int position)
{
}
public int ReadInto(float[] buffer, int offset, int count)
{
int num = 0;
lock (_lock)
{
for (int i = 0; i < count; i++)
{
if (_samplesAvailable == 0)
{
break;
}
buffer[offset + i] = _samples[_readIdx++];
if (_readIdx >= _samples.Length)
{
_readIdx = 0;
}
_samplesAvailable--;
num++;
}
}
return num;
}
}
public class SoundboardMixer : IAudioReader<float>, IDataReader<float>, IDisposable, IAudioDesc
{
private readonly SoundboardLoopback _loopback = super_soundboard.LoopbackInstance;
private readonly PlayerVoiceChat _vc;
private AudioClip? _micClip;
private string? _micDevice;
private int _lastMicPos;
private bool _isDisposed = false;
private int _deviceIndex = 0;
private int _noProgressCount = 0;
private readonly int[] _sampleRates = new int[3] { 48000, 44100, 32000 };
private int _currentSampleRate = 48000;
private readonly Queue<float> _micBuffer = new Queue<float>();
private readonly object _bufferLock = new object();
private const int MAX_BUFFER_SIZE = 96000;
private IAudioReader<float>? _recorderAudioReader = null;
private object? _recorderMicInputObject = null;
private Func<float[], int>? _recorderReadFunc = null;
public int SamplingRate => 48000;
public int Channels => 1;
public string? Error { get; private set; } = null;
public bool IsDisposed => _isDisposed;
public bool IsEof => false;
public SoundboardMixer(PlayerVoiceChat voiceChat, object? micInput)
{
object micInput2 = micInput;
base..ctor();
_vc = voiceChat;
if (micInput2 != null)
{
try
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Using recorder-provided MicInput of type " + micInput2.GetType().FullName));
object obj = micInput2;
AudioClip val = (AudioClip)((obj is AudioClip) ? obj : null);
if (val != null)
{
_micClip = val;
_lastMicPos = 0;
super_soundboard.Logger.LogDebug((object)("super_soundboard: Using recorder's AudioClip MicInput ('" + (_micDevice ?? "(unknown)") + "')."));
return;
}
if (micInput2 is IAudioReader<float> recorderAudioReader)
{
_micClip = null;
_recorderAudioReader = recorderAudioReader;
super_soundboard.Logger.LogDebug((object)"super_soundboard: Using recorder's IAudioReader<float> MicInput.");
return;
}
_recorderMicInputObject = micInput2;
try
{
Type type = micInput2.GetType();
super_soundboard.Logger.LogDebug((object)("super_soundboard: Recorder MicInput type: " + type.FullName));
MethodInfo method = type.GetMethod("Read", new Type[1] { typeof(float[]) });
MethodInfo methodInfo = type.GetMethod("ReadInto", new Type[3]
{
typeof(float[]),
typeof(int),
typeof(int)
}) ?? type.GetMethod("ReadInto", new Type[2]
{
typeof(float[]),
typeof(int)
});
MethodInfo method2 = type.GetMethod("ReadSamples", new Type[1] { typeof(float[]) });
MethodInfo method3 = type.GetMethod("Read", new Type[1] { typeof(byte[]) });
MethodInfo chosen = null;
if (method != null)
{
chosen = method;
}
else if (method2 != null)
{
chosen = method2;
}
else if (methodInfo != null)
{
chosen = methodInfo;
}
if (chosen != null)
{
_recorderReadFunc = delegate(float[] buf)
{
try
{
object obj2 = chosen.Invoke(micInput2, new object[1] { buf });
if (obj2 == null)
{
return -1;
}
if (obj2 is bool flag)
{
return flag ? buf.Length : 0;
}
if (obj2 is int result)
{
return result;
}
return -1;
}
catch (Exception ex4)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Recorder MicInput read invocation failed: " + ex4.Message));
return -1;
}
};
super_soundboard.Logger.LogDebug((object)("super_soundboard: Recorder MicInput supports method '" + chosen.Name + "', using it to read samples."));
}
else
{
super_soundboard.Logger.LogDebug((object)"super_soundboard: Recorder MicInput does not expose a compatible Read method.");
}
}
catch (Exception ex)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Error inspecting Recorder MicInput: " + ex.Message));
}
}
catch (Exception ex2)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Error handling recorder MicInput: " + ex2.Message));
}
}
if (!super_soundboard.AutoStartMicrophone)
{
super_soundboard.Logger.LogDebug((object)"super_soundboard: AutoStartMicrophone is disabled; not starting local Microphone.");
return;
}
try
{
StartLocalMicInternal();
}
catch (Exception ex3)
{
super_soundboard.Logger.LogWarning((object)("super_soundboard: Failed to start microphone: " + ex3.Message));
}
}
public void EnsureLocalMicStarted()
{
if (_recorderAudioReader != null || _recorderReadFunc != null || (Object)(object)_micClip != (Object)null)
{
super_soundboard.Logger.LogDebug((object)"super_soundboard: Local mic start skipped; recorder input already present or mic already started.");
return;
}
try
{
StartLocalMicInternal();
}
catch (Exception ex)
{
super_soundboard.Logger.LogWarning((object)("super_soundboard: EnsureLocalMicStarted failed: " + ex.Message));
}
}
public void StopLocalMic()
{
try
{
if (!string.IsNullOrEmpty(_micDevice))
{
Microphone.End(_micDevice);
super_soundboard.Logger.LogDebug((object)("super_soundboard: Stopped microphone '" + _micDevice + "' via StopLocalMic."));
_micDevice = null;
_micClip = null;
_noProgressCount = 0;
}
}
catch (Exception ex)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: StopLocalMic exception: " + ex.Message));
}
}
private void StartLocalMicInternal()
{
string[] devices = Microphone.devices;
if (devices.Length != 0)
{
_deviceIndex = 0;
_micDevice = devices[_deviceIndex];
_currentSampleRate = _sampleRates[0];
_micClip = Microphone.Start(_micDevice, true, 10, _currentSampleRate);
_lastMicPos = 0;
super_soundboard.Logger.LogDebug((object)("super_soundboard: Started microphone '" + _micDevice + "' with 10s buffer"));
if (super_soundboard.AutoSwitchMicrophone)
{
super_soundboard.Logger.LogDebug((object)"super_soundboard: Microphone auto-switch enabled; will try other devices if no data is produced.");
}
}
else
{
super_soundboard.Logger.LogWarning((object)"super_soundboard: No microphone devices found!");
}
}
public void Update()
{
if (_isDisposed)
{
return;
}
float[] array = null;
if ((Object)(object)_micClip != (Object)null)
{
int position = Microphone.GetPosition(_micDevice);
int num = ((position < _lastMicPos) ? (_micClip.samples - _lastMicPos + position) : (position - _lastMicPos));
if (position == _lastMicPos)
{
_noProgressCount++;
}
else
{
_noProgressCount = 0;
}
if (super_soundboard.AutoSwitchMicrophone && _noProgressCount > 120)
{
super_soundboard.Logger.LogWarning((object)$"SoundboardMixer: No progress on microphone '{_micDevice}' detected; attempting to switch device (attempts: {_noProgressCount}).");
TrySwitchDevice();
return;
}
if (num > 0)
{
array = new float[num];
if (_lastMicPos + num <= _micClip.samples)
{
_micClip.GetData(array, _lastMicPos);
}
else
{
int num2 = _micClip.samples - _lastMicPos;
int num3 = num - num2;
float[] array2 = new float[num2];
_micClip.GetData(array2, _lastMicPos);
Array.Copy(array2, 0, array, 0, num2);
float[] array3 = new float[num3];
_micClip.GetData(array3, 0);
Array.Copy(array3, 0, array, num2, num3);
}
_lastMicPos = (_lastMicPos + num) % _micClip.samples;
}
}
else
{
int num4 = 1024;
float[] array4 = new float[num4];
bool flag = false;
int num5 = 0;
if (_recorderAudioReader != null)
{
try
{
if (((IDataReader<float>)(object)_recorderAudioReader).Read(array4))
{
flag = true;
num5 = array4.Length;
}
}
catch (Exception ex)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Recorder IAudioReader read failed: " + ex.Message));
}
}
else if (_recorderReadFunc != null)
{
try
{
num5 = _recorderReadFunc(array4);
if (num5 > 0)
{
flag = true;
}
}
catch (Exception ex2)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Recorder read func failed: " + ex2.Message));
}
}
if (flag && num5 > 0)
{
array = new float[num5];
Array.Copy(array4, 0, array, 0, num5);
}
}
if (array == null || array.Length == 0)
{
return;
}
if ((Object)(object)super_soundboard.SpeechManagerInstance != (Object)null)
{
super_soundboard.SpeechManagerInstance.ProcessAudio(array);
}
lock (_bufferLock)
{
float[] array5 = array;
foreach (float item in array5)
{
_micBuffer.Enqueue(item);
}
while (_micBuffer.Count > 96000)
{
_micBuffer.Dequeue();
}
}
}
public bool Read(float[] buffer)
{
if (_isDisposed)
{
Array.Clear(buffer, 0, buffer.Length);
return false;
}
bool flag = (Object)(object)_micClip != (Object)null || _recorderAudioReader != null || _recorderReadFunc != null;
lock (_bufferLock)
{
int count = _micBuffer.Count;
if (flag)
{
if (count < buffer.Length)
{
return false;
}
}
else if (_loopback.SamplesAvailable == 0)
{
return false;
}
int num = Math.Min(count, buffer.Length);
for (int i = 0; i < num; i++)
{
buffer[i] = _micBuffer.Dequeue();
}
for (int j = num; j < buffer.Length; j++)
{
buffer[j] = 0f;
}
}
if (_loopback.SamplesAvailable > 0)
{
float[] array = new float[buffer.Length];
int num2 = _loopback.ReadInto(array, 0, buffer.Length);
for (int k = 0; k < num2; k++)
{
buffer[k] = Math.Max(-1f, Math.Min(1f, buffer[k] + array[k]));
}
}
float masterVolume = super_soundboard.MasterVolume;
if (Math.Abs(masterVolume - 1f) > 0.0001f)
{
for (int l = 0; l < buffer.Length; l++)
{
buffer[l] *= masterVolume;
}
}
return true;
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
try
{
if (!string.IsNullOrEmpty(_micDevice))
{
Microphone.End(_micDevice);
super_soundboard.Logger.LogDebug((object)("super_soundboard: Stopped microphone '" + _micDevice + "' on Dispose."));
_micDevice = null;
_micClip = null;
}
}
catch (Exception ex)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Exception while stopping microphone: " + ex.Message));
}
}
private void TrySwitchDevice()
{
try
{
string[] devices = Microphone.devices;
if (devices == null || devices.Length == 0)
{
super_soundboard.Logger.LogWarning((object)"super_soundboard: TrySwitchDevice: no microphone devices available.");
return;
}
int num = (_deviceIndex + 1) % devices.Length;
for (int i = 0; i < devices.Length; i++)
{
int num2 = (num + i) % devices.Length;
string text = devices[num2];
int[] sampleRates = _sampleRates;
foreach (int num3 in sampleRates)
{
try
{
if (!string.IsNullOrEmpty(_micDevice))
{
Microphone.End(_micDevice);
}
}
catch
{
}
try
{
AudioClip val = Microphone.Start(text, true, 10, num3);
int position = Microphone.GetPosition(text);
if (position > 0 && (Object)(object)val != (Object)null)
{
_micDevice = text;
_micClip = val;
_lastMicPos = position;
_deviceIndex = num2;
_currentSampleRate = num3;
_noProgressCount = 0;
super_soundboard.Logger.LogDebug((object)$"super_soundboard: Switched to microphone '{text}' at {num3} Hz");
return;
}
}
catch (Exception ex)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Try start mic '" + text + "' failed: " + ex.Message));
}
}
}
string text2 = devices[num];
try
{
if (!string.IsNullOrEmpty(_micDevice))
{
Microphone.End(_micDevice);
}
}
catch
{
}
try
{
_micDevice = text2;
_micClip = Microphone.Start(_micDevice, true, 10, _currentSampleRate);
_lastMicPos = 0;
_deviceIndex = num;
_noProgressCount = 0;
super_soundboard.Logger.LogDebug((object)("super_soundboard: Started microphone '" + _micDevice + "' after switching (no immediate progress)."));
}
catch (Exception ex2)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Failed to start fallback mic '" + text2 + "': " + ex2.Message));
}
}
catch (Exception ex3)
{
super_soundboard.Logger.LogDebug((object)("super_soundboard: Exception in TrySwitchDevice: " + ex3.Message));
}
}
}
public class SpeechManager : MonoBehaviour
{
[CompilerGenerated]
private sealed class <DownloadAndCache>d__26 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public string filename;
public SpeechManager <>4__this;
private string <localPath>5__1;
private string <uri>5__2;
private Task<byte[]> <task>5__3;
private Exception <ex>5__4;
private UnityWebRequest <www>5__5;
private bool <hasError>5__6;
private AudioClip <clip>5__7;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <DownloadAndCache>d__26(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 2)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<localPath>5__1 = null;
<uri>5__2 = null;
<task>5__3 = null;
<ex>5__4 = null;
<www>5__5 = null;
<clip>5__7 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0285: Unknown result type (might be due to invalid IL or missing references)
//IL_028b: Invalid comparison between Unknown and I4
//IL_0238: Unknown result type (might be due to invalid IL or missing references)
try
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
if (<>4__this._soundCache.ContainsKey(filename))
{
return false;
}
if (!<>4__this.IsValidFilename(filename))
{
super_soundboard.Logger.LogError((object)("Invalid filename detected: " + filename + ". Filename contains forbidden characters."));
return false;
}
<localPath>5__1 = Path.Combine(<>4__this._cacheDir, filename);
if (!File.Exists(<localPath>5__1))
{
<task>5__3 = _httpClient.GetByteArrayAsync(<>4__this.ApiBaseUrl + "/api/sounds/" + filename);
goto IL_0114;
}
goto IL_0200;
case 1:
<>1__state = -1;
goto IL_0114;
case 2:
{
<>1__state = -3;
<hasError>5__6 = false;
<hasError>5__6 = (int)<www>5__5.result != 1;
if (<hasError>5__6)
{
super_soundboard.Logger.LogError((object)("Failed to load cached sound " + filename + ": " + <www>5__5.error));
}
else
{
<clip>5__7 = DownloadHandlerAudioClip.GetContent(<www>5__5);
if ((Object)(object)<clip>5__7 != (Object)null)
{
((Object)<clip>5__7).name = filename;
<>4__this._soundCache[filename] = <clip>5__7;
super_soundboard.Logger.LogDebug((object)("Cached sound: " + filename));
}
<clip>5__7 = null;
}
<>m__Finally1();
<www>5__5 = null;
return false;
}
IL_0114:
if (!<task>5__3.IsCompleted)
{
<>2__current = null;
<>1__state = 1;
return true;
}
if (<task>5__3.IsFaulted)
{
super_soundboard.Logger.LogError((object)("Failed to download sound " + filename + ": " + (<task>5__3.Exception?.InnerException?.Message ?? <task>5__3.Exception?.Message)));
return false;
}
try
{
File.WriteAllBytes(<localPath>5__1, <task>5__3.Result);
}
catch (Exception ex)
{
<ex>5__4 = ex;
super_soundboard.Logger.LogError((object)("Failed to write sound file " + <localPath>5__1 + ": " + <ex>5__4.Message));
return false;
}
<task>5__3 = null;
goto IL_0200;
IL_0200:
<uri>5__2 = "file://" + <localPath>5__1.Replace("\\", "/");
<www>5__5 = UnityWebRequestMultimedia.GetAudioClip(<uri>5__2, <>4__this.GetAudioTypeFromExtension(filename));
<>1__state = -3;
<>2__current = <www>5__5.SendWebRequest();
<>1__state = 2;
return true;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<www>5__5 != null)
{
((IDisposable)<www>5__5).Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <DownloadAndCacheAndPlay>d__41 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public string filename;
public float volumePercent;
public SpeechManager <>4__this;
private AudioClip <clip>5__1;
private float <localVolumeScale>5__2;
private float <remoteVolumeScale>5__3;
private AudioSource <source>5__4;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <DownloadAndCacheAndPlay>d__41(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<clip>5__1 = null;
<source>5__4 = null;
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = <>4__this.DownloadAndCache(filename);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
if (<>4__this._soundCache.TryGetValue(filename, out <clip>5__1))
{
super_soundboard.Logger.LogInfo((object)$"Playing sound after download: {filename} (Vol: {volumePercent}%)");
<localVolumeScale>5__2 = volumePercent / 100f * super_soundboard.LocalVolume * super_soundboard.MasterVolume;
<remoteVolumeScale>5__3 = volumePercent / 100f * super_soundboard.RemoteVolume * super_soundboard.MasterVolume;
if ((Object)(object)super_soundboard.Instance != (Object)null)
{
<source>5__4 = ((Component)super_soundboard.Instance).GetComponent<AudioSource>();
if ((Object)(object)<source>5__4 != (Object)null)
{
<source>5__4.PlayOneShot(<clip>5__1, <localVolumeScale>5__2);
}
<source>5__4 = null;
}
((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.PushToLoopback(<clip>5__1, <remoteVolumeScale>5__3));
}
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <FetchConfigAndStart>d__20 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public SpeechManager <>4__this;
private int <retryCount>5__1;
private Task<string> <task>5__2;
private string <json>5__3;
private Mapping[] <>s__4;
private int <>s__5;
private Mapping <m>5__6;
private Exception <ex>5__7;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <FetchConfigAndStart>d__20(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<task>5__2 = null;
<json>5__3 = null;
<>s__4 = null;
<m>5__6 = null;
<ex>5__7 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0151: Unknown result type (might be due to invalid IL or missing references)
//IL_015b: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<retryCount>5__1 = 0;
goto IL_03cb;
case 1:
<>1__state = -1;
goto IL_00b0;
case 2:
{
<>1__state = -1;
goto IL_03cb;
}
IL_03cb:
if (<retryCount>5__1 < 10)
{
super_soundboard.Logger.LogDebug((object)$"SpeechManager: Fetching config from {<>4__this.ApiBaseUrl}/api/config (Attempt {<retryCount>5__1 + 1}/{10})...");
<task>5__2 = _httpClient.GetStringAsync(<>4__this.ApiBaseUrl + "/api/config");
goto IL_00b0;
}
return false;
IL_00b0:
if (!<task>5__2.IsCompleted)
{
<>2__current = null;
<>1__state = 1;
return true;
}
if (<task>5__2.IsFaulted)
{
super_soundboard.Logger.LogWarning((object)("Failed to fetch config: " + (<task>5__2.Exception?.InnerException?.Message ?? <task>5__2.Exception?.Message)));
<retryCount>5__1++;
if (<retryCount>5__1 < 10)
{
<>2__current = (object)new WaitForSeconds(2f);
<>1__state = 2;
return true;
}
super_soundboard.Logger.LogError((object)$"SpeechManager: Gave up fetching config after {10} attempts.");
return false;
}
super_soundboard.Logger.LogDebug((object)"SpeechManager: Config response received, parsing...");
try
{
<json>5__3 = <task>5__2.Result;
super_soundboard.Logger.LogDebug((object)$"SpeechManager: Config JSON length: {<json>5__3.Length}");
<>4__this._config = new ConfigData();
<>4__this._config.mappings = <>4__this.ParseMappings(<json>5__3);
if (<>4__this._config?.mappings != null && <>4__this._config.mappings.Length != 0)
{
super_soundboard.Logger.LogDebug((object)$"Loaded {<>4__this._config.mappings.Length} mappings.");
<>s__4 = <>4__this._config.mappings;
for (<>s__5 = 0; <>s__5 < <>s__4.Length; <>s__5++)
{
<m>5__6 = <>s__4[<>s__5];
if (<m>5__6.keywords != null && <m>5__6.keywords.Length != 0)
{
super_soundboard.Logger.LogDebug((object)string.Format("Mapping: [{0}] -> {1} (Vol: {2})", string.Join(", ", <m>5__6.keywords), <m>5__6.file, <m>5__6.volume));
}
<m>5__6 = null;
}
<>s__4 = null;
((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.PreloadAllSounds());
<>4__this.StartRecognition();
}
else
{
super_soundboard.Logger.LogError((object)"Failed to parse mappings from config.");
}
return false;
}
catch (Exception ex)
{
<ex>5__7 = ex;
super_soundboard.Logger.LogError((object)("Error parsing config: " + <ex>5__7.Message));
super_soundboard.Logger.LogError((object)("Stack trace: " + <ex>5__7.StackTrace));
return false;
}
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <PreloadAllSounds>d__25 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public SpeechManager <>4__this;
private HashSet<string> <uniqueFiles>5__1;
private Mapping[] <>s__2;
private int <>s__3;
private Mapping <mapping>5__4;
private HashSet<string>.Enumerator <>s__5;
private string <filename>5__6;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <PreloadAllSounds>d__25(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<uniqueFiles>5__1 = null;
<>s__2 = null;
<mapping>5__4 = null;
<>s__5 = default(HashSet<string>.Enumerator);
<filename>5__6 = null;
<>1__state = -2;
}
private bool MoveNext()
{
if (<>1__state != 0)
{
return false;
}
<>1__state = -1;
if (<>4__this._config == null || <>4__this._config.mappings == null || <>4__this._config.mappings.Length == 0)
{
super_soundboard.Logger.LogWarning((object)"No mappings to preload");
return false;
}
<uniqueFiles>5__1 = new HashSet<string>();
<>s__2 = <>4__this._config.mappings;
for (<>s__3 = 0; <>s__3 < <>s__2.Length; <>s__3++)
{
<mapping>5__4 = <>s__2[<>s__3];
if (!string.IsNullOrEmpty(<mapping>5__4.file))
{
<uniqueFiles>5__1.Add(<mapping>5__4.file);
}
<mapping>5__4 = null;
}
<>s__2 = null;
if (<uniqueFiles>5__1.Count > 0)
{
<>4__this._cachedSoundList = new string[<uniqueFiles>5__1.Count];
<uniqueFiles>5__1.CopyTo(<>4__this._cachedSoundList);
super_soundboard.Logger.LogDebug((object)$"Preloading {<uniqueFiles>5__1.Count} mapped sounds...");
<>s__5 = <uniqueFiles>5__1.GetEnumerator();
try
{
while (<>s__5.MoveNext())
{
<filename>5__6 = <>s__5.Current;
((MonoBehaviour)<>4__this).StartCoroutine(<>4__this.DownloadAndCache(<filename>5__6));
<filename>5__6 = null;
}
}
finally
{
((IDisposable)<>s__5).Dispose();
}
<>s__5 = default(HashSet<string>.Enumerator);
}
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <PushToLoopback>d__42 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public AudioClip clip;
public float volumeScale;
public SpeechManager <>4__this;
private float[] <data>5__1;
private float[] <monoData>5__2;
private float[] <resampledData>5__3;
private int <position>5__4;
private int <chunkSize>5__5;
private int <i>5__6;
private int <i>5__7;
private float <ratio>5__8;
private int <newLength>5__9;
private int <i>5__10;
private float <srcPos>5__11;
private int <srcIdx>5__12;
private float <frac>5__13;
private int <i>5__14;
private int <remaining>5__15;
private int <count>5__16;
private float[] <chunk>5__17;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <PushToLoopback>d__42(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<data>5__1 = null;
<monoData>5__2 = null;
<resampledData>5__3 = null;
<chunk>5__17 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0461: Unknown result type (might be due to invalid IL or missing references)
//IL_046b: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<data>5__1 = new float[clip.samples * clip.channels];
clip.GetData(<data>5__1, 0);
if (clip.channels == 2)
{
<monoData>5__2 = new float[clip.samples];
<i>5__6 = 0;
while (<i>5__6 < clip.samples)
{
<monoData>5__2[<i>5__6] = (<data>5__1[<i>5__6 * 2] + <data>5__1[<i>5__6 * 2 + 1]) * 0.5f;
<i>5__6++;
}
}
else if (clip.channels == 1)
{
<monoData>5__2 = <data>5__1;
}
else
{
super_soundboard.Logger.LogWarning((object)$"Unsupported channel count: {clip.channels}. Using first channel.");
<monoData>5__2 = new float[clip.samples];
<i>5__7 = 0;
while (<i>5__7 < clip.samples)
{
<monoData>5__2[<i>5__7] = <data>5__1[<i>5__7 * clip.channels];
<i>5__7++;
}
}
if (clip.frequency != 48000)
{
<ratio>5__8 = 48000f / (float)clip.frequency;
<newLength>5__9 = (int)((float)<monoData>5__2.Length * <ratio>5__8);
<resampledData>5__3 = new float[<newLength>5__9];
<i>5__10 = 0;
while (<i>5__10 < <newLength>5__9)
{
<srcPos>5__11 = (float)<i>5__10 / <ratio>5__8;
<srcIdx>5__12 = (int)<srcPos>5__11;
<frac>5__13 = <srcPos>5__11 - (float)<srcIdx>5__12;
if (<srcIdx>5__12 + 1 < <monoData>5__2.Length)
{
<resampledData>5__3[<i>5__10] = <monoData>5__2[<srcIdx>5__12] * (1f - <frac>5__13) + <monoData>5__2[<srcIdx>5__12 + 1] * <frac>5__13;
}
else if (<srcIdx>5__12 < <monoData>5__2.Length)
{
<resampledData>5__3[<i>5__10] = <monoData>5__2[<srcIdx>5__12];
}
<i>5__10++;
}
super_soundboard.Logger.LogDebug((object)$"Resampled audio from {clip.frequency}Hz to 48000Hz ({<monoData>5__2.Length} -> {<newLength>5__9} samples)");
}
else
{
<resampledData>5__3 = <monoData>5__2;
}
if (Math.Abs(volumeScale - 1f) > 0.01f)
{
<i>5__14 = 0;
while (<i>5__14 < <resampledData>5__3.Length)
{
<resampledData>5__3[<i>5__14] *= volumeScale;
<i>5__14++;
}
}
<position>5__4 = 0;
<chunkSize>5__5 = 4800;
break;
case 1:
<>1__state = -1;
<chunk>5__17 = null;
break;
}
if (<position>5__4 < <resampledData>5__3.Length)
{
<remaining>5__15 = <resampledData>5__3.Length - <position>5__4;
<count>5__16 = Math.Min(<chunkSize>5__5, <remaining>5__15);
<chunk>5__17 = new float[<count>5__16];
Array.Copy(<resampledData>5__3, <position>5__4, <chunk>5__17, 0, <count>5__16);
super_soundboard.LoopbackInstance?.Push(<chunk>5__17);
<position>5__4 += <count>5__16;
<>2__current = (object)new WaitForSeconds(0.1f);
<>1__state = 1;
return true;
}
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <SendAudioData>d__32 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public float[] samples;
public SpeechManager <>4__this;
private byte[] <pcmData>5__1;
private string <base64Audio>5__2;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <SendAudioData>d__32(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<pcmData>5__1 = null;
<base64Audio>5__2 = null;
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<pcmData>5__1 = <>4__this.ConvertToPCM16(samples);
<base64Audio>5__2 = Convert.ToBase64String(<pcmData>5__1);
<>2__current = <>4__this.SendToGoogleSpeechToText(<base64Audio>5__2);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
[CompilerGenerated]
private sealed class <SendToGoogleSpeechToText>d__34 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
public string base64Audio;
public SpeechManager <>4__this;
private string <apiKey>5__1;
private string <url>5__2;
private string <jsonBody>5__3;
private UnityWebRequest <www>5__4;
private byte[] <bodyRaw>5__5;
private string <responseText>5__6;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <SendToGoogleSpeechToText>d__34(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
int num = <>1__state;
if (num == -3 || num == 1)
{
try
{
}
finally
{
<>m__Finally1();
}
}
<apiKey>5__1 = null;
<url>5__2 = null;
<jsonBody>5__3 = null;
<www>5__4 = null;
<bodyRaw>5__5 = null;
<responseText>5__6 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Expected O, but got Unknown
//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
//IL_00e0: Expected O, but got Unknown
//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
//IL_00f1: Expected O, but got Unknown
//IL_0166: Unknown result type (might be due to invalid IL or missing references)
//IL_0181: Unknown result type (might be due to invalid IL or missing references)
//IL_0187: Invalid comparison between Unknown and I4
try
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<apiKey>5__1 = super_soundboard.GoogleApiKey;
if (string.IsNullOrEmpty(<apiKey>5__1))
{
super_soundboard.Logger.LogError((object)"SpeechManager: API Key is empty! Cannot send request.");
return false;
}
<url>5__2 = "https://speech.googleapis.com/v1/speech:recognize?key=" + <apiKey>5__1;
<jsonBody>5__3 = $"\r\n {{\r\n \"config\": {{\r\n \"encoding\": \"LINEAR16\",\r\n \"sampleRateHertz\": {16000},\r\n \"languageCode\": \"{super_soundboard.GoogleSpeechLanguage}\",\r\n \"model\": \"default\"\r\n }},\r\n \"audio\": {{\r\n \"content\": \"{base64Audio}\"\r\n }}\r\n }}";
<www>5__4 = new UnityWebRequest(<url>5__2, "POST");
<>1__state = -3;
<bodyRaw>5__5 = Encoding.UTF8.GetBytes(<jsonBody>5__3);
<www>5__4.uploadHandler = (UploadHandler)new UploadHandlerRaw(<bodyRaw>5__5);
<www>5__4.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
<www>5__4.SetRequestHeader("Content-Type", "application/json");
super_soundboard.Logger.LogDebug((object)("SpeechManager: Sending request to Google API... (Key: " + <apiKey>5__1.Substring(0, 5) + "...)"));
<>2__current = <www>5__4.SendWebRequest();
<>1__state = 1;
return true;
case 1:
<>1__state = -3;
super_soundboard.Logger.LogDebug((object)$"SpeechManager: Request completed. Result: {<www>5__4.result}");
if ((int)<www>5__4.result == 1)
{
<responseText>5__6 = <www>5__4.downloadHandler.text;
super_soundboard.Logger.LogDebug((object)("SpeechManager: Google API Response: " + <responseText>5__6));
<>4__this.ParseGoogleResponse(<responseText>5__6);
<responseText>5__6 = null;
}
else
{
super_soundboard.Logger.LogError((object)("Google API Error: " + <www>5__4.error));
super_soundboard.Logger.LogError((object)$"Response Code: {<www>5__4.responseCode}");
super_soundboard.Logger.LogError((object)("Response Body: " + <www>5__4.downloadHandler.text));
}
<bodyRaw>5__5 = null;
<>m__Finally1();
<www>5__4 = null;
return false;
}
}
catch
{
//try-fault
((IDisposable)this).Dispose();
throw;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
private void <>m__Finally1()
{
<>1__state = -1;
if (<www>5__4 != null)
{
((IDisposable)<www>5__4).Dispose();
}
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private static readonly HttpClient _httpClient = new HttpClient();
private string _cacheDir = "";
private const int SAMPLE_RATE = 16000;
private const float SILENCE_THRESHOLD_TIME = 0.5f;
private const float MAX_RECORDING_DURATION = 5f;
private const float VAD_THRESHOLD = 0.001f;
private float _silenceTimer = 0f;
private float _recordingDuration = 0f;
private bool _isSpeaking = false;
private List<float> _currentUtterance = new List<float>();
private object _audioLock = new object();
private int _logThrottle = 0;
private ConfigData? _config;
private Dictionary<string, AudioClip> _soundCache = new Dictionary<string, AudioClip>();
private string[]? _cachedSoundList;
private readonly Queue<Action> _mainThreadActions = new Queue<Action>();
private string ApiBaseUrl => super_soundboard.ApiBaseUrl;
public void Initialize()
{
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".";
_cacheDir = Path.Combine(path, "cache");
if (!Directory.Exists(_cacheDir))
{
Directory.CreateDirectory(_cacheDir);
}
super_soundboard.Logger.LogDebug((object)("SpeechManager: Initializing, starting config fetch from " + ApiBaseUrl + "..."));
((MonoBehaviour)this).StartCoroutine(FetchConfigAndStart());
}
public void OnApiBaseUrlChanged()
{
super_soundboard.Logger.LogInfo((object)("SpeechManager: Detected ApiBaseUrl change to " + ApiBaseUrl + ". Refreshing config and clearing caches..."));
_config = null;
_cachedSoundList = null;
try
{
foreach (AudioClip value in _soundCache.Values)
{
if ((Object)(object)value != (Object)null)
{
Object.Destroy((Object)(object)value);
}
}
}
catch (Exception ex)
{
super_soundboard.Logger.LogWarning((object)("SpeechManager: Error while destroying cached clips: " + ex.Message));
}
_soundCache.Clear();
try
{
_httpClient.BaseAddress = new Uri(ApiBaseUrl);
}
catch
{
}
((MonoBehaviour)this).StartCoroutine(FetchConfigAndStart());
}
[IteratorStateMachine(typeof(<FetchConfigAndStart>d__20))]
private IEnumerator FetchConfigAndStart()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <FetchConfigAndStart>d__20(0)
{
<>4__this = this
};
}
private Mapping[] ParseMappings(string json)
{
List<Mapping> list = new List<Mapping>();
try
{
Match match = Regex.Match(json, "\"mappings\"\\s*:\\s*\\[");
if (!match.Success)
{
super_soundboard.Logger.LogError((object)"Mappings array not found in JSON");
return new Mapping[0];
}
int num = match.Index + match.Length - 1;
int num2 = 0;
int num3 = num;
for (int i = num; i < json.Length; i++)
{
if (json[i] == '[')
{
num2++;
}
if (json[i] == ']')
{
num2--;
if (num2 == 0)
{
num3 = i;
break;
}
}
}
if (num3 <= num)
{
super_soundboard.Logger.LogError((object)"Could not find end of mappings array");
return new Mapping[0];
}
string text = json.Substring(num + 1, num3 - num - 1);
List<string> list2 = new List<string>();
int num4 = 0;
int num5 = -1;
for (int j = 0; j < text.Length; j++)
{
if (text[j] == '{')
{
if (num4 == 0)
{
num5 = j;
}
num4++;
}
else if (text[j] == '}')
{
num4--;
if (num4 == 0 && num5 != -1)
{
string item = text.Substring(num5, j - num5 + 1);
list2.Add(item);
num5 = -1;
}
}
}
super_soundboard.Logger.LogDebug((object)$"Found {list2.Count} mapping objects");
foreach (string item2 in list2)
{
Mapping mapping = new Mapping();
mapping.keywords = ExtractStringArray(item2, "keywords");
mapping.file = ExtractString(item2, "file");
mapping.volume = ExtractFloat(item2, "volume");
if (mapping.keywords != null && mapping.keywords.Length != 0 && !string.IsNullOrEmpty(mapping.file))
{
list.Add(mapping);
}
}
}
catch (Exception ex)
{
super_soundboard.Logger.LogError((object)("Error parsing mappings: " + ex.Message));
}
return list.ToArray();
}
private string[] ExtractStringArray(string json, string fieldName)
{
List<string> list = new List<string>();
try
{
string value = "\"" + fieldName + "\":[";
int num = json.IndexOf(value);
if (num == -1)
{
return new string[0];
}
int num2 = json.IndexOf("[", num);
int num3 = json.IndexOf("]", num2);
if (num2 == -1 || num3 == -1)
{
return new string[0];
}
string text = json.Substring(num2 + 1, num3 - num2 - 1);
bool flag = false;
int num4 = -1;
for (int i = 0; i < text.Length; i++)
{
if (text[i] != '"')
{
continue;
}
if (!flag)
{
num4 = i + 1;
flag = true;
continue;
}
if (num4 != -1)
{
string text2 = text.Substring(num4, i - num4);
if (!string.IsNullOrEmpty(text2))
{
list.Add(text2);
}
}
flag = false;
}
}
catch
{
}
return list.ToArray();
}
private string ExtractString(string json, string fieldName)
{
try
{
string text = "\"" + fieldName + "\":\"";
int num = json.IndexOf(text);
if (num == -1)
{
return "";
}
int num2 = num + text.Length;
int num3 = json.IndexOf("\"", num2);
if (num3 == -1)
{
return "";
}
return json.Substring(num2, num3 - num2);
}
catch
{
return "";
}
}
private float ExtractFloat(string json, string fieldName)
{
try
{
string text = "\"" + fieldName + "\":";
int num = json.IndexOf(text);
if (num == -1)
{
return 100f;
}
int num2 = num + text.Length;
int i;
for (i = num2; i < json.Length && (char.IsDigit(json[i]) || json[i] == '.' || json[i] == '-'); i++)
{
}
if (i > num2)
{
string s = json.Substring(num2, i - num2);
if (float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
{
return result;
}
}
}
catch
{
}
return 100f;
}
[IteratorStateMachine(typeof(<PreloadAllSounds>d__25))]
private IEnumerator PreloadAllSounds()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <PreloadAllSounds>d__25(0)
{
<>4__this = this
};
}
[IteratorStateMachine(typeof(<DownloadAndCache>d__26))]
private IEnumerator DownloadAndCache(string filename)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <DownloadAndCache>d__26(0)
{
<>4__this = this,
filename = filename
};
}
private AudioType GetAudioTypeFromExtension(string filePath)
{
//IL_003c: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
string text = Path.GetExtension(filePath).ToLowerInvariant();
if (1 == 0)
{
}
AudioType result = (AudioType)(text switch
{
".wav" => 20,
".ogg" => 14,
".mp3" => 13,
_ => 20,
});
if (1 == 0)
{
}
return result;
}
private bool IsValidFilename(string filename)
{
if (string.IsNullOrWhiteSpace(filename))
{
return false;
}
string fileName = Path.GetFileName(filename);
if (fileName != filename)
{
return false;
}
string text = Path.GetExtension(fileName).ToLowerInvariant();
return text == ".wav" || text == ".ogg" || text == ".mp3";
}
private void StartRecognition()
{
if (!super_soundboard.SpeechRecognitionEnabled)
{
super_soundboard.Logger.LogDebug((object)"Speech recognition is disabled in config.");
}
else if (string.IsNullOrEmpty(super_soundboard.GoogleApiKey))
{
super_soundboard.Logger.LogWarning((object)"Google API Key is missing. Speech recognition will not work.");
}
else
{
super_soundboard.Logger.LogDebug((object)"SpeechManager: Ready to receive audio from SoundboardMixer.");
}
}
public void ProcessAudio(float[] buffer)
{
if (!super_soundboard.SpeechRecognitionEnabled || string.IsNullOrEmpty(super_soundboard.GoogleApiKey))
{
return;
}
int num = buffer.Length / 3;
float[] array = new float[num];
for (int i = 0; i < num; i++)
{
array[i] = buffer[i * 3];
}
float num2 = 0f;
for (int j = 0; j < array.Length; j++)
{
if (Mathf.Abs(array[j]) > num2)
{
num2 = Mathf.Abs(array[j]);
}
}
if (_logThrottle++ > 100)
{
_logThrottle = 0;
if (num2 > 0f)
{
super_soundboard.Logger.LogDebug((object)$"SpeechManager: MaxVol={num2:F4}, IsSpeaking={_isSpeaking}, Duration={_recordingDuration:F2}");
}
}
lock (_audioLock)
{
if (num2 > 0.001f)
{
_isSpeaking = true;
_silenceTimer = 0f;
_recordingDuration += (float)buffer.Length / 48000f;
}
else if (_isSpeaking)
{
_silenceTimer += (float)buffer.Length / 48000f;
}
if (_isSpeaking)
{
_currentUtterance.AddRange(array);
}
bool flag = false;
if (_isSpeaking && _silenceTimer >= 0.5f)
{
flag = true;
}
else if (_recordingDuration >= 5f)
{
flag = true;
}
if (flag)
{
SendCurrentUtterance();
_isSpeaking = false;
_silenceTimer = 0f;
_recordingDuration = 0f;
}
}
}
private void SendCurrentUtterance()
{
if (_currentUtterance.Count != 0)
{
float[] samples = _currentUtterance.ToArray();
_currentUtterance.Clear();
super_soundboard.Logger.LogDebug((object)$"SpeechManager: Sending utterance of {samples.Length} samples to Google...");
EnqueueMainThread(delegate
{
((MonoBehaviour)this).StartCoroutine(SendAudioData(samples));
});
}
}
[IteratorStateMachine(typeof(<SendAudioData>d__32))]
private IEnumerator SendAudioData(float[] samples)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <SendAudioData>d__32(0)
{
<>4__this = this,
samples = samples
};
}
private byte[] ConvertToPCM16(float[] samples)
{
byte[] array = new byte[samples.Length * 2];
int num = 0;
foreach (float num2 in samples)
{
short num3 = (short)(Mathf.Clamp(num2, -1f, 1f) * 32767f);
array[num++] = (byte)((uint)num3 & 0xFFu);
array[num++] = (byte)((uint)(num3 >> 8) & 0xFFu);
}
return array;
}
[IteratorStateMachine(typeof(<SendToGoogleSpeechToText>d__34))]
private IEnumerator SendToGoogleSpeechToText(string base64Audio)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <SendToGoogleSpeechToText>d__34(0)
{
<>4__this = this,
base64Audio = base64Audio
};
}
private void ParseGoogleResponse(string json)
{
try
{
if (string.IsNullOrEmpty(json))
{
super_soundboard.Logger.LogWarning((object)"SpeechManager: Empty response from Google.");
return;
}
MatchCollection matchCollection = Regex.Matches(json, "\"transcript\"\\s*:\\s*\"([^\"]+)\"");
if (matchCollection.Count > 0)
{
foreach (Match item in matchCollection)
{
if (item.Success && item.Groups.Count > 1)
{
string value = item.Groups[1].Value;
super_soundboard.Logger.LogInfo((object)("Google Recognized: " + value));
CheckKeywords(value);
}
}
return;
}
if (json.Contains("\"results\""))
{
super_soundboard.Logger.LogWarning((object)("SpeechManager: No transcripts found in JSON via Regex. (Raw: " + json + ")"));
}
else
{
super_soundboard.Logger.LogDebug((object)("SpeechManager: No results parsed from JSON (likely silence). (Raw: " + json + ")"));
}
}
catch (Exception ex)
{
super_soundboard.Logger.LogError((object)("Error parsing Google response: " + ex.Message));
}
}
private void CheckKeywords(string text)
{
if (_config == null || _config.mappings == null)
{
super_soundboard.Logger.LogWarning((object)"CheckKeywords: Config is null or has no mappings. Cannot check keywords.");
return;
}
Mapping[] mappings = _config.mappings;
foreach (Mapping mapping in mappings)
{
if (mapping.keywords == null)
{
continue;
}
string[] keywords = mapping.keywords;
foreach (string text2 in keywords)
{
if (text.Contains(text2))
{
super_soundboard.Logger.LogInfo((object)("Keyword matched: '" + text2 + "' -> Playing " + mapping.file));
if (mapping.file != null)
{
PlaySound(mapping.file, mapping.volume);
return;
}
}
}
}
}
private void EnqueueMainThread(Action action)
{
lock (_mainThreadActions)
{
_mainThreadActions.Enqueue(action);
}
}
private void Update()
{
lock (_mainThreadActions)
{
while (_mainThreadActions.Count > 0)
{
_mainThreadActions.Dequeue()();
}
}
}
public void PlayRandomSound()
{
if (_cachedSoundList != null && _cachedSoundList.Length != 0)
{
string filename = _cachedSoundList[Random.Range(0, _cachedSoundList.Length)];
PlaySound(filename, 100f);
}
else
{
super_soundboard.Logger.LogWarning((object)"No sounds available to play randomly.");
}
}
private void PlaySound(string filename, float volumePercent)
{
if (_soundCache.TryGetValue(filename, out AudioClip value))
{
super_soundboard.Logger.LogInfo((object)$"Playing cached sound: {filename} (Vol: {volumePercent}%)");
float num = volumePercent / 100f * super_soundboard.LocalVolume * super_soundboard.MasterVolume;
float volumeScale = volumePercent / 100f * super_soundboard.RemoteVolume * super_soundboard.MasterVolume;
if ((Object)(object)super_soundboard.Instance != (Object)null)
{
AudioSource component = ((Component)super_soundboard.Instance).GetComponent<AudioSource>();
if ((Object)(object)component != (Object)null)
{
component.PlayOneShot(value, num);
}
}
((MonoBehaviour)this).StartCoroutine(PushToLoopback(value, volumeScale));
}
else
{
((MonoBehaviour)this).StartCoroutine(DownloadAndCacheAndPlay(filename, volumePercent));
}
}
[IteratorStateMachine(typeof(<DownloadAndCacheAndPlay>d__41))]
private IEnumerator DownloadAndCacheAndPlay(string filename, float volumePercent)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <DownloadAndCacheAndPlay>d__41(0)
{
<>4__this = this,
filename = filename,
volumePercent = volumePercent
};
}
[IteratorStateMachine(typeof(<PushToLoopback>d__42))]
private IEnumerator PushToLoopback(AudioClip clip, float volumeScale)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <PushToLoopback>d__42(0)
{
<>4__this = this,
clip = clip,
volumeScale = volumeScale
};
}
private void OnDestroy()
{
}
}
[Serializable]
public class ConfigResponse
{
public bool success;
public ConfigData? data;
}
[Serializable]
public class ConfigData
{
public Mapping[]? mappings;
public int cooldownMs;
public string? lang;
public int wsPort;
}
[Serializable]
public class Mapping
{
public string[]? keywords;
public string? file;
public float volume;
}
[Serializable]
public class SoundListResponse
{
public bool success;
public string[]? data;
}
[BepInPlugin("sasnews.super_soundboard", "super_soundboard", "1.0")]
public class super_soundboard : BaseUnityPlugin
{
private AudioSource? _source;
private static ConfigEntry<bool>? _speechRecognitionEnabled;
private static ConfigEntry<float>? _localVolume;
private static ConfigEntry<float>? _remoteVolume;
private static ConfigEntry<string>? _apiBaseUrl;
private static ConfigEntry<string>? _googleApiKey;
private static ConfigEntry<string>? _googleSpeechLanguage;
private static ConfigEntry<KeyCode>? _debugHotkey;
private static ConfigEntry<bool>? _autoStartMicrophone;
private static ConfigEntry<bool>? _autoSwitchMicrophone;
private SoundboardMixer? _soundboardMixer;
private PlayerVoiceChat? _vc;
private PlayerVoiceChat? _lastVc;
private int _initRetryCount = 0;
private const int MAX_INIT_RETRIES = 10;
private float _nextRetryTime = 0f;
internal static super_soundboard Instance { get; private set; }
private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger;
internal static ManualLogSource Logger => Instance._logger;
internal static bool SpeechRecognitionEnabled => _speechRecognitionEnabled?.Value ?? true;
internal static float LocalVolume => _localVolume?.Value ?? 1f;
internal static float RemoteVolume => _remoteVolume?.Value ?? 1f;
internal static string ApiBaseUrl
{
get
{
string text = _apiBaseUrl?.Value ?? "http://localhost:3211";
return text.TrimEnd('/');
}
}
internal static string GoogleApiKey => _googleApiKey?.Value ?? "AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw";
internal static string GoogleSpeechLanguage => _googleSpeechLanguage?.Value ?? "ja-JP";
internal static bool AutoStartMicrophone => _autoStartMicrophone?.Value ?? false;
internal static bool AutoSwitchMicrophone => _autoSwitchMicrophone?.Value ?? false;
internal static float MasterVolume => 0.5f;
internal static SoundboardLoopback? LoopbackInstance { get; private set; }
internal static SpeechManager? SpeechManagerInstance { get; private set; }
private void Awake()
{
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Expected O, but got Unknown
//IL_00db: Unknown result type (might be due to invalid IL or missing references)
//IL_00e5: Expected O, but got Unknown
Instance = this;
_googleApiKey = ((BaseUnityPlugin)this).Config.Bind<string>("SpeechRecognition", "GoogleApiKey", "AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw", "API Key for Google Cloud Speech-to-Text. If empty, speech recognition may not work.");
_googleSpeechLanguage = ((BaseUnityPlugin)this).Config.Bind<string>("SpeechRecognition", "GoogleSpeechLanguage", "ja-JP", "Language code for speech recognition (e.g. ja-JP, en-US)");
_speechRecognitionEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("SpeechRecognition", "Enabled", true, "Enable/disable automatic speech recognition");
_localVolume = ((BaseUnityPlugin)this).Config.Bind<float>("SpeechRecognition", "LocalVolume", 1f, new ConfigDescription("Volume for sounds you hear locally (0.0 = mute, 1.0 = normal, 2.0 = double)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
_remoteVolume = ((BaseUnityPlugin)this).Config.Bind<float>("SpeechRecognition", "RemoteVolume", 1f, new ConfigDescription("Volume for sounds sent to voice chat (0.0 = mute, 1.0 = normal, 2.0 = double)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
_apiBaseUrl = ((BaseUnityPlugin)this).Config.Bind<string>("SpeechRecognition", "ApiBaseUrl", "http://localhost:3211", "Base URL for the soundboard API server");
_apiBaseUrl.SettingChanged += ApiBaseUrlSettingChanged;
_debugHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keybinds", "DebugPlayRandomSound", (KeyCode)290, "Key to play a random sound (selectable). Use any Unity KeyCode value.");
_autoStartMicrophone = ((BaseUnityPlugin)this).Config.Bind<bool>("Microphone", "AutoStart", true, "Automatically start a microphone if the Recorder does not provide an input (set true to allow auto-start). Default: true");
_autoSwitchMicrophone = ((BaseUnityPlugin)this).Config.Bind<bool>("Microphone", "AutoSwitch", false, "Automatically switch microphone device if the current device produces no samples. Default: false");
_autoStartMicrophone.SettingChanged += AutoStartSettingChanged;
_autoSwitchMicrophone.SettingChanged += AutoSwitchSettingChanged;
SpeechManagerInstance = ((Component)this).gameObject.AddComponent<SpeechManager>();
SpeechManagerInstance.Initialize();
_source = ((Component)this).gameObject.AddComponent<AudioSource>();
_source.playOnAwake = false;
_source.loop = false;
_source.spatialBlend = 0f;
_source.dopplerLevel = 0f;
_source.priority = 128;
LoopbackInstance = new SoundboardLoopback();
Logger.LogDebug((object)"super_soundboard: Awake finished.");
}
private void Update()
{
//IL_014b: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)_vc == (Object)null)
{
_vc = Object.FindObjectOfType<PlayerVoiceChat>();
if ((Object)(object)_vc != (Object)null)
{
Logger.LogDebug((object)"super_soundboard: Found PlayerVoiceChat. Initializing soundboard recorder bridge...");
_lastVc = _vc;
InitSoundboard();
}
}
else if ((Object)(object)_lastVc != (Object)(object)_vc)
{
Logger.LogDebug((object)"super_soundboard: PlayerVoiceChat instance changed (scene reload detected). Resetting...");
_soundboardMixer?.Dispose();
_soundboardMixer = null;
_lastVc = _vc;
_initRetryCount = 0;
_nextRetryTime = 0f;
InitSoundboard();
}
else if (_soundboardMixer == null && _initRetryCount < 10 && Time.time >= _nextRetryTime)
{
Logger.LogDebug((object)$"super_soundboard: Retrying initialization (attempt {_initRetryCount + 1}/{10})...");
InitSoundboard();
_initRetryCount++;
_nextRetryTime = Time.time + 1f;
}
ConfigEntry<KeyCode>? debugHotkey = _debugHotkey;
if (Input.GetKeyDown((KeyCode)((debugHotkey == null) ? 290 : ((int)debugHotkey.Value))))
{
SpeechManagerInstance?.PlayRandomSound();
}
_soundboardMixer?.Update();
}
private void InitSoundboard()
{
if ((Object)(object)_vc == (Object)null)
{
return;
}
try
{
Recorder val = _vc.recorder;
if ((Object)(object)val == (Object)null)
{
Logger.LogDebug((object)"super_soundboard: PlayerVoiceChat.recorder is null — attempting fallback search for Recorder in scene...");
Recorder val2 = Object.FindObjectOfType<Recorder>();
if (!((Object)(object)val2 != (Object)null))
{
if (_initRetryCount == 0 || _initRetryCount >= 9)
{
Logger.LogWarning((object)"========================================");
Logger.LogWarning((object)"super_soundboard: Recorder not found on PlayerVoiceChat.");
if (_initRetryCount >= 9)
{
Logger.LogWarning((object)"Voice chat integration is not available.");
Logger.LogWarning((object)"Sounds will only play locally (you can hear them).");
Logger.LogWarning((object)"Others in voice chat will NOT hear the soundboard.");
}
else
{
Logger.LogWarning((object)"Will retry in a moment...");
}
Logger.LogWarning((object)"========================================");
}
return;
}
Logger.LogDebug((object)("super_soundboard: Found Recorder on GameObject '" + ((Object)((Component)val2).gameObject).name + "' (type " + ((object)val2).GetType().FullName + "). Will use it."));
val = val2;
try
{
PropertyInfo property = ((object)_vc).GetType().GetProperty("recorder", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null && property.CanWrite)
{
property.SetValue(_vc, val);
Logger.LogDebug((object)"super_soundboard: Assigned found Recorder to PlayerVoiceChat.recorder property.");
}
else
{
FieldInfo field = ((object)_vc).GetType().GetField("recorder", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
field.SetValue(_vc, val);
Logger.LogDebug((object)"super_soundboard: Assigned found Recorder to PlayerVoiceChat.recorder field.");
}
else
{
Logger.LogDebug((object)"super_soundboard: Could not assign Recorder to PlayerVoiceChat (no accessible property/field).");
}
}
}
catch (Exception ex)
{
Logger.LogDebug((object)("super_soundboard: Exception while assigning Recorder to PlayerVoiceChat: " + ex.Message));
}
}
try
{
PropertyInfo property2 = ((object)val).GetType().GetProperty("enabled");
if (property2 != null && property2.PropertyType == typeof(bool))
{
if (!(bool)property2.GetValue(val))
{
property2.SetValue(val, true);
Logger.LogDebug((object)"super_soundboard: Enabled the Recorder component.");
}
}
else
{
Behaviour val3 = (Behaviour)(object)val;
if ((Object)(object)val3 != (Object)null && !val3.enabled)
{
val3.enabled = true;
Logger.LogDebug((object)"super_soundboard: Enabled the Recorder component (cast to Behaviour).");
}
}
}
catch (Exception ex2)
{
Logger.LogDebug((object)("super_soundboard: Could not enable Recorder: " + ex2.Message));
}
try
{
Component val4 = (Component)(object)val;
string text = (((Object)(object)val4 != (Object)null) ? ((Object)val4.gameObject).name : "(unknown)");
Logger.LogDebug((object)("super_soundboard: Using Recorder type " + ((object)val).GetType().FullName + " on GameObject '" + text + "'"));
Logger.LogDebug((object)string.Format("super_soundboard: Recorder has InputFactory? {0}", ((object)val).GetType().GetProperty("InputFactory") != null));
Logger.LogDebug((object)string.Format("super_soundboard: Recorder has SourceType? {0}", ((object)val).GetType().GetProperty("SourceType") != null));
}
catch (Exception ex3)
{
Logger.LogDebug((object)("super_soundboard: Error inspecting Recorder: " + ex3.Message));
}
try
{
string[] devices = Microphone.devices;
if (devices == null || devices.Length == 0)
{
Logger.LogWarning((object)"super_soundboard: No microphone devices found on this machine (Microphone.devices is empty).");
}
else
{
Logger.LogDebug((object)string.Format("super_soundboard: Microphone devices ({0}): {1}", devices.Length, string.Join(", ", devices)));
}
}
catch (Exception ex4)
{
Logger.LogDebug((object)("super_soundboard: Could not query Microphone.devices: " + ex4.Message));
}
if (_soundboardMixer == null)
{
object obj = null;
try
{
PropertyInfo property3 = ((object)val).GetType().GetProperty("MicInput", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property3 != null)
{
obj = property3.GetValue(val);
Logger.LogDebug((object)$"super_soundboard: Found MicInput property: {obj != null}");
}
}
catch (Exception ex5)
{
Logger.LogDebug((object)("super_soundboard: Could not access MicInput: " + ex5.Message));
}
_soundboardMixer = new SoundboardMixer(_vc, obj);
}
try
{
PropertyInfo property4 = ((object)val).GetType().GetProperty("TransmitEnabled");
if (property4 != null && property4.PropertyType == typeof(bool))
{
try
{
bool flag = (bool)property4.GetValue(val);
Logger.LogDebug((object)$"super_soundboard: TransmitEnabled property exists, current value: {flag}");
}
catch (Exception ex6)
{
Logger.LogDebug((object)("super_soundboard: Could not read TransmitEnabled value: " + ex6.Message));
}
property4.SetValue(val, true);
Logger.LogDebug((object)"super_soundboard: TransmitEnabled set to true");
}
else
{
Logger.LogDebug((object)"super_soundboard: TransmitEnabled property not found on Recorder.");
}
PropertyInfo property5 = ((object)val).GetType().GetProperty("RecordingEnabled");
if (property5 != null && property5.PropertyType == typeof(bool))
{
try
{
bool flag2 = (bool)property5.GetValue(val);
Logger.LogDebug((object)$"super_soundboard: RecordingEnabled property exists, current value: {flag2}");
}
catch (Exception ex7)
{
Logger.LogDebug((object)("super_soundboard: Could not read RecordingEnabled value: " + ex7.Message));
}
property5.SetValue(val, true);
Logger.LogDebug((object)"super_soundboard: RecordingEnabled set to true");
}
else
{
Logger.LogDebug((object)"super_soundboard: RecordingEnabled property not found on Recorder.");
}
}
catch (Exception arg)
{
Logger.LogWarning((object)$"super_soundboard: Failed to enable recording/transmit: {arg}");
}
try
{
PropertyInfo property6 = ((object)val).GetType().GetProperty("InputFactory");
if (property6 != null)
{
Func<IAudioReader<float>> value = CreateMixerFactory;
property6.SetValue(val, value);
Logger.LogDebug((object)"super_soundboard: InputFactory set.");
}
else
{
Logger.LogError((object)("super_soundboard: InputFactory property not found on Recorder (recorder type: " + ((object)val).GetType().FullName + "). This may indicate a Photon Voice version mismatch."));
}
}
catch (Exception arg2)
{
Logger.LogError((object)$"super_soundboard: Failed to set InputFactory: {arg2}");
}
try
{
PropertyInfo property7 = ((object)val).GetType().GetProperty("SourceType");
if (property7 != null && property7.PropertyType.IsEnum)
{
object obj2 = Enum.ToObject(property7.PropertyType, 2);
property7.SetValue(val, obj2);
Logger.LogDebug((object)$"super_soundboard: SourceType set to {obj2} (Factory)");
}
}
catch (Exception arg3)
{
Logger.LogWarning((object)$"super_soundboard: Failed to set SourceType: {arg3}");
}
try
{
Component[] array = Object.FindObjectsOfType<Component>();
Component[] array2 = array;
foreach (Component val5 in array2)
{
Type type = ((object)val5).GetType();
if (!(type.Name == "PhotonVoiceView") && (type.FullName == null || !type.FullName.Contains("Photon.Voice")))
{
continue;
}
try
{
string text2 = "(unknown)";
if (val5 != null)
{
Component val6 = val5;
if (true)
{
text2 = ((Object)val6.gameObject).name;
}
}
Logger.LogDebug((object)("super_soundboard: Found PhotonVoiceView on '" + text2 + "' (type " + type.FullName + ")"));
object obj3 = null;
PropertyInfo property8 = type.GetProperty("RecorderInUse", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property8 != null)
{
obj3 = property8.GetValue(val5);
if (obj3 == null)
{
property8.SetValue(val5, val);
obj3 = val;
Logger.LogDebug((object)"super_soundboard: Assigned Recorder to PhotonVoiceView.RecorderInUse");
}
}
else
{
FieldInfo field2 = type.GetField("recorderInUse", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field2 != null)
{
obj3 = field2.GetValue(val5);
if (obj3 == null)
{
field2.SetValue(val5, val);
obj3 = val;
Logger.LogDebug((object)"super_soundboard: Assigned Recorder to PhotonVoiceView.recorderInUse");
}
}
else
{
Logger.LogDebug((object)"super_soundboard: PhotonVoiceView has no accessible RecorderInUse property/field.");
}
}
if (obj3 == null)
{
continue;
}
try
{
PropertyInfo property9 = obj3.GetType().GetProperty("TransmitEnabled");
if (property9 != null && property9.PropertyType == typeof(bool))
{
try
{
bool flag3 = (bool)property9.GetValue(obj3);
Logger.LogDebug((object)$"super_soundboard: PhotonVoiceView recorder TransmitEnabled current: {flag3}");
}
catch
{
}
property9.SetValue(obj3, true);
Logger.LogDebug((object)"super_soundboard: Set PhotonVoiceView recorder TransmitEnabled = true");
}
}
catch (Exception ex8)
{
Logger.LogDebug((object)("super_soundboard: Could not set PhotonVoiceView recorder TransmitEnabled: " + ex8.Message));
}
}
catch (Exception ex9)
{
Logger.LogDebug((object)("super_soundboard: Error handling PhotonVoiceView instance: " + ex9.Message));
}
}
}
catch (Exception ex10)
{
Logger.LogDebug((object)("super_soundboard: Error searching for PhotonVoiceView: " + ex10.Message));
}
try
{
MethodInfo method = ((object)val).GetType().GetMethod("RestartRecording");
if (method != null)
{
method.Invoke(val, null);
}
else
{
((object)val).GetType().GetMethod("StartRecording")?.Invoke(val, null);
}
}
catch (Exception arg4)
{
Logger.LogWarning((object)$"super_soundboard: Failed to restart recording: {arg4}");
}
Logger.LogDebug((object)"super_soundboard: Recorder hooked with SoundboardMixer (Factory mode).");
_initRetryCount = 10;
}
catch (Exception arg5)
{
Logger.LogError((object)$"super_soundboard: InitSoundboard failed: {arg5}");
}
}
private IAudioReader<float> CreateMixerFactory()
{
if (_soundboardMixer == null || _soundboardMixer.IsDisposed)
{
SoundboardMixer? soundboardMixer = _soundboardMixer;
if (soundboardMixer != null && soundboardMixer.IsDisposed)
{
Logger.LogDebug((object)"super_soundboard: Previous SoundboardMixer was disposed, creating new instance in CreateMixerFactory.");
}
else
{
Logger.LogDebug((object)"super_soundboard: Creating new SoundboardMixer instance in CreateMixerFactory.");
}
object micInput = null;
try
{
Recorder val = _vc?.recorder;
if ((Object)(object)val != (Object)null)
{
PropertyInfo property = ((object)val).GetType().GetProperty("MicInput", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property != null)
{
micInput = property.GetValue(val);
}
}
}
catch (Exception ex)
{
Logger.LogDebug((object)("super_soundboard: Could not access MicInput in CreateMixerFactory: " + ex.Message));
}
_soundboardMixer = new SoundboardMixer(_vc, micInput);
}
return (IAudioReader<float>)(object)_soundboardMixer;
}
private void OnDestroy()
{
_soundboardMixer?.Dispose();
try
{
if (_autoStartMicrophone != null)
{
_autoStartMicrophone.SettingChanged -= AutoStartSettingChanged;
}
if (_autoSwitchMicrophone != null)
{
_autoSwitchMicrophone.SettingChanged -= AutoSwitchSettingChanged;
}
if (_apiBaseUrl != null)
{
_apiBaseUrl.SettingChanged -= ApiBaseUrlSettingChanged;
}
}
catch
{
}
}
private void OnAutoStartChanged()
{
Logger.LogInfo((object)$"super_soundboard: AutoStartMicrophone changed => {AutoStartMicrophone}");
if (_soundboardMixer != null)
{
if (AutoStartMicrophone)
{
_soundboardMixer.EnsureLocalMicStarted();
}
else
{
_soundboardMixer.StopLocalMic();
}
}
}
private void OnAutoSwitchChanged()
{
Logger.LogInfo((object)$"super_soundboard: AutoSwitchMicrophone changed => {AutoSwitchMicrophone}");
}
private void AutoStartSettingChanged(object? sender, EventArgs e)
{
OnAutoStartChanged();
}
private void AutoSwitchSettingChanged(object? sender, EventArgs e)
{
OnAutoSwitchChanged();
}
private void ApiBaseUrlSettingChanged(object? sender, EventArgs e)
{
OnApiBaseUrlChanged();
}
private void OnApiBaseUrlChanged()
{
string apiBaseUrl = ApiBaseUrl;
try
{
if (_apiBaseUrl != null && _apiBaseUrl.Value != apiBaseUrl)
{
_apiBaseUrl.Value = apiBaseUrl;
}
}
catch (Exception ex)
{
Logger.LogWarning((object)("super_soundboard: Could not persist normalized ApiBaseUrl: " + ex.Message));
}
Logger.LogInfo((object)("super_soundboard: ApiBaseUrl changed => " + apiBaseUrl));
try
{
SpeechManagerInstance?.OnApiBaseUrlChanged();
}
catch (Exception ex2)
{
Logger.LogWarning((object)("super_soundboard: Error notifying SpeechManager of ApiBaseUrl change: " + ex2.Message));
}
}
}
}