Decompiled source of DEPO VoiceChat v1.0.4
DEPOVoiceChat.dll
Decompiled 5 days agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using CSCore; using CSCore.CoreAudioAPI; using CSCore.SoundIn; using CSCore.SoundOut; using CSCore.Streams; using Steamworks; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("DEPOVoiceChat")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("DEPOVoiceChat")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("04bd0ec2-8038-494e-a523-c810f2f5b373")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace DEPOVoiceChat; public static class Localization { public enum Language { English, Russian, Spanish, French, Chinese, Japanese } private static readonly Dictionary<string, Dictionary<Language, string>> texts = new Dictionary<string, Dictionary<Language, string>> { { "connected_clients", new Dictionary<Language, string> { { Language.English, "Connected clients:" }, { Language.Russian, "Подключено клиентов:" }, { Language.Spanish, "Clientes conectados:" }, { Language.Chinese, "已连接的客户端:" }, { Language.Japanese, "接続中のクライアント:" }, { Language.French, "Clients connectés :" } } }, { "volume_players", new Dictionary<Language, string> { { Language.English, "Volume players" }, { Language.Russian, "Громкость игроков" }, { Language.Spanish, "Volumen de jugadores" }, { Language.Chinese, "玩家音量" }, { Language.Japanese, "プレイヤーの音量" }, { Language.French, "Volume des joueurs" } } }, { "volume_microphone", new Dictionary<Language, string> { { Language.English, "Volume microphone" }, { Language.Russian, "Громкость микрофона" }, { Language.Spanish, "Volumen del micrófono" }, { Language.Chinese, "麦克风音量" }, { Language.Japanese, "マイクの音量" }, { Language.French, "Volume du micro" } } }, { "hear_myself", new Dictionary<Language, string> { { Language.English, "Hear myself" }, { Language.Russian, "Слушать себя" }, { Language.Spanish, "Escucharme a mí mismo" }, { Language.Chinese, "听自己的声音" }, { Language.Japanese, "自分の声を聞く" }, { Language.French, "S'entendre soi-même" } } }, { "select_microphone", new Dictionary<Language, string> { { Language.English, "Select microphone" }, { Language.Russian, "Выбрать микрофон" }, { Language.Spanish, "Seleccionar micrófono" }, { Language.Chinese, "选择麦克风" }, { Language.Japanese, "マイクを選択" }, { Language.French, "Sélectionner le micro" } } }, { "keybinds", new Dictionary<Language, string> { { Language.English, "Keybinds:" }, { Language.Russian, "Назначение клавиш:" }, { Language.Spanish, "Asignación de teclas:" }, { Language.Chinese, "按键绑定:" }, { Language.Japanese, "キー割り当て:" }, { Language.French, "Raccourcis clavier :" } } }, { "open_close_menu", new Dictionary<Language, string> { { Language.English, "Open/Close menu" }, { Language.Russian, "Открыть/Закрыть меню" }, { Language.Spanish, "Abrir/Cerrar menú" }, { Language.Chinese, "打开/关闭菜单" }, { Language.Japanese, "メニューを開閉" }, { Language.French, "Ouvrir/Fermer le menu" } } }, { "push_to_talk", new Dictionary<Language, string> { { Language.English, "Push-to-talk" }, { Language.Russian, "Нажать, чтобы разговаривать" }, { Language.Spanish, "Pulsar para hablar" }, { Language.Chinese, "按键发言" }, { Language.Japanese, "プッシュ・トゥ・トーク" }, { Language.French, "Appuyer pour parler" } } }, { "close", new Dictionary<Language, string> { { Language.English, "Close" }, { Language.Russian, "Закрыть" }, { Language.Spanish, "Cerrar" }, { Language.Chinese, "关闭" }, { Language.Japanese, "閉じる" }, { Language.French, "Fermer" } } }, { "microphone_mode", new Dictionary<Language, string> { { Language.English, "Microphone mode:" }, { Language.Russian, "Режим микрофона:" }, { Language.Spanish, "Modo micrófono:" }, { Language.Chinese, "麦克风模式:" }, { Language.Japanese, "マイクモード:" }, { Language.French, "Mode micro :" } } }, { "threshold", new Dictionary<Language, string> { { Language.English, "Voice threshold" }, { Language.Russian, "Порог громкости" }, { Language.Spanish, "Umbral de voz" }, { Language.Chinese, "语音激活阈值" }, { Language.Japanese, "音声認識しきい値" }, { Language.French, "Seuil de voix" } } }, { "voicechat_menu", new Dictionary<Language, string> { { Language.English, "Voicechat Menu" }, { Language.Russian, "Меню голосового чата" }, { Language.Spanish, "Menú de Voicechat" }, { Language.Chinese, "语音聊天菜单" }, { Language.Japanese, "ボイスチャットメニュー" }, { Language.French, "Menu de chat vocal" } } }, { "voice_activation", new Dictionary<Language, string> { { Language.English, "Voice Activation" }, { Language.Russian, "Активация голосом" }, { Language.Spanish, "Activación por voz" }, { Language.Chinese, "语音激活" }, { Language.Japanese, "音声アクティベーション" }, { Language.French, "Activation vocale" } } }, { "buffer_size", new Dictionary<Language, string> { { Language.English, "Buffer size (ms)" }, { Language.Russian, "Размер буфера (мс)" }, { Language.Spanish, "Tamaño del búfer (ms)" }, { Language.Chinese, "缓冲区大小(毫秒)" }, { Language.Japanese, "バッファサイズ(ms)" }, { Language.French, "Taille du tampon (ms)" } } }, { "buffer_current", new Dictionary<Language, string> { { Language.English, "Current buffer:" }, { Language.Russian, "Текущий буфер:" }, { Language.Spanish, "Búfer actual:" }, { Language.Chinese, "当前缓冲:" }, { Language.Japanese, "現在のバッファ:" }, { Language.French, "Tampon actuel :" } } }, { "language", new Dictionary<Language, string> { { Language.English, "Language" }, { Language.Russian, "Язык" }, { Language.Spanish, "Idioma" }, { Language.Chinese, "语言" }, { Language.Japanese, "言語" }, { Language.French, "Langue" } } }, { "speaking", new Dictionary<Language, string> { { Language.English, "Speaking..." }, { Language.Russian, "Говорит..." }, { Language.Spanish, "Hablando..." }, { Language.Chinese, "正在讲话..." }, { Language.Japanese, "話しています..." }, { Language.French, "Parle..." } } } }; public static Language CurrentLanguage { get; private set; } = Language.English; public static string T(string key) { if (texts.TryGetValue(key, out var value) && value.TryGetValue(CurrentLanguage, out var value2)) { return value2; } return key; } public static void SetLanguage(string lang) { switch (lang) { case "English": CurrentLanguage = Language.English; break; case "Russian (Русский)": CurrentLanguage = Language.Russian; break; case "Spanish (Español)": CurrentLanguage = Language.Spanish; break; case "French (Français)": CurrentLanguage = Language.French; break; case "Chinese (中文)": CurrentLanguage = Language.Chinese; break; case "Japanese (日本語)": CurrentLanguage = Language.Japanese; break; default: CurrentLanguage = Language.English; break; } } } [BepInPlugin("ru.makorddev.depovoicechat", "Voicechat", "1.0.0")] public class Main : BaseUnityPlugin { public static IPEndPoint ServerEP; private readonly CancellationTokenSource cts = new CancellationTokenSource(); private UI ui; private async void Awake() { Debug.Log((object)"[VoiceChat] VoiceChat loaded."); try { IPAddress[] addr = null; int retries = 3; while (retries-- > 0) { try { addr = await Dns.GetHostAddressesAsync("busiatep.ru"); if (addr.Length != 0) { break; } } catch { await Task.Delay(1000); } } if (addr == null || addr.Length == 0) { Debug.LogError((object)"[VoiceChat] DNS failed after retries."); ServerEP = new IPEndPoint(IPAddress.Loopback, 6001); } else { ServerEP = new IPEndPoint(addr[0], 6001); } } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] DNS exception: " + ex)); ServerEP = new IPEndPoint(IPAddress.Loopback, 6001); } try { SettingsManager.Load(); } catch { Debug.LogError((object)"[VoiceChat] Failed to load settings."); } try { VoiceManager.InitDevices(); } catch { Debug.LogError((object)"[VoiceChat] Failed to init devices."); } InitializeSteam(); SceneManager.activeSceneChanged += OnSceneChanged; try { Localization.SetLanguage(SettingsManager.CurrentSettings.language); } catch { Debug.LogWarning((object)"[VoiceChat] Failed to set language."); } GameObject uiObj = new GameObject("VoiceChatUI"); ui = uiObj.AddComponent<UI>(); Object.DontDestroyOnLoad((Object)(object)uiObj); } private async void Start() { VoiceManager.SetInstanceId(Guid.NewGuid().ToString()); bool connected = false; int attempts = 3; while (!connected && attempts-- > 0) { try { connected = await NetworkManager.Connect(); } catch (Exception ex) { Debug.LogWarning((object)("[VoiceChat] Connect attempt failed: " + ex)); } if (!connected) { await Task.Delay(2000); } } if (!connected) { Debug.LogError((object)"[VoiceChat] Failed to connect to server after retries."); return; } NetworkManager.OnReconnected += delegate { try { VoiceManager.RestartUdp(); } catch (Exception ex4) { Debug.LogWarning((object)("[VoiceChat] Failed to restart UDP: " + ex4)); } }; try { if (VoiceManager.MicDevices.Length != 0) { int idx = Mathf.Clamp(SettingsManager.CurrentSettings.selectedMicIndex, 0, VoiceManager.MicDevices.Length - 1); VoiceManager.StartCapture(idx); } } catch (Exception ex2) { Debug.LogError((object)("[VoiceChat] Failed to start mic capture: " + ex2)); } try { await VoiceManager.StartReceiving(cts.Token); } catch (Exception ex3) { Debug.LogError((object)("[VoiceChat] Failed to start receiving voice: " + ex3)); } } private void OnDestroy() { try { cts?.Cancel(); } catch { } try { NetworkManager.Disconnect(); } catch { } try { VoiceManager.StopCapture(); } catch { } try { VoiceManager.StopReceiving(); } catch { } try { VoiceManager.StopVoiceStream(); } catch { } GameObject val = GameObject.Find("Dispatcher"); if ((Object)(object)val != (Object)null) { try { Object.Destroy((Object)(object)val); } catch { } } } private void Update() { try { SpeakingIndicator.UpdateSpeakingIndicators(); ui?.UpdateUI(); } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] Update error: " + ex)); } } private void OnGUI() { ui?.DrawUI(); } private void OnSceneChanged(Scene oldScene, Scene newScene) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown Task.Run(async delegate { try { string msg = $"INFO|{SteamUser.GetSteamID().m_SteamID}|{SteamFriends.GetPersonaName()}|{((Scene)(ref newScene)).name}"; await NetworkManager.SendMessage(msg); } catch { Debug.LogWarning((object)"[VoiceChat] Failed to send scene info."); } }); if ((Object)(object)GameObject.Find("Dispatcher") == (Object)null) { GameObject val = new GameObject("Dispatcher"); try { val.AddComponent<UnityMainThreadDispatcher>(); } catch { } Object.DontDestroyOnLoad((Object)(object)val); } } private void InitializeSteam() { try { if (!SteamAPI.Init()) { Debug.LogError((object)"[VoiceChat] SteamAPI init failed!"); } } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] SteamAPI exception: " + ex)); } } } public static class NetworkManager { public enum ConnectionState { Disconnected, Connecting, Connected } private static TcpClient tcpClient; private static NetworkStream stream; private static CancellationTokenSource cts; private static CancellationTokenSource monitorCts; private static readonly string serverIp = "busiatep.ru"; private static readonly int serverPort = 6000; private static readonly int heartbeatInterval = 5000; private static string lastMessage = ""; private static readonly object msgLock = new object(); private static bool reconnecting = false; public static ConnectionState State { get; private set; } = ConnectionState.Disconnected; public static Dictionary<string, string> ClientList { get; private set; } = new Dictionary<string, string>(); public static TcpClient Client => tcpClient; public static NetworkStream Stream => stream; public static event Action OnReconnected; public static async Task<bool> Connect() { if (State == ConnectionState.Connecting || State == ConnectionState.Connected) { return State == ConnectionState.Connected; } State = ConnectionState.Connecting; Cleanup(); cts = new CancellationTokenSource(); CancellationToken token = cts.Token; try { tcpClient = new TcpClient(); await tcpClient.ConnectAsync(serverIp, serverPort); stream = tcpClient.GetStream(); Debug.Log((object)"[VoiceChat] TCP connected."); object arg = SteamUser.GetSteamID().m_SteamID; string personaName = SteamFriends.GetPersonaName(); Scene activeScene = SceneManager.GetActiveScene(); string info = $"INFO|{arg}|{personaName}|{((Scene)(ref activeScene)).name}"; await SendMessage(info); if (!(await WaitForResponse("CLIENTS|", 2000))) { Debug.LogWarning((object)"[VoiceChat] CLIENTS response timeout."); } Task.Run(() => HeartbeatLoop(token), token); Task.Run(() => ReceiveLoop(token), token); State = ConnectionState.Connected; StartMonitor(); return true; } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] Connect error: " + ex)); Disconnect(); StartReconnectLoop(); return false; } } private static void StartMonitor() { monitorCts?.Cancel(); monitorCts = new CancellationTokenSource(); CancellationToken token = monitorCts.Token; Task.Run(async delegate { while (!token.IsCancellationRequested) { try { await Task.Delay(3000, token); if (State != ConnectionState.Connected && !reconnecting) { Debug.LogWarning((object)"[VoiceChat] Connection lost. Trying to reconnect..."); StartReconnectLoop(); } } catch (TaskCanceledException) { break; } catch (Exception ex2) { Debug.LogError((object)("[VoiceChat] Monitor error: " + ex2)); } } }, token); } private static void StartReconnectLoop() { if (reconnecting) { return; } reconnecting = true; Task.Run(async delegate { int delay = 3000; while (State != ConnectionState.Connected) { Debug.Log((object)$"[VoiceChat] Reconnecting in {delay / 1000}s..."); await Task.Delay(delay); if (await Connect()) { reconnecting = false; NetworkManager.OnReconnected?.Invoke(); return; } delay = Math.Min(delay + 3000, 10000); } reconnecting = false; }); } public static async Task SendMessage(string msg) { if (tcpClient != null && tcpClient.Connected) { byte[] data = Encoding.UTF8.GetBytes(msg); try { await stream.WriteAsync(data, 0, data.Length); } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] Failed to send TCP message: " + ex)); Disconnect(); StartReconnectLoop(); } } else if (!reconnecting) { StartReconnectLoop(); } } private static async Task HeartbeatLoop(CancellationToken token) { try { while (!token.IsCancellationRequested) { await Task.Delay(heartbeatInterval, token).ContinueWith(delegate { }); if (token.IsCancellationRequested) { break; } await SendMessage("HEARTBEAT"); } } catch (TaskCanceledException) { } catch (Exception ex2) { Debug.LogError((object)("[VoiceChat] HeartbeatLoop error: " + ex2)); } } private static async Task ReceiveLoop(CancellationToken token) { byte[] buffer = new byte[4096]; try { while (!token.IsCancellationRequested) { int read = await stream.ReadAsync(buffer, 0, buffer.Length, token); if (read == 0) { break; } string message = Encoding.UTF8.GetString(buffer, 0, read); HandleServerMessage(message); } } catch (TaskCanceledException) { } catch (Exception ex2) { Debug.LogWarning((object)("[VoiceChat] ReceiveLoop error: " + ex2)); } finally { Debug.LogWarning((object)"[VoiceChat] TCP connection lost. Disconnecting..."); Disconnect(); StartReconnectLoop(); } } private static void HandleServerMessage(string message) { //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) lock (msgLock) { lastMessage = message; } try { if (message.StartsWith("CLIENTS")) { ClientList.Clear(); string[] array = message.Split(new char[1] { '|' }); if (array.Length <= 1) { return; } string[] array2 = array[1].Split(new char[1] { ',' }); foreach (string text in array2) { string[] array3 = text.Split(new char[1] { ':' }); if (array3.Length == 2) { ClientList[array3[0]] = array3[1]; } } } else { if (!message.StartsWith("SPEAKING|")) { return; } string[] array4 = message.Split(new char[1] { '|' }); if (array4.Length == 4) { string text2 = array4[1]; string name = array4[2]; Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name == text2) { VoiceManager.AllowScenePlayback(text2, name); } } } } catch (Exception ex) { Debug.LogWarning((object)("[VoiceChat] HandleServerMessage error: " + ex)); } } public static async Task<bool> WaitForResponse(string expected, int timeoutMs) { int waited = 0; while (waited < timeoutMs) { string msg; lock (msgLock) { msg = lastMessage; } if (msg.Contains(expected)) { return true; } await Task.Delay(10); waited += 10; msg = null; } return false; } public static void Disconnect() { try { State = ConnectionState.Disconnected; cts?.Cancel(); monitorCts?.Cancel(); stream?.Close(); tcpClient?.Close(); tcpClient = null; stream = null; cts = null; monitorCts = null; } catch { } } private static void Cleanup() { try { cts?.Cancel(); monitorCts?.Cancel(); stream?.Close(); tcpClient?.Close(); tcpClient = null; stream = null; cts = null; monitorCts = null; } catch { } } } public enum MicMode { PushToTalk, VoiceActivation } [Serializable] public class VoiceSettings { public float selfVolume = 1f; public float playersVolume = 1f; public bool hearSelf = false; public int selectedMicIndex = 0; public KeyCode menuToggleKey = (KeyCode)113; public KeyCode pushToTalkKey = (KeyCode)114; public MicMode micMode = MicMode.PushToTalk; public float voiceThresholdDb = -5f; public string language = "English"; public int bufferSizeMs = 20; } public static class SettingsManager { private static readonly string settingsPath = Path.Combine(Application.persistentDataPath, "voicechat_settings.json"); public static VoiceSettings CurrentSettings { get; private set; } = new VoiceSettings(); public static void Load() { if (File.Exists(settingsPath)) { try { string text = File.ReadAllText(settingsPath); CurrentSettings = JsonUtility.FromJson<VoiceSettings>(text); } catch { CurrentSettings = new VoiceSettings(); } } Localization.SetLanguage(CurrentSettings.language); } public static void Save() { try { string contents = JsonUtility.ToJson((object)CurrentSettings, true); File.WriteAllText(settingsPath, contents); } catch { } } } public class SpeakingIndicator : MonoBehaviour { public static readonly Dictionary<string, float> speakingPlayers = new Dictionary<string, float>(); public static readonly Dictionary<string, Coroutine> activeCoroutines = new Dictionary<string, Coroutine>(); public static readonly object lockObj = new object(); public static string speakText = "[" + Localization.T("speaking") + "]"; private static string oldSpeakText; private const float silenceTimeout = 1f; public static void UpdateSpeakingIndicators() { float time = Time.time; List<string> list = new List<string>(); if (speakText != "[" + Localization.T("speaking") + "]") { UpdateSpeakText(); } lock (lockObj) { foreach (KeyValuePair<string, float> speakingPlayer in speakingPlayers) { if (time - speakingPlayer.Value > 1f) { list.Add(speakingPlayer.Key); } } foreach (string item in list) { speakingPlayers.Remove(item); } } foreach (string item2 in list) { RemoveIndicator(item2); } } public static void UpdateSpeakText() { oldSpeakText = speakText; speakText = "[" + Localization.T("speaking") + "]"; UnityMainThreadDispatcher.Enqueue(delegate { Text[] array = Object.FindObjectsOfType<Text>(true); Text[] array2 = array; foreach (Text val in array2) { if (!string.IsNullOrEmpty(oldSpeakText) && val.text.Contains(oldSpeakText)) { val.text = val.text.Replace(oldSpeakText, speakText); } } }); } public static void OnPlayerSpeaking(string name) { if (string.IsNullOrEmpty(name)) { Debug.LogWarning((object)"[SpeakingIndicator] OnPlayerSpeaking called with null or empty name"); return; } bool flag = false; lock (lockObj) { speakingPlayers[name] = Time.time; if (!activeCoroutines.ContainsKey(name)) { flag = true; } } if (!flag) { return; } UnityMainThreadDispatcher.Enqueue(delegate { Coroutine value = StartAddIndicatorCoroutine(name); lock (lockObj) { activeCoroutines[name] = value; } }); } private static Coroutine StartAddIndicatorCoroutine(string playerName) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown GameObject val = new GameObject("DispatcherCoroutine_" + playerName); DispatcherCoroutineRunner dispatcherCoroutineRunner = val.AddComponent<DispatcherCoroutineRunner>(); return ((MonoBehaviour)dispatcherCoroutineRunner).StartCoroutine(dispatcherCoroutineRunner.AddIndicatorCoroutine(playerName)); } private static void RemoveIndicator(string playerName) { UnityMainThreadDispatcher.Enqueue(delegate { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown GameObject val = new GameObject("DispatcherRemove_" + playerName); DispatcherCoroutineRunner dispatcherCoroutineRunner = val.AddComponent<DispatcherCoroutineRunner>(); ((MonoBehaviour)dispatcherCoroutineRunner).StartCoroutine(dispatcherCoroutineRunner.RemoveIndicatorCoroutine(playerName)); }); } public static Text FindTextForPlayer(string playerName) { Text[] source = Object.FindObjectsOfType<Text>(true); Text val = ((IEnumerable<Text>)source).FirstOrDefault((Func<Text, bool>)((Text t) => ((Object)t).name.Contains("NameJugador") && t.text.Contains(playerName))); if ((Object)(object)val == (Object)null) { Debug.LogWarning((object)("[SpeakingIndicator] FindTextForPlayer: Could not find Text for '" + playerName + "'")); } return val; } } public class DispatcherCoroutineRunner : MonoBehaviour { [CompilerGenerated] private sealed class <AddIndicatorCoroutine>d__0 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string playerName; public DispatcherCoroutineRunner <>4__this; private Text <playerText>5__1; private Outline <outline>5__2; private bool <stillSpeaking>5__3; private object <>s__4; private bool <>s__5; private object <>s__6; private bool <>s__7; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <AddIndicatorCoroutine>d__0(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <playerText>5__1 = null; <outline>5__2 = null; <>s__4 = null; <>s__6 = null; <>1__state = -2; } private bool MoveNext() { //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <playerText>5__1 = SpeakingIndicator.FindTextForPlayer(playerName); if ((Object)(object)<playerText>5__1 == (Object)null) { return false; } if (!<playerText>5__1.text.Contains(SpeakingIndicator.speakText)) { Text obj = <playerText>5__1; obj.text = obj.text + " " + SpeakingIndicator.speakText; } <outline>5__2 = ((Component)<playerText>5__1).GetComponent<Outline>(); if ((Object)(object)<outline>5__2 != (Object)null) { ((Behaviour)<outline>5__2).enabled = false; <>2__current = (object)new WaitForSeconds(0.01f); <>1__state = 1; return true; } goto IL_0180; case 1: <>1__state = -1; ((Behaviour)<outline>5__2).enabled = true; goto IL_0180; case 2: <>1__state = -1; goto IL_0180; case 3: { <>1__state = -1; ((Behaviour)<outline>5__2).enabled = true; break; } IL_0180: <>s__4 = SpeakingIndicator.lockObj; <>s__5 = false; try { Monitor.Enter(<>s__4, ref <>s__5); <stillSpeaking>5__3 = SpeakingIndicator.speakingPlayers.ContainsKey(playerName); } finally { if (<>s__5) { Monitor.Exit(<>s__4); } } <>s__4 = null; if (<stillSpeaking>5__3) { <>2__current = null; <>1__state = 2; return true; } if ((Object)(object)<playerText>5__1 != (Object)null && <playerText>5__1.text.Contains(SpeakingIndicator.speakText)) { <playerText>5__1.text = <playerText>5__1.text.Replace(" " + SpeakingIndicator.speakText, ""); } if ((Object)(object)<outline>5__2 != (Object)null) { ((Behaviour)<outline>5__2).enabled = false; <>2__current = (object)new WaitForSeconds(0.01f); <>1__state = 3; return true; } break; } <>s__6 = SpeakingIndicator.lockObj; <>s__7 = false; try { Monitor.Enter(<>s__6, ref <>s__7); SpeakingIndicator.activeCoroutines.Remove(playerName); } finally { if (<>s__7) { Monitor.Exit(<>s__6); } } <>s__6 = null; VoiceManager.RemoveFromSpeaking(playerName); Object.Destroy((Object)(object)((Component)<>4__this).gameObject); 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 <RemoveIndicatorCoroutine>d__1 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string playerName; public DispatcherCoroutineRunner <>4__this; private Text <playerText>5__1; private Outline <outline>5__2; private object <>s__3; private bool <>s__4; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <RemoveIndicatorCoroutine>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <playerText>5__1 = null; <outline>5__2 = null; <>s__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: { <>1__state = -1; <playerText>5__1 = SpeakingIndicator.FindTextForPlayer(playerName); if ((Object)(object)<playerText>5__1 != (Object)null && <playerText>5__1.text.Contains(SpeakingIndicator.speakText)) { <playerText>5__1.text = <playerText>5__1.text.Replace(" " + SpeakingIndicator.speakText, ""); } Text obj = <playerText>5__1; <outline>5__2 = ((obj != null) ? ((Component)obj).GetComponent<Outline>() : null); if ((Object)(object)<outline>5__2 != (Object)null) { ((Behaviour)<outline>5__2).enabled = false; <>2__current = (object)new WaitForSeconds(0.01f); <>1__state = 1; return true; } break; } case 1: <>1__state = -1; ((Behaviour)<outline>5__2).enabled = true; break; } <>s__3 = SpeakingIndicator.lockObj; <>s__4 = false; try { Monitor.Enter(<>s__3, ref <>s__4); SpeakingIndicator.activeCoroutines.Remove(playerName); } finally { if (<>s__4) { Monitor.Exit(<>s__3); } } <>s__3 = null; VoiceManager.RemoveFromSpeaking(playerName); Object.Destroy((Object)(object)((Component)<>4__this).gameObject); 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(); } } [IteratorStateMachine(typeof(<AddIndicatorCoroutine>d__0))] public IEnumerator AddIndicatorCoroutine(string playerName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <AddIndicatorCoroutine>d__0(0) { <>4__this = this, playerName = playerName }; } [IteratorStateMachine(typeof(<RemoveIndicatorCoroutine>d__1))] public IEnumerator RemoveIndicatorCoroutine(string playerName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <RemoveIndicatorCoroutine>d__1(0) { <>4__this = this, playerName = playerName }; } } public class UI : MonoBehaviour { [CompilerGenerated] private sealed class <WaitForKeyPressed>d__17 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Action<KeyCode> callback; public UI <>4__this; private IEnumerator <>s__1; private KeyCode <kcode>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitForKeyPressed>d__17(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>s__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>4__this.waitingKey = true; break; case 1: <>1__state = -1; break; } if (<>4__this.waitingKey) { <>s__1 = Enum.GetValues(typeof(KeyCode)).GetEnumerator(); try { while (<>s__1.MoveNext()) { <kcode>5__2 = (KeyCode)<>s__1.Current; if (Input.GetKeyDown(<kcode>5__2)) { callback(<kcode>5__2); SettingsManager.Save(); <>4__this.waitingKey = false; break; } } } finally { if (<>s__1 is IDisposable disposable) { disposable.Dispose(); } } <>s__1 = null; <>2__current = null; <>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(); } } private bool waitingKey = false; private readonly CancellationTokenSource cts = new CancellationTokenSource(); public bool ShowMenu { get; set; } = false; public Rect MenuRect { get; set; } = new Rect((float)(Screen.width - 500), 100f, 400f, 600f); public bool MicDropdownOpen { get; set; } = false; public void UpdateUI() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); if (!(((Scene)(ref activeScene)).name != "menus")) { return; } if (Input.GetKeyDown(SettingsManager.CurrentSettings.menuToggleKey)) { ShowMenu = !ShowMenu; if (!ShowMenu) { MicDropdownOpen = false; } } if (SettingsManager.CurrentSettings.micMode == MicMode.PushToTalk) { if (Input.GetKeyDown(SettingsManager.CurrentSettings.pushToTalkKey)) { VoiceManager.StartVoiceStream(cts.Token); } if (Input.GetKeyUp(SettingsManager.CurrentSettings.pushToTalkKey)) { VoiceManager.StopVoiceStream(); } } } public void DrawUI() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Invalid comparison between Unknown and I4 //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Invalid comparison between Unknown and I4 if (ShowMenu) { GUI.color = Color.white; GUI.enabled = true; MenuRect = GUI.Window(0, MenuRect, new WindowFunction(DrawClientMenu), Localization.T("voicechat_menu")); Rect menuRect = MenuRect; if (((Rect)(ref menuRect)).Contains(Event.current.mousePosition) && ((int)Event.current.type == 0 || (int)Event.current.type == 1 || (int)Event.current.type == 3)) { Event.current.Use(); } if (MicDropdownOpen) { DrawMicDropdown(); } } } private void DrawMicDropdown() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_007a: Unknown result type (might be due to invalid IL or missing references) float num = 260f; float num2 = 24f; float num3 = Mathf.Min(num2 * (float)VoiceManager.MicDevices.Length, 6f * num2); Rect menuRect = MenuRect; float num4 = ((Rect)(ref menuRect)).x - num; menuRect = MenuRect; float num5 = ((Rect)(ref menuRect)).y + 190f; GUI.Box(new Rect(num4, num5, num, num3), ""); Rect val = default(Rect); for (int i = 0; i < VoiceManager.MicDevices.Length; i++) { ((Rect)(ref val))..ctor(num4, num5 + (float)i * num2, num, num2); if (GUI.Button(val, VoiceManager.MicDevices[i])) { SettingsManager.CurrentSettings.selectedMicIndex = i; SettingsManager.Save(); VoiceManager.StopCapture(); if (SettingsManager.CurrentSettings.hearSelf) { VoiceManager.StartCapture(i); } MicDropdownOpen = false; } } } [IteratorStateMachine(typeof(<WaitForKeyPressed>d__17))] public IEnumerator WaitForKeyPressed(Action<KeyCode> callback) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitForKeyPressed>d__17(0) { <>4__this = this, callback = callback }; } private void DrawClientMenu(int windowID) { //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(Array.Empty<GUILayoutOption>()); GUILayout.Label(string.Format("{0} {1}", Localization.T("connected_clients"), NetworkManager.ClientList.Count), Array.Empty<GUILayoutOption>()); GUILayout.Label(Localization.T("volume_players"), Array.Empty<GUILayoutOption>()); float num = GUILayout.HorizontalSlider(SettingsManager.CurrentSettings.playersVolume, 0f, 1f, Array.Empty<GUILayoutOption>()); if (Math.Abs(num - SettingsManager.CurrentSettings.playersVolume) > 0.001f) { SettingsManager.CurrentSettings.playersVolume = num; VoiceManager.UpdatePlayersVolume(num); SettingsManager.Save(); } GUILayout.Label(Localization.T("volume_microphone"), Array.Empty<GUILayoutOption>()); float num2 = GUILayout.HorizontalSlider(SettingsManager.CurrentSettings.selfVolume, 0f, 1f, Array.Empty<GUILayoutOption>()); if (Math.Abs(num2 - SettingsManager.CurrentSettings.selfVolume) > 0.001f) { SettingsManager.CurrentSettings.selfVolume = num2; VoiceManager.UpdateSelfVolume(num2); SettingsManager.Save(); } bool flag = GUILayout.Toggle(SettingsManager.CurrentSettings.hearSelf, Localization.T("hear_myself"), Array.Empty<GUILayoutOption>()); if (flag != SettingsManager.CurrentSettings.hearSelf) { SettingsManager.CurrentSettings.hearSelf = flag; SettingsManager.Save(); VoiceManager.StopCapture(); if (flag) { VoiceManager.StartCapture(SettingsManager.CurrentSettings.selectedMicIndex); } } GUILayout.Space(8f); GUILayout.Label(Localization.T("select_microphone"), Array.Empty<GUILayoutOption>()); if (GUILayout.Button((VoiceManager.MicDevices.Length != 0) ? VoiceManager.MicDevices[SettingsManager.CurrentSettings.selectedMicIndex] : "No microphone", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(260f) })) { MicDropdownOpen = !MicDropdownOpen; } GUILayout.Space(8f); GUILayout.Label(Localization.T("keybinds"), Array.Empty<GUILayoutOption>()); DrawKeybind("open_close_menu", SettingsManager.CurrentSettings.menuToggleKey, delegate(KeyCode k) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) SettingsManager.CurrentSettings.menuToggleKey = k; }); DrawKeybind("push_to_talk", SettingsManager.CurrentSettings.pushToTalkKey, delegate(KeyCode k) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) SettingsManager.CurrentSettings.pushToTalkKey = k; }); GUILayout.Space(8f); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label(Localization.T("buffer_size"), Array.Empty<GUILayoutOption>()); int bufferSizeMs = SettingsManager.CurrentSettings.bufferSizeMs; int num3 = (int)GUILayout.HorizontalSlider((float)bufferSizeMs, 10f, 100f, Array.Empty<GUILayoutOption>()); GUILayout.EndHorizontal(); GUILayout.Label(string.Format("{0} {1} ms", Localization.T("buffer_current"), num3), Array.Empty<GUILayoutOption>()); if (num3 != bufferSizeMs) { SettingsManager.CurrentSettings.bufferSizeMs = num3; SettingsManager.Save(); } GUILayout.Space(8f); GUILayout.Label(Localization.T("language"), Array.Empty<GUILayoutOption>()); string[] array = new string[6] { "English", "Russian (Русский)", "Spanish (Español)", "French (Français)", "Chinese (中文)", "Japanese (日本語)" }; int num4 = Array.IndexOf(array, SettingsManager.CurrentSettings.language); if (num4 == -1) { num4 = 0; } int num5 = 3; int num6 = Mathf.CeilToInt((float)array.Length / (float)num5); int num7 = num4; for (int i = 0; i < num6; i++) { int num8 = i * num5; int num9 = Mathf.Min(num8 + num5, array.Length); string[] array2 = new string[num9 - num8]; Array.Copy(array, num8, array2, 0, array2.Length); int num10 = GUILayout.Toolbar(Array.IndexOf(array2, array[num7]), array2, Array.Empty<GUILayoutOption>()); if (num10 != Array.IndexOf(array2, array[num7])) { num7 = num8 + num10; } } if (num7 != num4) { SettingsManager.CurrentSettings.language = array[num7]; Localization.SetLanguage(SettingsManager.CurrentSettings.language); SpeakingIndicator.UpdateSpeakText(); SettingsManager.Save(); } if (GUILayout.Button(Localization.T("close"), Array.Empty<GUILayoutOption>())) { ShowMenu = false; MicDropdownOpen = false; } GUILayout.EndVertical(); GUI.DragWindow(); } private void DrawKeybind(string labelKey, KeyCode currentKey, Action<KeyCode> onKeyPressed) { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label(Localization.T(labelKey), Array.Empty<GUILayoutOption>()); if (GUILayout.Button(((object)(KeyCode)(ref currentKey)).ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) }) && !waitingKey) { ((MonoBehaviour)this).StartCoroutine(WaitForKeyPressed(onKeyPressed)); } GUILayout.EndHorizontal(); } } public class UnityMainThreadDispatcher : MonoBehaviour { private static readonly Queue<Action> actions = new Queue<Action>(); public static void Enqueue(Action action) { lock (actions) { actions.Enqueue(action); } } private void Update() { lock (actions) { while (actions.Count > 0) { actions.Dequeue()(); } } } } public static class VoiceManager { private static WasapiCapture capture; private static SoundInSource soundInSource; private static IWaveSource waveSource; private static WasapiOut playback; private static MMDeviceEnumerator deviceEnum; private static string allowedScene; private static readonly object lockObj = new object(); public static readonly List<string> speaking = new List<string>(); private static bool streaming = false; private static int udpReceivePort; private static Thread udpReceiveThread; private static bool receiving = false; private static UdpClient udpClient; private static Thread keepAliveThreadSend; private static bool keepAliveSendRunning = false; private static WasapiOut playersPlayback; private static string instanceId; private static readonly CancellationTokenSource cts = new CancellationTokenSource(); public static string[] MicDevices { get; private set; } = Array.Empty<string>(); public static MMDevice[] CaptureDevices { get; private set; } = Array.Empty<MMDevice>(); public static void RemoveFromSpeaking(string name) { lock (lockObj) { speaking.Remove(name); } } private static async void SendUdpPunch() { try { if (!streaming) { byte[] punch = new byte[1]; udpClient?.Send(punch, punch.Length, Main.ServerEP); await Task.Delay(500); } } catch (Exception ex) { Exception e = ex; Debug.LogWarning((object)("[VoiceChat] UDP punch failed: " + e.Message)); } } private static void StartUdpKeepAliveSend() { if (keepAliveSendRunning) { return; } keepAliveSendRunning = true; keepAliveThreadSend = new Thread((ThreadStart)async delegate { while (keepAliveSendRunning) { await Task.Delay(20000); try { SendUdpPunch(); } catch (Exception ex) { Debug.LogWarning((object)("[VoiceChat] KeepAliveSend: " + ex.Message)); } } }) { IsBackground = true }; keepAliveThreadSend.Start(); } public static void InitDevices() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown try { deviceEnum = new MMDeviceEnumerator(); MMDeviceCollection val = deviceEnum.EnumAudioEndpoints((DataFlow)1, (DeviceState)1); List<string> list = new List<string>(); List<MMDevice> list2 = new List<MMDevice>(); foreach (MMDevice item in val) { list.Add(item.FriendlyName); list2.Add(item); } MicDevices = list.ToArray(); CaptureDevices = list2.ToArray(); } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] CSCore: InitDevices failed: " + ex.Message)); MicDevices = Array.Empty<string>(); CaptureDevices = Array.Empty<MMDevice>(); } } public static void StartCapture(int deviceIndex) { //IL_004e: 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_0060: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown try { StopCapture(); if (SettingsManager.CurrentSettings.hearSelf && CaptureDevices.Length != 0 && deviceIndex >= 0 && deviceIndex < CaptureDevices.Length) { MMDevice device = CaptureDevices[deviceIndex]; capture = new WasapiCapture { Device = device }; capture.Initialize(); soundInSource = new SoundInSource((ISoundIn)(object)capture) { FillWithZeros = true }; waveSource = FluentExtensions.ToWaveSource(FluentExtensions.ToSampleSource((IWaveSource)(object)soundInSource), 16); playback = new WasapiOut(); playback.Initialize(waveSource); playback.Volume = SettingsManager.CurrentSettings.selfVolume; capture.Start(); playback.Play(); } } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] VoiceManager: StartCapture failed: " + ex.Message)); StopCapture(); } } public static void StopCapture() { try { WasapiCapture obj = capture; if (obj != null) { obj.Stop(); } } catch { } try { WasapiOut obj3 = playback; if (obj3 != null) { obj3.Stop(); } } catch { } try { WasapiCapture obj5 = capture; if (obj5 != null) { obj5.Dispose(); } } catch { } try { WasapiOut obj7 = playback; if (obj7 != null) { obj7.Dispose(); } } catch { } try { SoundInSource obj9 = soundInSource; if (obj9 != null) { obj9.Dispose(); } } catch { } try { ((IDisposable)waveSource)?.Dispose(); } catch { } capture = null; playback = null; soundInSource = null; waveSource = null; } public static void SetInstanceId(string instanceid) { instanceId = instanceid; } public static async Task StartVoiceStream(CancellationToken token) { StopVoiceStream(); streaming = true; try { if (NetworkManager.State != NetworkManager.ConnectionState.Connected) { Debug.LogWarning((object)"[VoiceChat] TCP connection missing, trying reconnect..."); if (!(await NetworkManager.Connect())) { Debug.LogError((object)"[VoiceChat] failed to reconnect tcp."); streaming = false; return; } } if (udpClient == null) { udpClient = new UdpClient(0); udpReceivePort = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; udpClient.Client.ReceiveTimeout = 1000; udpClient.Client.SendTimeout = 1000; } await NetworkManager.SendMessage("SPEAK_REQUEST"); if (!(await NetworkManager.WaitForResponse("SPEAK_OK", 500))) { Debug.LogError((object)"[VoiceChat] failed to request speak."); streaming = false; return; } string localSteamID = SteamUser.GetSteamID().m_SteamID.ToString(); await NetworkManager.SendMessage($"UDP_INFO|{localSteamID}|{udpReceivePort}|{instanceId}"); Task.Run(() => SendAudioLoop(token), token); } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] error starting voice stream: " + ex)); streaming = false; } } public static void StopVoiceStream() { if (streaming) { streaming = false; } } public static async Task StartReceiving(CancellationToken token) { if (receiving) { return; } try { if (udpClient == null) { udpClient = new UdpClient(0); } udpReceivePort = ((IPEndPoint)udpClient.Client.LocalEndPoint).Port; receiving = true; udpClient.Client.ReceiveTimeout = 1000; udpClient.Client.SendTimeout = 1000; string steamID = SteamUser.GetSteamID().m_SteamID.ToString(); await NetworkManager.SendMessage($"UDP_INFO|{steamID}|{udpReceivePort}|{instanceId}"); StartUdpKeepAliveSend(); Task.Run(() => ReceiveAudioLoop(token), token); } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] failed to start udp receive: " + ex.Message)); } } public static void StopReceiving() { try { receiving = false; keepAliveSendRunning = false; if (keepAliveThreadSend != null) { try { keepAliveThreadSend.Join(500); } catch { } keepAliveThreadSend = null; } try { udpClient?.Close(); } catch { } if (udpReceiveThread != null) { try { udpReceiveThread.Join(500); } catch { } udpReceiveThread = null; } } catch (Exception ex) { Debug.LogError((object)("[VoiceChat] error stopping udp receive: " + ex.Message)); } } private static async Task ReceiveAudioLoop(CancellationToken token) { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); WaveFormat format = new WaveFormat(48000, 16, 1); int bufferSize = format.BytesPerSecond * SettingsManager.CurrentSettings.bufferSizeMs / 1000; WriteableBufferingSource bufferSource = new WriteableBufferingSource(format, bufferSize) { FillWithZeros = true }; try { playersPlayback = new WasapiOut(); playersPlayback.Initialize((IWaveSource)(object)bufferSource); playersPlayback.Volume = SettingsManager.CurrentSettings.playersVolume; playersPlayback.Play(); while (receiving && !token.IsCancellationRequested) { try { byte[] data = udpClient.Receive(ref remoteEP); if (data != null && data.Length != 0) { bufferSource.Write(data, 0, data.Length); } UnityMainThreadDispatcher.Enqueue(delegate { lock (lockObj) { foreach (string item in speaking) { SpeakingIndicator.OnPlayerSpeaking(item); } } }); } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut) { } catch (ObjectDisposedException) { break; } await Task.Delay(1, token); } } catch (OperationCanceledException) { } catch (Exception ex4) { Debug.LogError((object)("[VoiceChat] ReceiveAudioLoop fatal: " + ex4)); } finally { try { WasapiOut obj = playersPlayback; if (obj != null) { obj.Stop(); } } catch { } try { WasapiOut obj3 = playersPlayback; if (obj3 != null) { obj3.Dispose(); } } catch { } } } private static async Task SendAudioLoop(CancellationToken token) { WasapiCapture micCapture = null; SoundInSource source = null; try { micCapture = new WasapiCapture(); micCapture.Initialize(); source = new SoundInSource((ISoundIn)(object)micCapture) { FillWithZeros = false }; WaveFormat targetFormat = new WaveFormat(48000, 16, 1); IWaveSource converted = FluentExtensions.ToWaveSource(FluentExtensions.ChangeSampleRate(FluentExtensions.ToMono(FluentExtensions.ToSampleSource((IWaveSource)(object)source)), targetFormat.SampleRate), 16); micCapture.Start(); byte[] buffer = new byte[((IAudioSource)converted).WaveFormat.BytesPerSecond * SettingsManager.CurrentSettings.bufferSizeMs / 1000]; while (streaming && !token.IsCancellationRequested) { int read = ((IReadableAudioSource<byte>)(object)converted).Read(buffer, 0, buffer.Length); bool sendData = true; if (SettingsManager.CurrentSettings.micMode == MicMode.VoiceActivation) { float rms = 0f; for (int i = 0; i < read && i + 1 < read; i += 2) { short sample = (short)(buffer[i] | (buffer[i + 1] << 8)); float f = (float)sample / 32768f; rms += f * f; } if (read > 0) { rms = Mathf.Sqrt(rms / (float)(read / 2)); } float db = 20f * Mathf.Log10(Mathf.Max(rms, 0.0001f)); sendData = db >= SettingsManager.CurrentSettings.voiceThresholdDb; } if (sendData && read > 0) { try { udpClient?.Send(buffer, read, Main.ServerEP); } catch (Exception ex3) { Exception ex2 = ex3; Debug.LogWarning((object)("[VoiceChat] UDP send failed: " + ex2.Message)); } } await Task.Delay(10, token); } } catch (OperationCanceledException) { } catch (Exception ex3) { Exception ex = ex3; Debug.LogError((object)("[VoiceChat] udp capture error: " + ex.Message)); } finally { SoundInSource obj = source; if (obj != null) { obj.Dispose(); } WasapiCapture obj2 = micCapture; if (obj2 != null) { obj2.Dispose(); } } } public static void RestartUdp() { StopReceiving(); keepAliveSendRunning = false; if (keepAliveThreadSend != null) { try { keepAliveThreadSend.Join(500); } catch { } keepAliveThreadSend = null; } try { udpClient?.Close(); } catch { } udpClient = null; receiving = false; StartReceiving(cts.Token); } public static void UpdatePlayersVolume(float volume) { if (playersPlayback != null) { playersPlayback.Volume = volume; } } public static void UpdateSelfVolume(float volume) { if (playback != null) { playback.Volume = volume; } } public static void AllowScenePlayback(string scene, string name) { if (scene == "menus") { return; } lock (lockObj) { allowedScene = scene; if (!speaking.Contains(name)) { speaking.Add(name); } } } public static bool IsAllowed(string scene) { return allowedScene != null && allowedScene == scene; } }
CSCore.dll
Decompiled 5 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Configuration; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Runtime.Serialization; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using CSCore.Codecs.AAC; using CSCore.Codecs.AIFF; using CSCore.Codecs.DDP; using CSCore.Codecs.FLAC; using CSCore.Codecs.FLAC.Metadata; using CSCore.Codecs.MP1; using CSCore.Codecs.MP2; using CSCore.Codecs.MP3; using CSCore.Codecs.WAV; using CSCore.Codecs.WMA; using CSCore.CoreAudioAPI; using CSCore.DMO; using CSCore.DMO.Effects; using CSCore.DSP; using CSCore.DirectSound; using CSCore.MediaFoundation; using CSCore.SoundIn; using CSCore.SoundOut; using CSCore.SoundOut.MMInterop; using CSCore.Streams; using CSCore.Streams.SampleConverter; using CSCore.Tags.ID3; using CSCore.Tags.ID3.Frames; using CSCore.Utils; using CSCore.Utils.Buffer; using CSCore.Win32; [assembly: CompilationRelaxations(8)] [assembly: AssemblyTitle("CSCore")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyDescription(".NET Sound Library")] [assembly: Guid("7939b0f4-fed9-4dcf-bb59-a17505864c55")] [assembly: AssemblyFileVersion("1.2.1.2")] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CLSCompliant(true)] [assembly: ComVisible(false)] [assembly: InternalsVisibleTo("CSCore.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100237314800493cdf9aabec35955e8928e3d5416ad1d223e8914e0e025ff9095b21bbb696235b9d3886b0edec26107ca0af49c3170fc08d117e8e9265ab371b157f2c2b27843d97c1d312850d10d1272c1d46d18f02ac56f46676cbe7946049b1b344db7154d35788fee27b3d581bd7d43e41813b10fd360a3fbfab9199d9e86a4")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CSCore")] [assembly: AssemblyCopyright("Florian R.")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.1.2")] [module: UnverifiableCode] namespace CSCore { public static class AudioSubTypes { public static readonly Guid Unknown = new Guid(0, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Pcm = new Guid(1, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Adpcm = new Guid(2, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid IeeeFloat = new Guid(3, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Vselp = new Guid(4, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid IbmCvsd = new Guid(5, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid ALaw = new Guid(6, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MuLaw = new Guid(7, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Dts = new Guid(8, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Drm = new Guid(9, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WmaVoice9 = new Guid(10, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid OkiAdpcm = new Guid(16, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid DviAdpcm = new Guid(17, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid ImaAdpcm = new Guid(17, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MediaspaceAdpcm = new Guid(18, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid SierraAdpcm = new Guid(19, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid G723Adpcm = new Guid(20, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid DigiStd = new Guid(21, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid DigiFix = new Guid(22, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid DialogicOkiAdpcm = new Guid(23, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MediaVisionAdpcm = new Guid(24, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid CUCodec = new Guid(25, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid YamahaAdpcm = new Guid(32, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid SonarC = new Guid(33, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid DspGroupTrueSpeech = new Guid(34, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid EchoSpeechCorporation1 = new Guid(35, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid AudioFileAf36 = new Guid(36, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Aptx = new Guid(37, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid AudioFileAf10 = new Guid(38, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Prosody1612 = new Guid(39, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Lrc = new Guid(40, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid DolbyAc2 = new Guid(48, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Gsm610 = new Guid(49, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MsnAudio = new Guid(50, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid AntexAdpcme = new Guid(51, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid ControlResVqlpc = new Guid(52, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid DigiReal = new Guid(53, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid DigiAdpcm = new Guid(54, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid ControlResCr10 = new Guid(55, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_NMS_VBXADPCM = new Guid(56, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_CS_IMAADPCM = new Guid(57, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_ECHOSC3 = new Guid(58, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_ROCKWELL_ADPCM = new Guid(59, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_ROCKWELL_DIGITALK = new Guid(60, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_XEBEC = new Guid(61, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_G721_ADPCM = new Guid(64, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_G728_CELP = new Guid(65, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_MSG723 = new Guid(66, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Mpeg = new Guid(80, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_RT24 = new Guid(82, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_PAC = new Guid(83, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MpegLayer3 = new Guid(85, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_LUCENT_G723 = new Guid(89, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_CIRRUS = new Guid(96, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_ESPCM = new Guid(97, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE = new Guid(98, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_CANOPUS_ATRAC = new Guid(99, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_G726_ADPCM = new Guid(100, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_G722_ADPCM = new Guid(101, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_DSAT_DISPLAY = new Guid(103, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_BYTE_ALIGNED = new Guid(105, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_AC8 = new Guid(112, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_AC10 = new Guid(113, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_AC16 = new Guid(114, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_AC20 = new Guid(115, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_RT24 = new Guid(116, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_RT29 = new Guid(117, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_RT29HW = new Guid(118, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_VR12 = new Guid(119, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_VR18 = new Guid(120, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_TQ40 = new Guid(121, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SOFTSOUND = new Guid(128, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VOXWARE_TQ60 = new Guid(129, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_MSRT24 = new Guid(130, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_G729A = new Guid(131, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_MVI_MVI2 = new Guid(132, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_DF_G726 = new Guid(133, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_DF_GSM610 = new Guid(134, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_ISIAUDIO = new Guid(136, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_ONLIVE = new Guid(137, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SBC24 = new Guid(145, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_DOLBY_AC3_SPDIF = new Guid(146, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_MEDIASONIC_G723 = new Guid(147, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_PROSODY_8KBPS = new Guid(148, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_ZYXEL_ADPCM = new Guid(151, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_PHILIPS_LPCBB = new Guid(152, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_PACKED = new Guid(153, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_MALDEN_PHONYTALK = new Guid(160, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Gsm = new Guid(161, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid G729 = new Guid(162, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid G723 = new Guid(163, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Acelp = new Guid(164, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid RawAac = new Guid(255, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_RHETOREX_ADPCM = new Guid(256, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_IRAT = new Guid(257, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VIVO_G723 = new Guid(273, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VIVO_SIREN = new Guid(274, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_DIGITAL_G723 = new Guid(291, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SANYO_LD_ADPCM = new Guid(293, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SIPROLAB_ACEPLNET = new Guid(304, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SIPROLAB_ACELP4800 = new Guid(305, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SIPROLAB_ACELP8V3 = new Guid(306, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SIPROLAB_G729 = new Guid(307, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SIPROLAB_G729A = new Guid(308, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SIPROLAB_KELVIN = new Guid(309, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_G726ADPCM = new Guid(320, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_QUALCOMM_PUREVOICE = new Guid(336, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_QUALCOMM_HALFRATE = new Guid(337, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_TUBGSM = new Guid(341, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_MSAUDIO1 = new Guid(352, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WindowsMediaAudio = new Guid(353, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WindowsMediaAudioProfessional = new Guid(354, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WindowsMediaAudioLosseless = new Guid(355, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WindowsMediaAudioSpdif = new Guid(356, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_UNISYS_NAP_ADPCM = new Guid(368, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_UNISYS_NAP_ULAW = new Guid(369, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_UNISYS_NAP_ALAW = new Guid(370, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_UNISYS_NAP_16K = new Guid(371, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_CREATIVE_ADPCM = new Guid(512, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_CREATIVE_FASTSPEECH8 = new Guid(514, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_CREATIVE_FASTSPEECH10 = new Guid(515, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_UHER_ADPCM = new Guid(528, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_QUARTERDECK = new Guid(544, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_ILINK_VC = new Guid(560, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_RAW_SPORT = new Guid(576, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_ESST_AC3 = new Guid(577, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_IPI_HSX = new Guid(592, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_IPI_RPELP = new Guid(593, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_CS2 = new Guid(608, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SONY_SCX = new Guid(624, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_FM_TOWNS_SND = new Guid(768, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_BTV_DIGITAL = new Guid(1024, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_QDESIGN_MUSIC = new Guid(1104, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_VME_VMPCM = new Guid(1664, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_TPC = new Guid(1665, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_OLIGSM = new Guid(4096, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_OLIADPCM = new Guid(4097, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_OLICELP = new Guid(4098, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_OLISBC = new Guid(4099, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_OLIOPR = new Guid(4100, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_LH_CODEC = new Guid(4352, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_NORRIS = new Guid(5120, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_SOUNDSPACE_MUSICOMPRESS = new Guid(5376, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MPEG_ADTS_AAC = new Guid(5632, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MPEG_RAW_AAC = new Guid(5633, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MPEG_LOAS = new Guid(5634, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid NOKIA_MPEG_ADTS_AAC = new Guid(5640, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid NOKIA_MPEG_RAW_AAC = new Guid(5641, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid VODAFONE_MPEG_ADTS_AAC = new Guid(5642, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid VODAFONE_MPEG_RAW_AAC = new Guid(5643, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MPEG_HEAAC = new Guid(5648, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_DVM = new Guid(8192, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Vorbis1 = new Guid(26447, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Vorbis2 = new Guid(26448, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Vorbis3 = new Guid(26449, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Vorbis1P = new Guid(26479, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Vorbis2P = new Guid(26480, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Vorbis3P = new Guid(26481, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_RAW_AAC1 = new Guid(255, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_WMAVOICE9 = new Guid(10, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid Extensible = new Guid(65534, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_DEVELOPMENT = new Guid(65535, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid WAVE_FORMAT_FLAC = new Guid(61868, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); public static readonly Guid MediaTypeAudio = new Guid("73647561-0000-0010-8000-00AA00389B71"); public static AudioEncoding EncodingFromSubType(Guid audioSubType) { byte[] value = audioSubType.ToByteArray(); int num = BitConverter.ToInt32(value, 0); if (Enum.IsDefined(typeof(AudioEncoding), (short)num)) { return (AudioEncoding)num; } throw new ArgumentException("Invalid audioSubType.", "audioSubType"); } public static Guid SubTypeFromEncoding(AudioEncoding audioEncoding) { if (Enum.IsDefined(typeof(AudioEncoding), (short)audioEncoding)) { return new Guid((int)audioEncoding, 0, 16, 128, 0, 0, 170, 0, 56, 155, 113); } throw new ArgumentException("Invalid encoding.", "audioEncoding"); } } public enum AudioEncoding : short { Unknown = 0, Pcm = 1, Adpcm = 2, IeeeFloat = 3, Vselp = 4, IbmCvsd = 5, ALaw = 6, MuLaw = 7, Dts = 8, Drm = 9, WmaVoice9 = 10, OkiAdpcm = 16, DviAdpcm = 17, ImaAdpcm = 17, MediaspaceAdpcm = 18, SierraAdpcm = 19, G723Adpcm = 20, DigiStd = 21, DigiFix = 22, DialogicOkiAdpcm = 23, MediaVisionAdpcm = 24, CUCodec = 25, YamahaAdpcm = 32, SonarC = 33, DspGroupTrueSpeech = 34, EchoSpeechCorporation1 = 35, AudioFileAf36 = 36, Aptx = 37, AudioFileAf10 = 38, Prosody1612 = 39, Lrc = 40, DolbyAc2 = 48, Gsm610 = 49, MsnAudio = 50, AntexAdpcme = 51, ControlResVqlpc = 52, DigiReal = 53, DigiAdpcm = 54, ControlResCr10 = 55, WAVE_FORMAT_NMS_VBXADPCM = 56, WAVE_FORMAT_CS_IMAADPCM = 57, WAVE_FORMAT_ECHOSC3 = 58, WAVE_FORMAT_ROCKWELL_ADPCM = 59, WAVE_FORMAT_ROCKWELL_DIGITALK = 60, WAVE_FORMAT_XEBEC = 61, WAVE_FORMAT_G721_ADPCM = 64, WAVE_FORMAT_G728_CELP = 65, WAVE_FORMAT_MSG723 = 66, Mpeg = 80, WAVE_FORMAT_RT24 = 82, WAVE_FORMAT_PAC = 83, MpegLayer3 = 85, WAVE_FORMAT_LUCENT_G723 = 89, WAVE_FORMAT_CIRRUS = 96, WAVE_FORMAT_ESPCM = 97, WAVE_FORMAT_VOXWARE = 98, WAVE_FORMAT_CANOPUS_ATRAC = 99, WAVE_FORMAT_G726_ADPCM = 100, WAVE_FORMAT_G722_ADPCM = 101, WAVE_FORMAT_DSAT_DISPLAY = 103, WAVE_FORMAT_VOXWARE_BYTE_ALIGNED = 105, WAVE_FORMAT_VOXWARE_AC8 = 112, WAVE_FORMAT_VOXWARE_AC10 = 113, WAVE_FORMAT_VOXWARE_AC16 = 114, WAVE_FORMAT_VOXWARE_AC20 = 115, WAVE_FORMAT_VOXWARE_RT24 = 116, WAVE_FORMAT_VOXWARE_RT29 = 117, WAVE_FORMAT_VOXWARE_RT29HW = 118, WAVE_FORMAT_VOXWARE_VR12 = 119, WAVE_FORMAT_VOXWARE_VR18 = 120, WAVE_FORMAT_VOXWARE_TQ40 = 121, WAVE_FORMAT_SOFTSOUND = 128, WAVE_FORMAT_VOXWARE_TQ60 = 129, WAVE_FORMAT_MSRT24 = 130, WAVE_FORMAT_G729A = 131, WAVE_FORMAT_MVI_MVI2 = 132, WAVE_FORMAT_DF_G726 = 133, WAVE_FORMAT_DF_GSM610 = 134, WAVE_FORMAT_ISIAUDIO = 136, WAVE_FORMAT_ONLIVE = 137, WAVE_FORMAT_SBC24 = 145, WAVE_FORMAT_DOLBY_AC3_SPDIF = 146, WAVE_FORMAT_MEDIASONIC_G723 = 147, WAVE_FORMAT_PROSODY_8KBPS = 148, WAVE_FORMAT_ZYXEL_ADPCM = 151, WAVE_FORMAT_PHILIPS_LPCBB = 152, WAVE_FORMAT_PACKED = 153, WAVE_FORMAT_MALDEN_PHONYTALK = 160, Gsm = 161, G729 = 162, G723 = 163, Acelp = 164, RawAac = 255, WAVE_FORMAT_RHETOREX_ADPCM = 256, WAVE_FORMAT_IRAT = 257, WAVE_FORMAT_VIVO_G723 = 273, WAVE_FORMAT_VIVO_SIREN = 274, WAVE_FORMAT_DIGITAL_G723 = 291, WAVE_FORMAT_SANYO_LD_ADPCM = 293, WAVE_FORMAT_SIPROLAB_ACEPLNET = 304, WAVE_FORMAT_SIPROLAB_ACELP4800 = 305, WAVE_FORMAT_SIPROLAB_ACELP8V3 = 306, WAVE_FORMAT_SIPROLAB_G729 = 307, WAVE_FORMAT_SIPROLAB_G729A = 308, WAVE_FORMAT_SIPROLAB_KELVIN = 309, WAVE_FORMAT_G726ADPCM = 320, WAVE_FORMAT_QUALCOMM_PUREVOICE = 336, WAVE_FORMAT_QUALCOMM_HALFRATE = 337, WAVE_FORMAT_TUBGSM = 341, WAVE_FORMAT_MSAUDIO1 = 352, WindowsMediaAudio = 353, WindowsMediaAudioProfessional = 354, WindowsMediaAudioLosseless = 355, WindowsMediaAudioSpdif = 356, WAVE_FORMAT_UNISYS_NAP_ADPCM = 368, WAVE_FORMAT_UNISYS_NAP_ULAW = 369, WAVE_FORMAT_UNISYS_NAP_ALAW = 370, WAVE_FORMAT_UNISYS_NAP_16K = 371, WAVE_FORMAT_CREATIVE_ADPCM = 512, WAVE_FORMAT_CREATIVE_FASTSPEECH8 = 514, WAVE_FORMAT_CREATIVE_FASTSPEECH10 = 515, WAVE_FORMAT_UHER_ADPCM = 528, WAVE_FORMAT_QUARTERDECK = 544, WAVE_FORMAT_ILINK_VC = 560, WAVE_FORMAT_RAW_SPORT = 576, WAVE_FORMAT_ESST_AC3 = 577, WAVE_FORMAT_IPI_HSX = 592, WAVE_FORMAT_IPI_RPELP = 593, WAVE_FORMAT_CS2 = 608, WAVE_FORMAT_SONY_SCX = 624, WAVE_FORMAT_FM_TOWNS_SND = 768, WAVE_FORMAT_BTV_DIGITAL = 1024, WAVE_FORMAT_QDESIGN_MUSIC = 1104, WAVE_FORMAT_VME_VMPCM = 1664, WAVE_FORMAT_TPC = 1665, WAVE_FORMAT_OLIGSM = 4096, WAVE_FORMAT_OLIADPCM = 4097, WAVE_FORMAT_OLICELP = 4098, WAVE_FORMAT_OLISBC = 4099, WAVE_FORMAT_OLIOPR = 4100, WAVE_FORMAT_LH_CODEC = 4352, WAVE_FORMAT_NORRIS = 5120, WAVE_FORMAT_SOUNDSPACE_MUSICOMPRESS = 5376, MPEG_ADTS_AAC = 5632, MPEG_RAW_AAC = 5633, MPEG_LOAS = 5634, NOKIA_MPEG_ADTS_AAC = 5640, NOKIA_MPEG_RAW_AAC = 5641, VODAFONE_MPEG_ADTS_AAC = 5642, VODAFONE_MPEG_RAW_AAC = 5643, MPEG_HEAAC = 5648, WAVE_FORMAT_DVM = 8192, Vorbis1 = 26447, Vorbis2 = 26448, Vorbis3 = 26449, Vorbis1P = 26479, Vorbis2P = 26480, Vorbis3P = 26481, WAVE_FORMAT_RAW_AAC1 = 255, WAVE_FORMAT_WMAVOICE9 = 10, Extensible = -2, WAVE_FORMAT_DEVELOPMENT = -1, WAVE_FORMAT_FLAC = -3668 } [Flags] public enum ChannelMask { SpeakerFrontLeft = 1, SpeakerFrontRight = 2, SpeakerFrontCenter = 4, SpeakerLowFrequency = 8, SpeakerBackLeft = 0x10, SpeakerBackRight = 0x20, SpeakerFrontLeftOfCenter = 0x40, SpeakerFrontRightOfCenter = 0x80, SpeakerBackCenter = 0x100, SpeakerSideLeft = 0x200, SpeakerSideRight = 0x400, SpeakerTopCenter = 0x800, SpeakerTopFrontLeft = 0x1000, SpeakerTopFrontCenter = 0x2000, SpeakerTopFrontRight = 0x4000, SpeakerTopBackLeft = 0x8000, SpeakerTopBackCenter = 0x10000, SpeakerTopBackRight = 0x20000 } public static class ChannelMasks { public const ChannelMask MonoMask = ChannelMask.SpeakerFrontCenter; public const ChannelMask StereoMask = ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight; public const ChannelMask FiveDotOneWithRearMask = ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight; public const ChannelMask FiveDotOneWithSideMask = ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight; public const ChannelMask SevenDotOneMask = ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight; } public static class FluentExtensions { public static TResult AppendSource<TInput, TResult>(this TInput input, Func<TInput, TResult> func) where TInput : IAudioSource { return func(input); } public static TResult AppendSource<TInput, TResult>(this TInput input, Func<TInput, TResult> func, out TResult outputSource) where TInput : IAudioSource { outputSource = func(input); return outputSource; } public static IWaveSource ChangeSampleRate(this IWaveSource input, int destinationSampleRate) { if (input == null) { throw new ArgumentNullException("input"); } if (destinationSampleRate <= 0) { throw new ArgumentOutOfRangeException("destinationSampleRate"); } if (input.WaveFormat.SampleRate == destinationSampleRate) { return input; } return new DmoResampler(input, destinationSampleRate); } public static ISampleSource ChangeSampleRate(this ISampleSource input, int destinationSampleRate) { if (input == null) { throw new ArgumentNullException("input"); } if (destinationSampleRate <= 0) { throw new ArgumentOutOfRangeException("destinationSampleRate"); } if (input.WaveFormat.SampleRate == destinationSampleRate) { return input; } return new DmoResampler(input.ToWaveSource(), destinationSampleRate).ToSampleSource(); } public static IWaveSource ToStereo(this IWaveSource input) { if (input == null) { throw new ArgumentNullException("input"); } if (input.WaveFormat.Channels == 2) { return input; } if (input.WaveFormat.Channels == 1) { return new MonoToStereoSource(input.ToSampleSource()).ToWaveSource(input.WaveFormat.BitsPerSample); } if (input.WaveFormat is WaveFormatExtensible waveFormatExtensible) { ChannelMask channelMask = waveFormatExtensible.ChannelMask; ChannelMatrix matrix = ChannelMatrix.GetMatrix(channelMask, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight); return new DmoChannelResampler(input, matrix); } WaveFormat waveFormat = (WaveFormat)input.WaveFormat.Clone(); waveFormat.Channels = 2; return new DmoResampler(input, waveFormat); } public static ISampleSource ToStereo(this ISampleSource input) { if (input == null) { throw new ArgumentNullException("input"); } if (input.WaveFormat.Channels == 2) { return input; } if (input.WaveFormat.Channels == 1) { return new MonoToStereoSource(input); } return input.ToWaveSource().ToStereo().ToSampleSource(); } public static IWaveSource ToMono(this IWaveSource input) { if (input == null) { throw new ArgumentNullException("input"); } if (input.WaveFormat.Channels == 1) { return input; } if (input.WaveFormat.Channels == 2) { return new StereoToMonoSource(input.ToSampleSource()).ToWaveSource(input.WaveFormat.BitsPerSample); } if (input.WaveFormat is WaveFormatExtensible waveFormatExtensible) { ChannelMask channelMask = waveFormatExtensible.ChannelMask; ChannelMatrix matrix = ChannelMatrix.GetMatrix(channelMask, ChannelMask.SpeakerFrontCenter); return new DmoChannelResampler(input, matrix); } WaveFormat waveFormat = (WaveFormat)input.WaveFormat.Clone(); waveFormat.Channels = 1; return new DmoResampler(input, waveFormat); } public static ISampleSource ToMono(this ISampleSource input) { if (input == null) { throw new ArgumentNullException("input"); } if (input.WaveFormat.Channels == 1) { return input; } if (input.WaveFormat.Channels == 2) { return new StereoToMonoSource(input); } return input.ToWaveSource().ToMono().ToSampleSource(); } public static IWaveSource Loop(this IWaveSource input) { return new LoopStream(input) { EnableLoop = true }; } public static IWaveSource ToWaveSource(this ISampleSource sampleSource, int bits) { if (sampleSource == null) { throw new ArgumentNullException("sampleSource"); } return bits switch { 8 => new SampleToPcm8(sampleSource), 16 => new SampleToPcm16(sampleSource), 24 => new SampleToPcm24(sampleSource), 32 => new SampleToIeeeFloat32(sampleSource), _ => throw new ArgumentOutOfRangeException("bits", "Must be 8, 16, 24 or 32 bits."), }; } public static IWaveSource ToWaveSource(this ISampleSource sampleSource) { if (sampleSource == null) { throw new ArgumentNullException("sampleSource"); } return new SampleToIeeeFloat32(sampleSource); } public static ISampleSource ToSampleSource(this IWaveSource waveSource) { if (waveSource == null) { throw new ArgumentNullException("waveSource"); } return WaveToSampleBase.CreateConverter(waveSource); } public static SynchronizedWaveSource<TAudioSource, T> Synchronized<TAudioSource, T>(this TAudioSource audioSource) where TAudioSource : class, IReadableAudioSource<T> { if (audioSource == null) { throw new ArgumentNullException("audioSource"); } return new SynchronizedWaveSource<TAudioSource, T>(audioSource); } } public interface IAudioSource : IDisposable { bool CanSeek { get; } WaveFormat WaveFormat { get; } long Position { get; set; } long Length { get; } } public interface IWaveSource : IReadableAudioSource<byte>, IAudioSource, IDisposable { } [StructLayout(LayoutKind.Sequential, Pack = 2)] public class WaveFormat : ICloneable, IEquatable<WaveFormat> { private AudioEncoding _encoding; private short _channels; private int _sampleRate; private int _bytesPerSecond; private short _blockAlign; private short _bitsPerSample; private short _extraSize; public virtual int Channels { get { return _channels; } protected internal set { _channels = (short)value; UpdateProperties(); } } public virtual int SampleRate { get { return _sampleRate; } protected internal set { _sampleRate = value; UpdateProperties(); } } public virtual int BytesPerSecond { get { return _bytesPerSecond; } protected internal set { _bytesPerSecond = value; } } public virtual int BlockAlign { get { return _blockAlign; } protected internal set { _blockAlign = (short)value; } } public virtual int BitsPerSample { get { return _bitsPerSample; } protected internal set { _bitsPerSample = (short)value; UpdateProperties(); } } public virtual int ExtraSize { get { return _extraSize; } protected internal set { _extraSize = (short)value; } } public virtual int BytesPerSample => BitsPerSample / 8; public virtual int BytesPerBlock => BytesPerSample * Channels; public virtual AudioEncoding WaveFormatTag { get { return _encoding; } protected internal set { _encoding = value; } } public WaveFormat() : this(44100, 16, 2) { } public WaveFormat(int sampleRate, int bits, int channels) : this(sampleRate, bits, channels, AudioEncoding.Pcm) { } public WaveFormat(int sampleRate, int bits, int channels, AudioEncoding encoding) : this(sampleRate, bits, channels, encoding, 0) { } public WaveFormat(int sampleRate, int bits, int channels, AudioEncoding encoding, int extraSize) { if (sampleRate < 1) { throw new ArgumentOutOfRangeException("sampleRate"); } if (bits < 0) { throw new ArgumentOutOfRangeException("bits"); } if (channels < 1) { throw new ArgumentOutOfRangeException("channels", "Number of channels has to be bigger than 0."); } _sampleRate = sampleRate; _bitsPerSample = (short)bits; _channels = (short)channels; _encoding = encoding; _extraSize = (short)extraSize; UpdateProperties(); } public long MillisecondsToBytes(double milliseconds) { long num = (long)((double)BytesPerSecond / 1000.0 * milliseconds); return num - num % BlockAlign; } public double BytesToMilliseconds(long bytes) { bytes -= bytes % BlockAlign; return (double)bytes / (double)BytesPerSecond * 1000.0; } public virtual bool Equals(WaveFormat other) { if (Channels == other.Channels && SampleRate == other.SampleRate && BytesPerSecond == other.BytesPerSecond && BlockAlign == other.BlockAlign && BitsPerSample == other.BitsPerSample && ExtraSize == other.ExtraSize) { return WaveFormatTag == other.WaveFormatTag; } return false; } public override string ToString() { return GetInformation().ToString(); } public virtual object Clone() { return MemberwiseClone(); } internal virtual void SetWaveFormatTagInternal(AudioEncoding waveFormatTag) { WaveFormatTag = waveFormatTag; } internal virtual void SetBitsPerSampleAndFormatProperties(int bitsPerSample) { BitsPerSample = bitsPerSample; UpdateProperties(); } protected internal virtual void UpdateProperties() { BlockAlign = BitsPerSample / 8 * Channels; BytesPerSecond = BlockAlign * SampleRate; } [DebuggerStepThrough] private StringBuilder GetInformation() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("ChannelsAvailable: " + Channels); stringBuilder.Append("|SampleRate: " + SampleRate); stringBuilder.Append("|Bps: " + BytesPerSecond); stringBuilder.Append("|BlockAlign: " + BlockAlign); stringBuilder.Append("|BitsPerSample: " + BitsPerSample); stringBuilder.Append("|Encoding: " + _encoding); return stringBuilder; } } } namespace CSCore.DSP { public class DmoResampler : WaveAggregatorBase { internal MediaBuffer InputBuffer; internal object LockObj = new object(); internal DmoOutputDataBuffer OutputBuffer; internal WaveFormat Outputformat; private readonly bool _ignoreBaseStreamPosition; internal decimal Ratio; internal WMResampler Resampler; private bool _disposed; private int _quality = 30; private byte[] _readBuffer; private long _position; public override WaveFormat WaveFormat => Outputformat; public override long Position { get { if (_ignoreBaseStreamPosition) { return _position; } return InputToOutput(base.Position); } set { base.Position = OutputToInput(value); if (_ignoreBaseStreamPosition) { _position = InputToOutput(base.Position); } } } public override long Length => InputToOutput(base.Length); public int Quality { get { return _quality; } set { if (value < 1 || value > 60) { throw new ArgumentOutOfRangeException("value"); } _quality = value; using (Resampler.MediaObject.Lock()) { Resampler.ResamplerProps.SetHalfFilterLength(value); } } } private static WaveFormat GetWaveFormatWithChangedSampleRate(IWaveSource source, int destSampleRate) { if (source == null) { throw new ArgumentNullException("source"); } WaveFormat waveFormat = (WaveFormat)source.WaveFormat.Clone(); waveFormat.SampleRate = destSampleRate; return waveFormat; } public DmoResampler(IWaveSource source, int destinationSampleRate) : this(source, GetWaveFormatWithChangedSampleRate(source, destinationSampleRate)) { } public DmoResampler(IWaveSource source, WaveFormat outputFormat) : this(source, outputFormat, ignoreBaseStreamPosition: true) { } public DmoResampler(IWaveSource source, WaveFormat outputFormat, bool ignoreBaseStreamPosition) : base(source) { if (source == null) { throw new ArgumentNullException("source"); } if (outputFormat == null) { throw new ArgumentNullException("outputFormat"); } Initialize(source.WaveFormat, outputFormat); Outputformat = outputFormat; _ignoreBaseStreamPosition = ignoreBaseStreamPosition; } internal void Initialize(WaveFormat inputformat, WaveFormat outputformat) { Ratio = (decimal)outputformat.BytesPerSecond / (decimal)inputformat.BytesPerSecond; lock (LockObj) { Resampler = new WMResampler(); MediaObject mediaObject = Resampler.MediaObject; if (!mediaObject.SupportsInputFormat(0, inputformat)) { throw new NotSupportedException("Inputformat not supported."); } mediaObject.SetInputType(0, inputformat); if (!mediaObject.SupportsOutputFormat(0, outputformat)) { throw new NotSupportedException("Outputformat not supported."); } mediaObject.SetOutputType(0, outputformat); InputBuffer = new MediaBuffer(inputformat.BytesPerSecond / 2); OutputBuffer = new DmoOutputDataBuffer(outputformat.BytesPerSecond / 2); } } public override int Read(byte[] buffer, int offset, int count) { lock (LockObj) { int num = 0; while (num < count) { MediaObject mediaObject = Resampler.MediaObject; if (mediaObject.IsReadyForInput(0)) { int num2 = (int)OutputToInput(count - num); _readBuffer = _readBuffer.CheckBuffer(num2); int num3 = base.Read(_readBuffer, 0, num2); if (num3 <= 0 || _disposed) { break; } if (InputBuffer.MaxLength < num3) { InputBuffer.Dispose(); InputBuffer = new MediaBuffer(num3); } InputBuffer.Write(_readBuffer, 0, num3); mediaObject.ProcessInput(0, InputBuffer); OutputBuffer.Reset(); MediaBuffer mediaBuffer = (MediaBuffer)OutputBuffer.Buffer; if (mediaBuffer.MaxLength < count) { mediaBuffer.Dispose(); OutputBuffer.Buffer = new MediaBuffer(count); } OutputBuffer.Buffer.SetLength(0); mediaObject.ProcessOutput(ProcessOutputFlags.None, new DmoOutputDataBuffer[1] { OutputBuffer }, 1); if (OutputBuffer.Length > 0) { OutputBuffer.Read(buffer, offset + num); num += OutputBuffer.Length; } } } if (_ignoreBaseStreamPosition) { _position += num; } return num; } } internal long InputToOutput(long position) { long num = (long)((decimal)position * Ratio); return num - num % Outputformat.BlockAlign; } internal long OutputToInput(long position) { long num = (long)((decimal)position / Ratio); return num - num % BaseSource.WaveFormat.BlockAlign; } public void DisposeResamplerOnly() { bool disposeBaseSource = base.DisposeBaseSource; base.DisposeBaseSource = false; Dispose(); base.DisposeBaseSource = disposeBaseSource; } protected override void Dispose(bool disposing) { if (!disposing) { base.DisposeBaseSource = false; } base.Dispose(disposing); DisposeAndReset(ref Resampler); OutputBuffer.Dispose(); DisposeAndReset(ref InputBuffer); _readBuffer = null; _disposed = true; } private void DisposeAndReset<T>(ref T obj) where T : class, IDisposable { if (obj != null) { try { obj.Dispose(); } catch (ObjectDisposedException) { } obj = null; } } } } namespace CSCore { public interface ISampleSource : IReadableAudioSource<float>, IAudioSource, IDisposable { } [StructLayout(LayoutKind.Sequential, Pack = 2)] public class WaveFormatExtensible : WaveFormat { internal const int WaveFormatExtensibleExtraSize = 22; private short _samplesUnion; private ChannelMask _channelMask; private Guid _subFormat; public int ValidBitsPerSample { get { return _samplesUnion; } protected internal set { _samplesUnion = (short)value; } } public int SamplesPerBlock { get { return _samplesUnion; } protected internal set { _samplesUnion = (short)value; } } public ChannelMask ChannelMask { get { return _channelMask; } protected internal set { _channelMask = value; } } public Guid SubFormat { get { return _subFormat; } protected internal set { _subFormat = value; } } public static Guid SubTypeFromWaveFormat(WaveFormat waveFormat) { if (waveFormat == null) { throw new ArgumentNullException("waveFormat"); } if (waveFormat is WaveFormatExtensible) { return ((WaveFormatExtensible)waveFormat).SubFormat; } return AudioSubTypes.SubTypeFromEncoding(waveFormat.WaveFormatTag); } internal WaveFormatExtensible() { } public WaveFormatExtensible(int sampleRate, int bits, int channels, Guid subFormat) : base(sampleRate, bits, channels, AudioEncoding.Extensible, 22) { _samplesUnion = (short)bits; _subFormat = SubTypeFromWaveFormat(this); int num = 0; for (int i = 0; i < channels; i++) { num |= 1 << i; } _channelMask = (ChannelMask)num; _subFormat = subFormat; } public WaveFormatExtensible(int sampleRate, int bits, int channels, Guid subFormat, ChannelMask channelMask) : this(sampleRate, bits, channels, subFormat) { Array values = Enum.GetValues(typeof(ChannelMask)); int num = 0; for (int i = 0; i < values.Length; i++) { if ((channelMask & (ChannelMask)values.GetValue(i)) == (ChannelMask)values.GetValue(i)) { num++; } } if (channels != num) { throw new ArgumentException("Channels has to equal the set flags in the channelmask."); } _channelMask = channelMask; } public WaveFormat ToWaveFormat() { return new WaveFormat(SampleRate, BitsPerSample, Channels, AudioSubTypes.EncodingFromSubType(SubFormat)); } public override object Clone() { return MemberwiseClone(); } internal override void SetWaveFormatTagInternal(AudioEncoding waveFormatTag) { SubFormat = AudioSubTypes.SubTypeFromEncoding(waveFormatTag); } [DebuggerStepThrough] public override string ToString() { StringBuilder stringBuilder = new StringBuilder(base.ToString()); stringBuilder.Append("|SubFormat: " + SubFormat); stringBuilder.Append("|ChannelMask: " + ChannelMask); return stringBuilder.ToString(); } } } namespace CSCore.DSP { public class ChannelMatrix { private static class Factory { private class FactoryEntry { public ChannelMask Input { get; set; } public ChannelMask Output { get; set; } public ChannelMatrix Matrix { get; set; } public FactoryEntry(ChannelMask input, ChannelMask output, ChannelMatrix matrix) { Input = input; Output = output; Matrix = matrix; } } private static readonly FactoryEntry[] FactoryEntries = new FactoryEntry[18] { new FactoryEntry(ChannelMask.SpeakerFrontCenter, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, MonoToStereoMatrix), new FactoryEntry(ChannelMask.SpeakerFrontCenter, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight, MonoToFiveDotOneSurroundWithRear), new FactoryEntry(ChannelMask.SpeakerFrontCenter, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, MonoToFiveDotOneSurroundWithSide), new FactoryEntry(ChannelMask.SpeakerFrontCenter, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, MonoToSevenDotOneSurround), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, ChannelMask.SpeakerFrontCenter, StereoToMonoMatrix), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight, StereoToFiveDotOneSurroundWithRear), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, StereoToFiveDotOneSurroundWithSide), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, StereoToSevenDotOneSurround), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight, ChannelMask.SpeakerFrontCenter, FiveDotOneSurroundWithRearToMono), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, FiveDotOneSurroundWithRearToStereo), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, FiveDotOneSurroundWithRearToSevenDotOne), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, ChannelMask.SpeakerFrontCenter, FiveDotOneSurroundWithSideToMono), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, FiveDotOneSurroundWithSideToStereo), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, FiveDotOneSurroundWithSideToSevenDotOne), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, ChannelMask.SpeakerFrontCenter, SevenDotOneSurroundToMono), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, SevenDotOneSurroundToStereo), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight, SevenDotOneSurroundToFiveDotOneSurroundWithRear), new FactoryEntry(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, SevenDotOneSurroundToFiveDotOneSurroundWithSide) }; public static ChannelMatrix GetMatrix(ChannelMask from, ChannelMask to) { if (from == to) { throw new ArgumentException("from must not equal to."); } FactoryEntry factoryEntry = FactoryEntries.FirstOrDefault((FactoryEntry x) => x.Input == from && x.Output == to); if (factoryEntry == null) { throw new KeyNotFoundException("Could not find a channel matrix for specified channelmasks."); } return factoryEntry.Matrix; } } public static readonly ChannelMatrix StereoToFiveDotOneSurroundWithRear; public static readonly ChannelMatrix FiveDotOneSurroundWithRearToStereo; public static readonly ChannelMatrix StereoToFiveDotOneSurroundWithSide; public static readonly ChannelMatrix FiveDotOneSurroundWithSideToStereo; public static readonly ChannelMatrix StereoToSevenDotOneSurround; public static readonly ChannelMatrix SevenDotOneSurroundToStereo; public static readonly ChannelMatrix MonoToFiveDotOneSurroundWithRear; public static readonly ChannelMatrix FiveDotOneSurroundWithRearToMono; public static readonly ChannelMatrix MonoToFiveDotOneSurroundWithSide; public static readonly ChannelMatrix FiveDotOneSurroundWithSideToMono; public static readonly ChannelMatrix MonoToSevenDotOneSurround; public static readonly ChannelMatrix SevenDotOneSurroundToMono; public static readonly ChannelMatrix StereoToMonoMatrix; public static readonly ChannelMatrix MonoToStereoMatrix; public static readonly ChannelMatrix FiveDotOneSurroundWithRearToSevenDotOne; public static readonly ChannelMatrix SevenDotOneSurroundToFiveDotOneSurroundWithRear; public static readonly ChannelMatrix FiveDotOneSurroundWithSideToSevenDotOne; public static readonly ChannelMatrix SevenDotOneSurroundToFiveDotOneSurroundWithSide; private readonly ChannelMask _inputMask; private readonly ChannelMatrixElement[,] _matrix; private readonly ChannelMask _outputMask; public ChannelMask InputMask => _inputMask; public ChannelMask OutputMask => _outputMask; public int Height => _matrix.GetLength(0); public int Width => _matrix.GetLength(1); public int InputChannelCount => Height; public int OutputChannelCount => Width; public ChannelMatrixElement this[int input, int output] { get { return _matrix[input, output]; } set { _matrix[input, output] = value; } } public static ChannelMatrix GetMatrix(ChannelMask from, ChannelMask to) { return Factory.GetMatrix(from, to); } public static ChannelMatrix GetMatrix(WaveFormat from, WaveFormat to) { if (from == null) { throw new ArgumentNullException("from"); } if (to == null) { throw new ArgumentNullException("to"); } if (TryExtractChannelMask(from, out var channelMask) && TryExtractChannelMask(to, out var channelMask2)) { return GetMatrix(channelMask, channelMask2); } return null; } private static bool TryExtractChannelMask(WaveFormat waveFormat, out ChannelMask channelMask) { channelMask = (ChannelMask)0; if (waveFormat is WaveFormatExtensible waveFormatExtensible) { channelMask = waveFormatExtensible.ChannelMask; } else if (waveFormat.Channels == 1) { channelMask = ChannelMask.SpeakerFrontCenter; } else { if (waveFormat.Channels != 2) { return false; } channelMask = ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight; } return true; } internal WaveFormat BuildOutputWaveFormat(IAudioSource audioSource) { if (audioSource == null) { throw new ArgumentNullException("source"); } return new WaveFormatExtensible(audioSource.WaveFormat.SampleRate, audioSource.WaveFormat.BitsPerSample, OutputChannelCount, WaveFormatExtensible.SubTypeFromWaveFormat(audioSource.WaveFormat), OutputMask); } static ChannelMatrix() { StereoToFiveDotOneSurroundWithRear = new ChannelMatrix(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight); StereoToFiveDotOneSurroundWithRear.SetMatrix(new float[2, 6] { { 0.314f, 0f, 0.222f, 0.031f, 0.268f, 0.164f }, { 0f, 0.314f, 0.222f, 0.031f, 0.164f, 0.268f } }); FiveDotOneSurroundWithRearToStereo = StereoToFiveDotOneSurroundWithRear.Flip(); StereoToFiveDotOneSurroundWithSide = new ChannelMatrix(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight); StereoToFiveDotOneSurroundWithSide.SetMatrix(new float[2, 6] { { 0.32f, 0f, 0.226f, 0.032f, 0.292f, 0.13f }, { 0f, 0.32f, 0.226f, 0.032f, 0.13f, 0.292f } }); FiveDotOneSurroundWithSideToStereo = StereoToFiveDotOneSurroundWithSide.Flip(); StereoToSevenDotOneSurround = new ChannelMatrix(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight); StereoToSevenDotOneSurround.SetMatrix(new float[2, 8] { { 0.222f, 0f, 0.157f, 0.022f, 0.189f, 0.116f, 0.203f, 0.09f }, { 0f, 0.222f, 0.157f, 0.022f, 0.116f, 0.189f, 0.09f, 0.203f } }); SevenDotOneSurroundToStereo = StereoToSevenDotOneSurround.Flip(); MonoToFiveDotOneSurroundWithRear = new ChannelMatrix(ChannelMask.SpeakerFrontCenter, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight); MonoToFiveDotOneSurroundWithRear.SetMatrix(new float[1, 6] { { 0.192f, 0.192f, 0.192f, 0.038f, 0.192f, 0.192f } }); FiveDotOneSurroundWithRearToMono = MonoToFiveDotOneSurroundWithRear.Flip(); MonoToFiveDotOneSurroundWithSide = new ChannelMatrix(ChannelMask.SpeakerFrontCenter, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight); MonoToFiveDotOneSurroundWithSide.SetMatrix(new float[1, 6] { { 0.192f, 0.192f, 0.192f, 0.038f, 0.192f, 0.192f } }); FiveDotOneSurroundWithSideToMono = MonoToFiveDotOneSurroundWithSide.Flip(); MonoToSevenDotOneSurround = new ChannelMatrix(ChannelMask.SpeakerFrontCenter, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight); MonoToSevenDotOneSurround.SetMatrix(new float[1, 8] { { 0.139f, 0.139f, 0.139f, 0.028f, 0.139f, 0.139f, 0.139f, 0.139f } }); SevenDotOneSurroundToMono = MonoToSevenDotOneSurround.Flip(); FiveDotOneSurroundWithRearToSevenDotOne = new ChannelMatrix(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight); FiveDotOneSurroundWithRearToSevenDotOne.SetMatrix(new float[6, 8] { { 0.518f, 0f, 0f, 0f, 0f, 0f, 0.189f, 0f }, { 0f, 0.518f, 0f, 0f, 0f, 0f, 0f, 0.189f }, { 0f, 0f, 0.518f, 0f, 0f, 0f, 0f, 0f }, { 0f, 0f, 0f, 0.518f, 0f, 0f, 0f, 0f }, { 0f, 0f, 0f, 0f, 0.518f, 0f, 0.482f, 0f }, { 0f, 0f, 0f, 0f, 0f, 0.518f, 0f, 0.482f } }); SevenDotOneSurroundToFiveDotOneSurroundWithRear = FiveDotOneSurroundWithRearToSevenDotOne.Flip(); FiveDotOneSurroundWithSideToSevenDotOne = new ChannelMatrix(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight | ChannelMask.SpeakerFrontCenter | ChannelMask.SpeakerLowFrequency | ChannelMask.SpeakerBackLeft | ChannelMask.SpeakerBackRight | ChannelMask.SpeakerSideLeft | ChannelMask.SpeakerSideRight); FiveDotOneSurroundWithSideToSevenDotOne.SetMatrix(new float[6, 8] { { 0.447f, 0f, 0f, 0f, 0f, 0f, 0f, 0f }, { 0f, 0.447f, 0f, 0f, 0f, 0f, 0f, 0f }, { 0f, 0f, 0.447f, 0f, 0f, 0f, 0f, 0f }, { 0f, 0f, 0f, 0.447f, 0f, 0f, 0f, 0f }, { 0f, 0f, 0f, 0f, 0.429f, 0.124f, 0.447f, 0f }, { 0f, 0f, 0f, 0f, 0.124f, 0.429f, 0f, 0.447f } }); SevenDotOneSurroundToFiveDotOneSurroundWithSide = FiveDotOneSurroundWithSideToSevenDotOne.Flip(); StereoToMonoMatrix = new ChannelMatrix(ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight, ChannelMask.SpeakerFrontCenter); StereoToMonoMatrix.SetMatrix(new float[2, 1] { { 0.5f }, { 0.5f } }); MonoToStereoMatrix = new ChannelMatrix(ChannelMask.SpeakerFrontCenter, ChannelMask.SpeakerFrontLeft | ChannelMask.SpeakerFrontRight); MonoToStereoMatrix.SetMatrix(new float[1, 2] { { 1f, 1f } }); } public ChannelMatrix(ChannelMask inputMask, ChannelMask outputMask) { _inputMask = inputMask; _outputMask = outputMask; if (inputMask <= (ChannelMask)0) { throw new ArgumentException("Invalid inputMask"); } if (outputMask <= (ChannelMask)0) { throw new ArgumentException("Invalid outputMask"); } _matrix = new ChannelMatrixElement[GetValuesOfChannelMask(inputMask).Length, GetValuesOfChannelMask(outputMask).Length]; for (int i = 0; i < Width; i++) { for (int j = 0; j < Height; j++) { _matrix[j, i] = new ChannelMatrixElement(GetValuesOfChannelMask(inputMask)[j], GetValuesOfChannelMask(outputMask)[i]); } } } public void SetMatrix(float[,] matrix) { if (matrix == null) { throw new ArgumentException("matrix"); } if (matrix.GetLength(1) != Width) { throw new ArgumentException("Matrix has to have a width of " + Width); } if (matrix.GetLength(0) != Height) { throw new ArgumentException("Matrix has to have a height of " + Height); } for (int i = 0; i < Width; i++) { for (int j = 0; j < Height; j++) { this[j, i].Value = matrix[j, i]; } } } public float[] GetOneDimensionalMatrix() { List<float> list = new List<float>(); for (int i = 0; i < Width; i++) { for (int j = 0; j < Height; j++) { list.Add(this[j, i].Value); } } return list.ToArray(); } public ChannelMatrix Flip() { ChannelMatrix channelMatrix = new ChannelMatrix(OutputMask, InputMask); for (int i = 0; i < OutputChannelCount; i++) { for (int j = 0; j < InputChannelCount; j++) { ChannelMatrixElement channelMatrixElement = this[j, i]; channelMatrix[i, j] = new ChannelMatrixElement(channelMatrixElement.OutputChannel, channelMatrixElement.InputChannel) { Value = channelMatrixElement.Value }; } } return channelMatrix; } private static ChannelMask[] GetValuesOfChannelMask(ChannelMask channelMask) { Array values = Enum.GetValues(typeof(ChannelMask)); List<ChannelMask> list = new List<ChannelMask>(); for (int i = 0; i < values.Length; i++) { if ((channelMask & (ChannelMask)values.GetValue(i)) == (ChannelMask)values.GetValue(i)) { list.Add((ChannelMask)values.GetValue(i)); } } return list.ToArray(); } } } namespace CSCore.Streams { public sealed class MonoToStereoSource : SampleAggregatorBase { private float[] _buffer; private readonly WaveFormat _waveFormat; public override long Position { get { return base.Position * 2; } set { value -= value % WaveFormat.BlockAlign; base.Position = value / 2; } } public override long Length => base.Length * 2; public override WaveFormat WaveFormat => _waveFormat; public MonoToStereoSource(ISampleSource source) : base(source) { if (source == null) { throw new ArgumentNullException("source"); } if (source.WaveFormat.Channels != 1) { throw new ArgumentException("The WaveFormat of the source has be a mono format (one channel).", "source"); } _waveFormat = new WaveFormat(source.WaveFormat.SampleRate, 32, 2, AudioEncoding.IeeeFloat); } public override int Read(float[] buffer, int offset, int count) { int num = count / 2; _buffer = _buffer.CheckBuffer(num); int num2 = offset; int num3 = base.Read(_buffer, 0, num); for (int i = 0; i < num3; i++) { buffer[num2++] = _buffer[i]; buffer[num2++] = _buffer[i]; } return num3 * 2; } protected override void Dispose(bool disposing) { base.Dispose(disposing); _buffer = null; } } } namespace CSCore.DSP { public class DmoChannelResampler : DmoResampler { private readonly ChannelMatrix _channelMatrix; public ChannelMatrix ChannelMatrix => _channelMatrix; public DmoChannelResampler(IWaveSource source, ChannelMatrix channelMatrix) : this(source, channelMatrix, source.WaveFormat.SampleRate) { } public DmoChannelResampler(IWaveSource source, ChannelMatrix channelMatrix, WaveFormat outputFormat) : base(source, outputFormat) { if (source == null) { throw new ArgumentNullException("source"); } if (channelMatrix == null) { throw new ArgumentNullException("channelMatrix"); } if (outputFormat == null) { throw new ArgumentNullException("outputFormat"); } if (source.WaveFormat.Channels != channelMatrix.InputChannelCount) { throw new ArgumentException("The number of channels of the source has to be equal to the number of input channels specified by the channelMatrix."); } WaveFormatExtensible inputformat = new WaveFormatExtensible(source.WaveFormat.SampleRate, source.WaveFormat.BitsPerSample, source.WaveFormat.Channels, WaveFormatExtensible.SubTypeFromWaveFormat(source.WaveFormat), channelMatrix.InputMask); Outputformat = new WaveFormatExtensible(outputFormat.SampleRate, outputFormat.BitsPerSample, outputFormat.Channels, WaveFormatExtensible.SubTypeFromWaveFormat(outputFormat), channelMatrix.OutputMask); Initialize(inputformat, Outputformat); _channelMatrix = channelMatrix; CommitChannelMatrixChanges(); } public DmoChannelResampler(IWaveSource source, ChannelMatrix channelMatrix, int destinationSampleRate) : this(source, channelMatrix, GetOutputWaveFormat(source, destinationSampleRate, channelMatrix)) { } private static WaveFormat GetOutputWaveFormat(IWaveSource source, int sampleRate, ChannelMatrix channelMatrix) { if (source == null) { throw new ArgumentNullException("source"); } if (channelMatrix == null) { throw new ArgumentNullException("channelMatrix"); } WaveFormat waveFormat = channelMatrix.BuildOutputWaveFormat(source); waveFormat.SampleRate = sampleRate; return waveFormat; } public void CommitChannelMatrixChanges() { using (Resampler.MediaObject.Lock()) { Resampler.MediaObject.SetOutputType(0, Resampler.MediaObject.GetOutputCurrentType(0), SetTypeFlags.None); Resampler.ResamplerProps.SetUserChannelMtx(_channelMatrix.GetOneDimensionalMatrix()); } } } } namespace CSCore.Streams { public class StereoToMonoSource : SampleAggregatorBase { private readonly WaveFormat _waveFormat; private float[] _buffer; public override long Position { get { return BaseSource.Position / 2; } set { value -= value % WaveFormat.BlockAlign; BaseSource.Position = value * 2; } } public override long Length => BaseSource.Length / 2; public override WaveFormat WaveFormat => _waveFormat; public StereoToMonoSource(ISampleSource source) : base(source) { if (source == null) { throw new ArgumentNullException("source"); } if (source.WaveFormat.Channels != 2) { throw new ArgumentException("The WaveFormat of the source has be a stereo format (two channels).", "source"); } _waveFormat = new WaveFormat(source.WaveFormat.SampleRate, 32, 1, AudioEncoding.IeeeFloat); } public unsafe override int Read(float[] buffer, int offset, int count) { _buffer = _buffer.CheckBuffer(count * 2); int num = BaseSource.Read(_buffer, 0, count * 2); fixed (float* ptr = buffer) { float* ptr2 = ptr + offset; for (int i = 0; i < num - 1; i += 2) { *(ptr2++) = (_buffer[i] + _buffer[i + 1]) / 2f; } } return num / 2; } protected override void Dispose(bool disposing) { base.Dispose(disposing); _buffer = null; } } public class LoopStream : WaveAggregatorBase { private bool _enableLoop = true; private bool _raisedStreamFinishedEvent; public bool EnableLoop { get { return _enableLoop; } set { _enableLoop = value; } } public event EventHandler StreamFinished; public LoopStream(IWaveSource source) : base(source) { } public override int Read(byte[] buffer, int offset, int count) { int i; int num; for (i = base.Read(buffer, offset, count); i < count; i += num) { num = base.Read(buffer, offset + i, count - i); if (num == 0) { EventHandler streamFinished = this.StreamFinished; if (streamFinished != null && !_raisedStreamFinishedEvent) { streamFinished(this, EventArgs.Empty); _raisedStreamFinishedEvent = true; } if (!EnableLoop) { break; } Position = 0L; } else { _raisedStreamFinishedEvent = false; } } return i; } } } namespace CSCore.Streams.SampleConverter { public class SampleToPcm8 : SampleToWaveBase { public SampleToPcm8(ISampleSource source) : base(source, 8, AudioEncoding.Pcm) { } public override int Read(byte[] buffer, int offset, int count) { Buffer = Buffer.CheckBuffer(count); int num = Source.Read(Buffer, 0, count); for (int i = offset; i < num; i++) { byte b = (byte)((Buffer[i] + 1f) * 128f); buffer[i] = b; } return num; } } public class SampleToPcm16 : SampleToWaveBase { public SampleToPcm16(ISampleSource source) : base(source, 16, AudioEncoding.Pcm) { if (source == null) { throw new ArgumentNullException("source"); } } public override int Read(byte[] buffer, int offset, int count) { Buffer = Buffer.CheckBuffer(count / 2); int num = Source.Read(Buffer, 0, count / 2); int num2 = offset; for (int i = 0; i < num; i++) { short value = (short)(Buffer[i] * 32767f); byte[] bytes = BitConverter.GetBytes(value); buffer[num2++] = bytes[0]; buffer[num2++] = bytes[1]; } return num * 2; } } public class SampleToPcm24 : SampleToWaveBase { public SampleToPcm24(ISampleSource source) : base(source, 24, AudioEncoding.Pcm) { if (source == null) { throw new ArgumentNullException("source"); } } public unsafe override int Read(byte[] buffer, int offset, int count) { int num = count / 3; Buffer = Buffer.CheckBuffer(num); int num2 = Source.Read(Buffer, 0, num); int num3 = offset; for (int i = 0; i < num2; i++) { uint num4 = (uint)(Buffer[i] * 8388608f); byte* ptr = (byte*)(&num4); buffer[num3++] = *ptr; buffer[num3++] = ptr[1]; buffer[num3++] = ptr[2]; } return num2 * 3; } } public class SampleToIeeeFloat32 : SampleToWaveBase { public SampleToIeeeFloat32(ISampleSource source) : base(source, 32, AudioEncoding.IeeeFloat) { if (source == null) { throw new ArgumentNullException("source"); } } public override int Read(byte[] buffer, int offset, int count) { Buffer = Buffer.CheckBuffer(count / 4); int num = Source.Read(Buffer, offset / 4, count / 4); System.Buffer.BlockCopy(Buffer, 0, buffer, offset, num * 4); return num * 4; } } public abstract class WaveToSampleBase : ISampleSource, IReadableAudioSource<float>, IAudioSource, IDisposable { private readonly WaveFormat _waveFormat; protected internal IWaveSource Source; protected internal byte[] Buffer; public WaveFormat WaveFormat => _waveFormat; public long Position { get { if (!CanSeek) { return 0L; } return Source.Position / Source.WaveFormat.BytesPerSample; } set { if (CanSeek) { value -= value % WaveFormat.BlockAlign; Source.Position = value * Source.WaveFormat.BytesPerSample; return; } throw new InvalidOperationException(); } } public long Length { get { if (!CanSeek || Source.Length == 0L) { return 0L; } return Source.Length / Source.WaveFormat.BytesPerSample; } } public bool CanSeek => Source.CanSeek; protected WaveToSampleBase(IWaveSource source) { if (source == null) { throw new ArgumentNullException("source"); } Source = source; _waveFormat = (WaveFormat)source.WaveFormat.Clone(); _waveFormat.BitsPerSample = 32; _waveFormat.SetWaveFormatTagInternal(AudioEncoding.IeeeFloat); } public abstract int Read(float[] buffer, int offset, int count); public void Dispose() { Dispose(disposing: true); } protected virtual void Dispose(bool disposing) { if (Source != null) { Source.Dispose(); Source = null; } } ~WaveToSampleBase() { Dispose(disposing: false); } public static ISampleSource CreateConverter(IWaveSource source) { if (source == null) { throw new ArgumentNullException("source"); } int bitsPerSample = source.WaveFormat.BitsPerSample; if (source.WaveFormat.IsPCM()) { return bitsPerSample switch { 8 => new Pcm8BitToSample(source), 16 => new Pcm16BitToSample(source), 24 => new Pcm24BitToSample(source), 32 => new Pcm32BitToSample(source), _ => throw new NotSupportedException("Waveformat is not supported. Invalid BitsPerSample value."), }; } if (source.WaveFormat.IsIeeeFloat() && bitsPerSample == 32) { return new IeeeFloatToSample(source); } throw new NotSupportedException("Waveformat is not supported. Invalid WaveformatTag."); } } } namespace CSCore.Streams { public class SynchronizedWaveSource<TBaseSource, T> : IAggregator<T, TBaseSource>, IReadableAudioSource<T>, IAudioSource, IDisposable where TBaseSource : class, IReadableAudioSource<T> { private readonly object _lockObj = new object(); private TBaseSource _baseSource; private bool _disposed; public WaveFormat WaveFormat { get { lock (_lockObj) { return BaseSource.WaveFormat; } } } public long Position { get { lock (_lockObj) { return BaseSource.Position; } } set { lock (_lockObj) { value -= value % WaveFormat.BlockAlign; BaseSource.Position = value; } } } public long Length { get { lock (_lockObj) { return BaseSource.Length; } } } public bool CanSeek { get { lock (_lockObj) { return BaseSource.CanSeek; } } } public TBaseSource BaseSource { get { lock (_lockObj) { return _baseSource; } } set { lock (_lockObj) { if (value == null) { throw new ArgumentNullException("value"); } _baseSource = value; } } } public SynchronizedWaveSource(TBaseSource baseWaveSource) { BaseSource = baseWaveSource; } public int Read(T[] buffer, int offset, int count) { lock (_lockObj) { return BaseSource.Read(buffer, offset, count); } } public static explicit operator TBaseSource(SynchronizedWaveSource<TBaseSource, T> synchronizedWaveSource) { if (synchronizedWaveSource == null) { throw new ArgumentNullException("synchronizedWaveSource"); } return synchronizedWaveSource.BaseSource; } protected void Dispose(bool disposing) { lock (_lockObj) { if (BaseSource != null) { BaseSource.Dispose(); } _baseSource = null; } } public void Dispose() { lock (_lockObj) { if (!_disposed) { _disposed = true; Dispose(disposing: true); GC.SuppressFinalize(this); } } } ~SynchronizedWaveSource() { lock (_lockObj) { Dispose(disposing: false); } } } } namespace CSCore { public interface IReadableAudioSource<in T> : IAudioSource, IDisposable { int Read(T[] buffer, int offset, int count); } public interface IWriteable { void Write(byte[] buffer, int offset, int count); } public enum MmResult { NoError = 0, Error = 1, BadDevice = 2, NotEnabled = 3, Allocated = 4, InvalidHandle = 5, NoDriver = 6, NoMemory = 7, NotSupported = 8, BadErrorNumber = 9, InvalidFlag = 10, InvalidParameter = 11, HandleBusy = 12, InvalidAlias = 13, BadDatabase = 14, KeyNotFound = 15, ReadError = 16, WriteError = 17, DeleteError = 18, ValueNotFound = 19, NoDriverCallback = 20, MoreData = 21, BadFormat = 32, StillPlaying = 33, Unprepared = 34, Synchronous = 35 } public class StoppedEventArgs : EventArgs { private readonly Exception _exception; public virtual bool HasError => _exception != null; public virtual Exception Exception => _exception; public StoppedEventArgs() : this(null) { } public StoppedEventArgs(Exception exception) { _exception = exception; } } public abstract class TimeConverter { internal class _WaveSourceTimeConverter : TimeConverter { public override long ToRawElements(WaveFormat waveFormat, TimeSpan timeSpan) { return waveFormat.MillisecondsToBytes(timeSpan.TotalMilliseconds); } public override TimeSpan ToTimeSpan(WaveFormat waveFormat, long rawElements) { return TimeSpan.FromMilliseconds(waveFormat.BytesToMilliseconds(rawElements)); } } internal class _SampleSourceTimeConverter : TimeConverter { public override long ToRawElements(WaveFormat waveFormat, TimeSpan timeSpan) { return waveFormat.MillisecondsToBytes(timeSpan.TotalMilliseconds) / waveFormat.BytesPerSample; } public override TimeSpan ToTimeSpan(WaveFormat waveFormat, long rawElements) { return TimeSpan.FromMilliseconds(waveFormat.BytesToMilliseconds(rawElements * waveFormat.BytesPerSample)); } } public static readonly TimeConverter SampleSourceTimeConverter = new _SampleSourceTimeConverter(); public static readonly TimeConverter WaveSourceTimeConverter = new _WaveSourceTimeConverter(); public abstract long ToRawElements(WaveFormat waveFormat, TimeSpan timeSpan); public abstract TimeSpan ToTimeSpan(WaveFormat waveFormat, long rawElements); } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, AllowMultiple = false)] public sealed class TimeConverterAttribute : Attribute { public Type TimeConverterType { get; private set; } public object[] Args { get; set; } public bool ForceNewInstance { get; set; } public TimeConverterAttribute(Type timeConverterType) { if ((object)timeConverterType == null) { throw new ArgumentNullException("timeConverterType"); } if (!typeof(TimeConverter).IsAssignableFrom(timeConverterType)) { throw new ArgumentException("Specified type is no time converter.", "timeConverterType"); } TimeConverterType = timeConverterType; } } public sealed class TimeConverterFactory { private class CacheItem { public TimeConverter TimeConverter { get; set; } public TimeConverterAttribute TimeConverterAttribute { get; set; } public bool CreateNewInstance { get; set; } public TimeConverter GetTimeConverter() { if (CreateNewInstance) { return (TimeConverter)Activator.CreateInstance(TimeConverterAttribute.TimeConverterType, TimeConverterAttribute.Args); } return TimeConverter; } } private static readonly TimeConverterFactory _instance = new TimeConverterFactory(); private readonly Dictionary<Type, TimeConverter> _timeConverters; private readonly Dictionary<Type, CacheItem> _cache; public static TimeConverterFactory Instance => _instance; private TimeConverterFactory() { _timeConverters = new Dictionary<Type, TimeConverter>(); _cache = new Dictionary<Type, CacheItem>(); RegisterTimeConverterForSourceType<IWaveSource>(TimeConverter.WaveSourceTimeConverter); RegisterTimeConverterForSourceType<ISampleSource>(TimeConverter.SampleSourceTimeConverter); } public void RegisterTimeConverterForSourceType<TSource>(TimeConverter timeConverter) where TSource : IAudioSource { if (timeConverter == null) { throw new ArgumentNullException("timeConverter"); } Type typeFromHandle = typeof(TSource); if (_timeConverters.ContainsKey(typeFromHandle)) { throw new ArgumentException("A timeconverter for the same source type got already registered."); } _timeConverters.Add(typeFromHandle, timeConverter); } public void UnregisterTimeConverter<TSource>() where TSource : IAudioSource { Type typeFromHandle = typeof(TSource); if (!_timeConverters.ContainsKey(typeFromHandle)) { throw new ArgumentException("There is no timeconverter registered for the specified source type."); } _timeConverters.Remove(typeFromHandle); } public TimeConverter GetTimeConverterForSource<TSource>(TSource source) where TSource : class, IAudioSource { if (source == null) { throw new ArgumentNullException("source"); } return GetTimeConverterForSourceType(source.GetType()); } public TimeConverter GetTimeConverterForSource<TSource>() where TSource : IAudioSource { return GetTimeConverterForSourceType(typeof(TSource)); } public TimeConverter GetTimeConverterForSourceType(Type sourceType) { if ((object)sourceType == null) { throw new ArgumentNullException("sourceType"); } if (!typeof(IAudioSource).IsAssignableFrom(sourceType)) { throw new ArgumentException("Specified type is no AudioSource.", "sourceType"); } if (_cache.ContainsKey(sourceType)) { return _cache[sourceType].GetTimeConverter(); } TimeConverterAttribute timeConverterAttribute = sourceType.GetCustomAttributes(typeof(TimeConverterAttribute), inherit: false).FirstOrDefault() as TimeConverterAttribute; TimeConverter timeConverter = null; try { if (timeConverterAttribute == null) { Type[] array = (from x in GetTypes(sourceType) where _timeConverters.ContainsKey(x) select x).ToArray(); if (array.Length == 1) { timeConverter = _timeConverters[array.First()]; return timeConverter; } if (array.Length == 0) { throw new ArgumentException("No registered time converter for the specified source type was found."); } throw new ArgumentException("Multiple possible time converters, for the specified source type, were found. Specify which time converter to use, through the TimeConverterAttribute."); } Type timeConverterType = timeConverterAttribute.TimeConverterType; timeConverter = (TimeConverter)Activator.CreateInstance(timeConverterType, timeConverterAttribute.Args); return timeConverter; } finally { if (timeConverter != null) { CacheItem value = ((timeConverterAttribute != null) ? new CacheItem { CreateNewInstance = timeConverterAttribute.ForceNewInstance, TimeConverterAttribute = timeConverterAttribute, TimeConverter = (timeConverterAttribute.ForceNewInstance ? null : timeConverter) } : new CacheItem { CreateNewInstance = false, TimeConverter = timeConverter }); _cache[sourceType] = value; } } } public void ClearCache() { _cache.Clear(); } private IEnumerable<Type> GetTypes(Type type) { if ((object)type.BaseType != typeof(object)) { return Enumerable.Repeat(type.BaseType, 1).Concat<Type>(type.GetInterfaces()).Concat(GetTypes(type.BaseType)) .Distinct(); } return type.GetInterfaces(); } } public interface ISampleAggregator : ISampleSource, IReadableAudioSource<float>, IAudioSource, IDisposable, IAggregator<float, ISampleSource> { } public interface IAggregator<in T, out TAggregator> : IReadableAudioSource<T>, IAudioSource, IDisposable where TAggregator : IReadableAudioSource<T> { TAggregator BaseSource { get; } } public interface IWaveAggregator : IWaveSource, IReadableAudioSource<byte>, IAudioSource, IDisposable, IAggregator<byte, IWaveSource> { } [Serializable] public class MmException : Exception { public MmResult Result { get; private set; } [Obsolete("Use the Function property instead.")] public string Target { get; private set; } public string Function => Target; public static void Try(MmResult result, string function) { if (result != 0) { throw new MmException(result, function); } } public MmException(MmResult result, string function) { Result = result; Target = function; } public MmException(SerializationInfo info, StreamingContext context) : base(info, context) { if (info == null) { throw new ArgumentNullException("info"); } Target = info.GetString("Target"); Result = (MmResult)info.GetInt32("Result"); } public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("Target", Target); info.AddValue("Result", (int)Result); } } public class SampleAggregatorBase : ISampleAggregator, ISampleSource, IReadableAudioSource<float>, IAudioSource, IDisposable, IAggregator<float, ISampleSource> { private bool _disposed; private ISampleSource _baseSource; public virtual WaveFormat WaveFormat => BaseSource.WaveFormat; public virtual long Position { get { if (!CanSeek) { return 0L; } return BaseSource.Position; } set { if (CanSeek) { value -= value % WaveFormat.BlockAlign; BaseSource.Position = value; return; } throw new InvalidOperationException("Underlying BaseSource is not readable."); } } public virtual long Length { get { if (!CanSeek) { return 0L; } return BaseSource.Length; } } public bool CanSeek => BaseSource.CanSeek; public virtual ISampleSource BaseSource { get { return _baseSource; } set { if (value == null) { throw new ArgumentNullException("value"); } _baseSource = value; } } public bool DisposeBaseSource { get; set; } public SampleAggregatorBase(ISampleSource source) { if (source == null) { throw new ArgumentNullException("source"); } _baseSource = source; DisposeBaseSource = true; } public virtual int Read(float[] buffer, int offset, int count) { if (offset % WaveFormat.Channels != 0) { offset -= offset % WaveFormat.Channels; } if (count % WaveFormat.Channels != 0) { count -= count % WaveFormat.Channels; } return BaseSource.Read(buffer, offset, count); } public void Dispose() { if (!_disposed) { _disposed = true; Dispose(disposing: true); GC.SuppressFinalize(this); } } protected virtual void Dispose(bool disposing) { if (DisposeBaseSource && BaseSource != null) { BaseSource.Dispose(); _baseSource = null; } } ~SampleAggregatorBase() { Dispose(disposing: false); } } public static class Extensions { public static TimeSpan GetLength(this IAudioSource source) { return source.GetTime(source.Length); } public static TimeSpan GetPosition(this IAudioSource source) { return source.GetTime(source.Position); } public static void SetPosition(this IAudioSource source, TimeSpan position) { if (source == null) { throw new ArgumentNullException("source"); } if (position.TotalMilliseconds < 0.0) { throw new ArgumentOutOfRangeException("position"); } long rawElements = source.GetRawElements(position); source.Position = rawElements; } public static TimeSpan GetTime(this IAudioSource source, long elementCount) { if (source == null) { throw new ArgumentNullException("source"); } if (elementCount < 0) { throw new ArgumentNullException("elementCount"); } return TimeConverterFactory.Instance.GetTimeConverterForSource(source).ToTimeSpan(source.WaveFormat, elementCount); } public static long GetMilliseconds(this IAudioSource source, long elementCount) { if (source == null) { throw new ArgumentNullException("source"); } if (elementCount < 0) { throw new ArgumentOutOfRangeException("elementCount"); } return (long)source.GetTime(elementCount).TotalMilliseconds; } public static long GetRawElements(this IAudioSource source, TimeSpan timespan) { if (source == null) { throw new ArgumentNullException("source"); } return TimeConverterFactory.Instance.GetTimeConverterForSource(source).ToRawElements(source.WaveFormat, timespan); } public static long GetRawElements(this IAudioSource source, long milliseconds) { if (source == null) { throw new ArgumentNullException("source"); } if (milliseconds < 0) { throw new ArgumentOutOfRangeException("milliseconds"); } return source.GetRawElements(TimeSpan.FromMilliseconds(milliseconds)); } public static void WriteToFile(this IWaveSource source, string filename) { if (source == null) { throw new ArgumentNullException("source"); } using FileStream stream = File.OpenWrite(filename); source.WriteToWaveStream(stream); } public static void WriteToWaveStream(this IWaveSource source, Stream stream) { if (source == null) { throw new ArgumentNullException("source"); } if (stream == null) { throw new ArgumentNullException("stream"); } if (!stream.CanWrite) { throw new ArgumentException("Stream is not writeable.", "stream"); } using WaveWriter waveWriter = new WaveWriter(stream, source.WaveFormat); byte[] array = new byte[source.WaveFormat.BytesPerSecond]; int count; while ((count = source.Read(array, 0, array.Length)) > 0) { waveWriter.Write(array, 0, count); } } public static void WriteToStream(this IWaveSource waveSource, Stream stream) { if (waveSource == null) { throw new ArgumentNullException("waveSource"); } if (stream == null) { throw new ArgumentNullException("stream"); } if (!stream.CanWrite) { throw new ArgumentException("Stream is not writeable.", "stream"); } byte[] array = new byte[waveSource.WaveFormat.BytesPerSecond]; int count; while ((count = waveSource.Read(array, 0, array.Length)) > 0) { stream.Write(array, 0, count); } } public static T[] CheckBuffer<T>(this T[] inst, long size, bool exactSize = false) { if (inst == null || (!exactSize && inst.Length < size) || (exactSize && inst.Length != size)) { return new T[size]; } return inst; } internal static byte[] ReadBytes(this IWaveSource waveSource, int count) { if (waveSource == null) { throw new ArgumentNullException("waveSource"); } count -= count % waveSource.WaveFormat.BlockAlign; if (count <= 0) { throw new ArgumentOutOfRangeException("count"); } byte[] array = new byte[count]; int num = waveSource.Read(array, 0, array.Length); if (num < count) { Array.Resize(ref array, num); } return array; } internal static bool IsClosed(this Stream stream) { if (!stream.CanRead) { return !stream.CanWrite; } return false; } internal static bool IsEndOfStream(this Stream stream) { return stream.Position == stream.Length; } internal static int LowWord(this int number) { return number & 0xFFFF; } internal static int LowWord(this int number, int newValue) { return (int)((number & 0xFFFF0000u) + (newValue & 0xFFFF)); } internal static int HighWord(this int number) { return (int)(number & 0xFFFF0000u); } internal static int HighWord(this int number, int newValue) { return (number & 0xFFFF) + (newValue << 16); } internal static uint LowWord(this uint number) { return number & 0xFFFFu; } internal static uint LowWord(this uint number, int newValue) { return (uint)((uint)((int)number & -65536) + (newValue & 0xFFFF)); } internal static uint HighWord(this uint number) { return number & 0xFFFF0000u; } internal static uint HighWord(this uint number, int newValue) { return (uint)((number & 0xFFFF) + (newValue << 16)); } internal static Guid GetGuid(this object obj) { return obj.GetType().GUID; } internal static void WaitForExit(this Thread thread) { if (thread != null) { if (thread == Thread.CurrentThread) { throw new InvalidOperationException("Deadlock detected."); } thread.Join(); } } internal static bool WaitForExit(this Thread thread, int timeout) { if (thread == null) { return true; } if (thread == Thread.CurrentThread) { throw new InvalidOperationException("Deadlock detected."); } return thread.Join(timeout); } internal static bool IsPCM(this WaveFormat waveFormat) { if (waveFormat == null) { throw new ArgumentNullException("waveFormat"); } if (waveFormat is WaveFormatExtensible) { return ((WaveFormatExtensible)waveFormat).SubFormat == AudioSubTypes.Pcm; } return waveFormat.WaveFormatTag == AudioEncoding.Pcm; } internal static bool IsIeeeFloat(this WaveFormat waveFormat) { if (waveFormat == null) { throw new ArgumentNullException("waveFormat"); } if (waveFormat is WaveFormatExtensible) { return ((WaveFormatExtensible)waveFormat).SubFormat == AudioSubTypes.IeeeFloat; } return waveFormat.WaveFormatTag == AudioEncoding.IeeeFloat; } internal static AudioEncoding GetWaveFormatTag(this WaveFormat waveFormat) { if (waveFormat is WaveFormatExtensible) { return AudioSubTypes.EncodingFromSubType(((WaveFormatExtensible)waveFormat).SubFormat); } return waveFormat.WaveFormatTag; } public static bool WaitForStopped(this ISoundOut soundOut, int millisecondsTimeout) { if (soundOut == null) { throw new ArgumentNullException("soundOut"); } if (millisecondsTimeout < -1) { throw new ArgumentOutOfRangeException("millisecondsTimeout"); } if (soundOut.PlaybackState == PlaybackState.Stopped) { return true; } AutoResetEvent waitHandle = new AutoResetEvent(initialState: false); try { EventHandler<PlaybackStoppedEventArgs> value = delegate { waitHandle.Set(); }; soundOut.Stopped += value; bool result = waitHandle.WaitOne(millisecondsTimeout); soundOut.Stopped -= value; return result; } finally { if (waitHandle != null) { ((IDisposable)waitHandle).Dispose(); } } } public static void WaitForStopped(this ISoundOut soundOut) { soundOut.WaitForStopped(-1); } internal static void SetValueForValueType<T>(this FieldInfo field, ref T item, object value) where T : struct { field.SetValueDirect(__makeref(item), value); } } } namespace CSCore.SoundOut { public class PlaybackStoppedEventArgs : StoppedEventArgs { public PlaybackStoppedEventArgs() : this(null) { } public PlaybackStoppedEventArgs(Exception exception) : base(exception) { } } } namespace CSCore.Codecs.WAV { public class WaveWriter : IDisposable, IWriteable { private readonly WaveFormat _waveFormat; private readonly long _waveStartPosition; private int _dataLength; private bool _isDisposed; private Stream _stream; private BinaryWriter _writer; private bool _isDisposing; private readonly bool _closeStream; public bool IsDisposed => _isDisposed; public bool IsDisposing => _isDisposing; public WaveWriter(string fileName, WaveFormat waveFormat) : this(File.OpenWrite(fileName), waveFormat) { _closeStream = true; } public WaveWriter(Stream stream, WaveFormat waveFormat) { if (stream == null) { throw new ArgumentNullException("stream"); } if (!stream.CanWrite) { throw new ArgumentException("Stream not writeable.", "stream"); } if (!stream.CanSeek) { throw new ArgumentException("Stream not seekable.", "stream"); } _isDisposing = false; _isDisposed = false; _stream = stream; _waveStartPosition = stream.Position; _writer = new BinaryWriter(stream); for (int i = 0; i < 44; i++) { _writer.Write((byte)0); } _waveFormat = waveFormat; WriteHeader(); _closeStream = false; } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } [Obsolete("Use the Extensions.WriteToWaveStream extension instead.")] public static void WriteToFile(string filename, IWaveSource source, bool deleteFileIfAlreadyExists, int maxlength = -1) { if (deleteFileIfAlreadyExists && File.Exists(filename)) { File.Delete(filename); } int num = 0; byte[] array = new byte[source.WaveFormat.BytesPerSecond]; using WaveWriter waveWriter = new WaveWriter(filename, source.WaveFormat); int num2; while ((num2 = source.Read(array, 0, array.Length)) > 0) { waveWriter.Write(array, 0, num2); num += num2; if (maxlength != -1 && num > maxlength) { break; } } } public void WriteSample(float sample) { CheckObjectDisposed(); if (sample < -1f || sample > 1f) { sample = Math.Max(-1f, Math.Min(1f, sample)); } if (_waveFormat.IsPCM()) { switch (_waveFormat.BitsPerSample) { case 8: Write((byte)(255f * sample)); break; case 16: Write((short)(32767f * sample)); break; case 24: { byte[] bytes = BitConverter.GetBytes((int)(8388607f * sample)); Write(new byte[3] { bytes[0], bytes[1], bytes[2] }, 0, 3); break; } case 32: Write((int)(2.1474836E+09f * sample)); break; default: throw new InvalidOperationException("Invalid Waveformat", new InvalidOperationException("Invalid BitsPerSample while using PCM encoding.")); } } else if (_waveFormat.IsIeeeFloat()) { Write(sample); } else { if (_waveFormat.WaveFormatTag != AudioEncoding.Extensible || _waveFormat.BitsPerSample != 32) { throw new InvalidOperationException("Invalid Waveformat: Waveformat has to be PCM[8, 16, 24, 32] or IeeeFloat[32]"); } Write(65535 * (int)sample); } } public void WriteSamples(float[] samples, int offset, int count) { CheckObjectDisposed(); for (int i = offset; i < offset + count; i++) { WriteSample(samples[i]); } } public void Write(byte[] buffer, int offset, int count) { CheckObjectDisposed(); _stream.Write(buffer, offset, count); _dataLength += count; } public void Write(byte value) { CheckObjectDisposed(); _writer.Write(value); _dataLength++; } public void Write(short value) { CheckObjectDisposed(); _writer.Write(value); _dataLength += 2; } public void Write(int value) { CheckObjectDisposed(); _writer.Write(value); _dataLength += 4; } public void Write(float value) { CheckObjectDisposed(); _writer.Write(value); _dataLength += 4; } private void WriteHeader() { _writer.Flush(); long position = _stream.Position; _stream.Position = _waveStartPosition; WriteRiffHeader(); WriteFmtChunk(); WriteDataChunk(); _writer.Flush(); _stream.Position = position; } private void WriteRiffHeader() { _writer.Write(Encoding.UTF8.GetBytes("RIFF")); _writer.Write((int)(_stream.Length - 8)); _writer.Write(Encoding.UTF8.GetBytes("WAVE")); } private void WriteFmtChunk() { AudioEncoding audioEncoding = _waveFormat.WaveFormatTag; if (audioEncoding == AudioEncoding.Extensible && _waveFormat is WaveFormatExtensible) { audioEncoding = AudioSubTypes.EncodingFromSubType((_waveFormat as WaveFormatExtensible).SubFormat); } _writer.Write(Encoding.UTF8.GetBytes("fmt ")); _writer.Write(16); _writer.Write((short)audioEncoding); _writer.Write((short)_waveFormat.Channels); _writer.Write(_waveFormat.SampleRate); _writer.Write(_waveFormat.BytesPerSecond); _writer.Write((short)_waveFormat.BlockAlign); _writer.Write((short)_waveFormat.BitsPerSample); } private void WriteDataChunk() { _writer.Write(Encoding.UTF8.GetBytes("data")); _writer.Write(_dataLength); } private void CheckObjectDisposed() { if (_isDisposed) { throw new ObjectDisposedException("WaveWriter"); } } protected virtual void Dispose(bool disposing) { if (_isDisposed || !disposing) { return; } try { _isDisposing = true; WriteHeader(); } catch (Exception) { } finally { if (_closeStream) { if (_writer != null) { _writer.Close(); _writer = null; } if (_stream != null) { _stream.Dispose(); _stream = null; } } _isDisposing = false; } _isDisposed = true; } ~WaveWriter() { Dispose(disposing: false); } } } namespace CSCore.SoundOut { public interface ISoundOut : IDisposable { float Volume { get; set; } IWaveSource WaveSource { get; } PlaybackState PlaybackState { get; } event EventHandler<PlaybackStoppedEventArgs> Stopped; void Play(); void Pause(); void Resume(); void Stop(); void Initialize(IWaveSource source); } public enum PlaybackState { Stopped, Playing, Paused } } namespace CSCore { public abstract class WaveAggregatorBase : IWaveAggregator, IWaveSource, IReadableAudioSource<byte>, IAudioSource, IDisposable, IAggregator<byte, IWaveSource> { private IWaveSource _baseSource; private bool _disposed; public bool DisposeBaseSource { get; set; } public virtual IWaveSource BaseSource { get { return _baseSource; } set { if (value == null) { throw new ArgumentNullException("value", "BaseSource must not be null."); } _baseSource = value; } } public virtual WaveFormat WaveFormat => BaseSource.WaveFormat; public virtual long Position { get { if (!CanSeek) { return 0L; } return BaseSource.Position; } set { if (CanSeek) { value -= value % WaveFormat.BlockAlign; BaseSource.Position = value; return; } throw new InvalidOperationException(); } } public virtual long Length { get { if (!CanSeek) { return 0L; } return BaseSource.Length; } } public virtual bool CanSeek => BaseSource.CanSeek; protected WaveAggregat