Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Super Soundboard v0.1.4
super_soundboard.dll
Decompiled 6 months agousing 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)); } } } }