Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SemiBoombox v1.2.0
SemiBoombox.dll
Decompiled a year 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.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Microsoft.Win32; using NAudio.CoreAudioApi; using NAudio.CoreAudioApi.Interfaces; using NAudio.Dmo; using NAudio.Dmo.Effect; using NAudio.Dsp; using NAudio.FileFormats.Wav; using NAudio.MediaFoundation; using NAudio.Mixer; using NAudio.Utils; using NAudio.Wasapi.CoreAudioApi; using NAudio.Wasapi.CoreAudioApi.Interfaces; using NAudio.Wave; using NAudio.Wave.Asio; using NAudio.Wave.Compression; using NAudio.Wave.SampleProviders; using Photon.Pun; using Photon.Realtime; using SemiBoombox.Utils; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("SemiBoombox")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0+4baf32f067d47e44bc937b32bdd9d13ee3aa6988")] [assembly: AssemblyProduct("Semi Boombox")] [assembly: AssemblyTitle("SemiBoombox")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.2.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SemiBoombox { public class Boombox : MonoBehaviour { public PhotonView photonView; public AudioSource audioSource; private static Dictionary<string, AudioClip> downloadedClips = new Dictionary<string, AudioClip>(); private static Dictionary<string, HashSet<int>> downloadsReady = new Dictionary<string, HashSet<int>>(); public static Dictionary<string, string> downloadedSongs = new Dictionary<string, string>(); private bool isDownloading; private static Dictionary<int, Boombox> _boomboxCache = new Dictionary<int, Boombox>(); public static Dictionary<int, Boombox> BoomboxCache { get { if (IsCacheInvalid()) { RefreshBoomboxCache(); } return _boomboxCache; } } private void Awake() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown audioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); audioSource.spatialBlend = 1f; audioSource.playOnAwake = false; audioSource.minDistance = 1f; audioSource.maxDistance = 30f; audioSource.rolloffMode = (AudioRolloffMode)2; AnimationCurve val = new AnimationCurve(); val.AddKey(1f, 1f); val.AddKey(30f, 0f); audioSource.SetCustomCurve((AudioSourceCurveType)0, val); photonView = ((Component)this).GetComponent<PhotonView>(); if ((Object)(object)photonView == (Object)null) { Debug.LogError((object)"PhotonView not found on Boombox object."); } else if (photonView.IsMine) { audioSource.volume = 0.1f; ((Component)this).gameObject.AddComponent<BoomboxUI>(); } else { audioSource.volume = 0.2f; } } [PunRPC] public async void RequestSong(string url, int requesterId) { Debug.Log((object)$"RequestSong RPC received: url={url}, requesterId={requesterId}"); if (photonView.IsMine && isDownloading) { Debug.Log((object)"Already downloading a song. Please wait for the current download to finish."); return; } if (photonView.IsMine) { isDownloading = true; } if (!downloadedClips.ContainsKey(url)) { try { string filePath = await Task.Run(() => YoutubeDL.DownloadAudioAsync(url)); AudioClip val = await Task.Run(() => AudioConverter.GetAudioClipAsync(filePath)); downloadedClips[url] = val; Debug.Log((object)("Downloaded and cached clip for url: " + url)); AddDownloadedSong(((Object)val).name, url); } catch (Exception ex) { Debug.LogError((object)("Failed to download audio: " + ex.Message)); isDownloading = false; return; } } else { Debug.Log((object)("Clip already cached for url: " + url)); } photonView.RPC("ReportDownloadComplete", (RpcTarget)0, new object[2] { PhotonNetwork.LocalPlayer.ActorNumber, url }); await WaitForAllPlayersReady(url); photonView.RPC("SyncPlayback", (RpcTarget)0, new object[2] { url, requesterId }); if (photonView.IsMine) { isDownloading = false; } } [PunRPC] public void ReportDownloadComplete(int actorNumber, string url) { if (!downloadsReady.ContainsKey(url)) { downloadsReady[url] = new HashSet<int>(); } downloadsReady[url].Add(actorNumber); Debug.Log((object)$"Player {actorNumber} reported ready for url: {url}. Total ready: {downloadsReady[url].Count}"); } private async Task WaitForAllPlayersReady(string url) { int totalPlayers = PhotonNetwork.PlayerList.Length; while (!downloadsReady.ContainsKey(url) || downloadsReady[url].Count < totalPlayers) { await Task.Delay(100); } } [PunRPC] public void SyncPlayback(string url, int requesterId) { if (photonView.Owner != null && photonView.Owner.ActorNumber == requesterId) { if (!downloadedClips.ContainsKey(url)) { Debug.LogError((object)("Clip not found for url: " + url)); return; } audioSource.clip = downloadedClips[url]; audioSource.Play(); Debug.Log((object)$"SyncPlayback RPC executed: url={url}, requesterId={requesterId}"); } } [PunRPC] public void StopPlayback(int requesterId) { if (photonView.Owner != null && photonView.Owner.ActorNumber == requesterId) { Debug.Log((object)$"Stopping playback on Boombox owned by player {requesterId}"); if (audioSource.isPlaying) { audioSource.Stop(); } } } [PunRPC] public void SyncTime(float time, int requesterId) { if (photonView.Owner != null && photonView.Owner.ActorNumber == requesterId) { if (audioSource.isPlaying) { audioSource.time = time; } Debug.Log((object)$"Syncing time to {time} seconds, requesterId={requesterId}"); } } private static bool IsCacheInvalid() { if (_boomboxCache == null || _boomboxCache.Count == 0) { return true; } foreach (KeyValuePair<int, Boombox> item in _boomboxCache) { if ((Object)(object)item.Value == (Object)null || (Object)(object)((Component)item.Value).gameObject == (Object)null) { return true; } } return false; } private static void RefreshBoomboxCache() { _boomboxCache.Clear(); Boombox[] array = Object.FindObjectsOfType<Boombox>(); foreach (Boombox boombox in array) { if (boombox.photonView.Owner != null) { int actorNumber = boombox.photonView.Owner.ActorNumber; _boomboxCache[actorNumber] = boombox; } } } private void AddDownloadedSong(string songName, string url) { if (!downloadedSongs.ContainsKey(songName)) { downloadedSongs.Add(songName, url); } } } public class BoomboxUI : MonoBehaviour { public PhotonView photonView; private bool showUI; private string urlInput = ""; private string urlFeedback = ""; private float volume = 0.15f; private float currentPlaybackTime; private float totalPlaybackTime; private float lastSyncTime; private const float SYNC_COOLDOWN = 0.15f; private bool sliderLocked; private Rect windowRect = new Rect(100f, 100f, 400f, 500f); private Vector2 scrollPosition = Vector2.zero; private Boombox boombox; private void Awake() { boombox = ((Component)this).GetComponent<Boombox>(); photonView = boombox.photonView; } private void Update() { if (Input.GetKeyDown((KeyCode)120)) { showUI = !showUI; Cursor.visible = showUI; Cursor.lockState = (CursorLockMode)((!showUI) ? 1 : 0); } if ((Object)(object)boombox.audioSource != (Object)null && (Object)(object)boombox.audioSource.clip != (Object)null) { if (boombox.audioSource.isPlaying) { currentPlaybackTime = boombox.audioSource.time; totalPlaybackTime = boombox.audioSource.clip.length; } else { currentPlaybackTime = boombox.audioSource.time; totalPlaybackTime = boombox.audioSource.clip.length; } } else { currentPlaybackTime = 0f; totalPlaybackTime = 0f; } } private void OnGUI() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (showUI) { windowRect = GUI.Window(0, windowRect, new WindowFunction(DrawUI), "Boombox Controller"); } } private void DrawUI(int windowID) { //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) GUILayout.Label("Enter YouTube URL:", Array.Empty<GUILayoutOption>()); urlInput = GUILayout.TextField(urlInput, 200, Array.Empty<GUILayoutOption>()); GUILayout.Label(urlFeedback, Array.Empty<GUILayoutOption>()); GUILayout.Space(10f); GUILayout.Label($"Volume: {Mathf.Round(volume * 100f)}%", Array.Empty<GUILayoutOption>()); float num = GUILayout.HorizontalSlider(volume, 0f, 1f, Array.Empty<GUILayoutOption>()); if (num != volume) { volume = num; foreach (Boombox value in Boombox.BoomboxCache.Values) { if ((Object)(object)value.audioSource != (Object)null) { value.audioSource.volume = volume; } } } GUILayout.Space(10f); GUILayout.Label("Current Time: " + FormatTime(currentPlaybackTime) + " / " + FormatTime(totalPlaybackTime), Array.Empty<GUILayoutOption>()); sliderLocked = Time.time - lastSyncTime < 0.15f; if ((Object)(object)boombox.audioSource != (Object)null && boombox.audioSource.isPlaying) { GUI.enabled = !sliderLocked; float num2 = GUILayout.HorizontalSlider(currentPlaybackTime, 0f, totalPlaybackTime, Array.Empty<GUILayoutOption>()); GUI.enabled = true; if (!sliderLocked && Mathf.Abs(num2 - currentPlaybackTime) > 0.1f) { lastSyncTime = Time.time; currentPlaybackTime = num2; photonView.RPC("SyncTime", (RpcTarget)0, new object[2] { currentPlaybackTime, PhotonNetwork.LocalPlayer.ActorNumber }); } } GUILayout.Space(10f); GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Play", Array.Empty<GUILayoutOption>())) { if (IsValidUrl(urlInput, out var correctedUrl)) { photonView.RPC("RequestSong", (RpcTarget)0, new object[2] { correctedUrl, PhotonNetwork.LocalPlayer.ActorNumber }); } else { urlFeedback = "Invalid URL!"; Debug.LogError((object)"Invalid URL!"); } } if (GUILayout.Button("Stop", Array.Empty<GUILayoutOption>())) { photonView.RPC("StopPlayback", (RpcTarget)0, new object[1] { PhotonNetwork.LocalPlayer.ActorNumber }); } if (GUILayout.Button("Close", Array.Empty<GUILayoutOption>())) { showUI = false; Cursor.lockState = (CursorLockMode)1; } GUILayout.EndHorizontal(); GUILayout.Space(10f); GUILayout.Label("Downloaded Songs:", Array.Empty<GUILayoutOption>()); scrollPosition = GUILayout.BeginScrollView(scrollPosition, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(200f) }); foreach (KeyValuePair<string, string> downloadedSong in Boombox.downloadedSongs) { if (GUILayout.Button(downloadedSong.Key, Array.Empty<GUILayoutOption>())) { urlInput = downloadedSong.Value; } } GUILayout.EndScrollView(); GUI.DragWindow(); } private bool IsValidUrl(string url, out string correctedUrl) { string pattern = "^https?:\\/\\/(www\\.)?youtube\\.com\\/watch\\?v=[a-zA-Z0-9_-]+$"; correctedUrl = url; if (Regex.IsMatch(url, pattern)) { return true; } if (url.Contains("youtube") && url.Contains("watch?v=")) { correctedUrl = "https://www.youtube.com/watch?v=" + url.Split(new string[1] { "watch?v=" }, StringSplitOptions.None)[1].Split(new char[1] { '&' })[0]; urlFeedback = "URL fixed to: " + correctedUrl; return true; } return false; } private string FormatTime(float seconds) { TimeSpan timeSpan = TimeSpan.FromSeconds(seconds); return $"{timeSpan.Minutes:D2}:{timeSpan.Seconds:D2}"; } } [BepInPlugin("SemiBoombox", "Semi Boombox", "1.2.0")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Logger; private static Harmony _harmony; private void Awake() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"Plugin SemiBoombox is loaded!"); Task.Run(delegate { YoutubeDL.InitializeAsync().Wait(); }); _harmony = new Harmony("SemiBoombox"); _harmony.PatchAll(); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "SemiBoombox"; public const string PLUGIN_NAME = "Semi Boombox"; public const string PLUGIN_VERSION = "1.2.0"; } } namespace SemiBoombox.Utils { public static class AudioConverter { public static string ConvertM4AToWav(string inputFile) { string text = Path.ChangeExtension(inputFile, ".wav"); using MediaFoundationReader mediaFoundationReader = new MediaFoundationReader(inputFile); using WaveFileWriter destination = new WaveFileWriter(text, mediaFoundationReader.WaveFormat); mediaFoundationReader.CopyTo(destination); return text; } public static async Task<AudioClip> WavToAudioClipAsync(string filePath) { byte[] wavBytes; using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { wavBytes = new byte[fileStream.Length]; await fileStream.ReadAsync(wavBytes, 0, wavBytes.Length); } int num = 44; short num2 = BitConverter.ToInt16(wavBytes, 22); int num3 = BitConverter.ToInt32(wavBytes, 24); short num4 = BitConverter.ToInt16(wavBytes, 34); int num5 = wavBytes.Length - num; int num6 = num4 / 8; int num7 = num5 / num6; float[] array = new float[num7]; if (num4 == 16) { for (int i = 0; i < num7; i++) { short num8 = BitConverter.ToInt16(wavBytes, num + i * num6); array[i] = (float)num8 / 32768f; } int num9 = num7 / num2; AudioClip obj = AudioClip.Create(Path.GetFileNameWithoutExtension(filePath), num9, (int)num2, num3, false); obj.SetData(array, 0); return obj; } throw new NotSupportedException("Only 16-bit PCM WAV files are supported in this example."); } public static async Task<AudioClip> GetAudioClipAsync(string filePath) { string wavFile = ConvertM4AToWav(filePath); AudioClip result = await WavToAudioClipAsync(wavFile); string directoryName = Path.GetDirectoryName(filePath); if (File.Exists(filePath)) { File.Delete(filePath); } if (File.Exists(wavFile)) { File.Delete(wavFile); } if (Directory.Exists(directoryName) && Directory.GetFileSystemEntries(directoryName).Length == 0) { Directory.Delete(directoryName); } return result; } } public static class YoutubeDL { private static readonly string baseFolder = Path.Combine(Directory.GetCurrentDirectory(), "SemiBoombox"); private const string YTDLP_URL = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"; private static readonly string ytDlpPath = Path.Combine(baseFolder, "yt-dlp.exe"); private static bool _updateCheckDone = false; public static async Task InitializeAsync() { if (!Directory.Exists(baseFolder)) { Directory.CreateDirectory(baseFolder); } if (_updateCheckDone) { return; } if (File.Exists(ytDlpPath)) { try { Console.WriteLine("Updating yt-dlp using the --update command..."); await RunUpdateCommandAsync(); } catch (Exception ex) { Console.WriteLine("Error during yt-dlp self-update: " + ex.Message); File.Delete(ytDlpPath); await DownloadFileAsync("https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe", ytDlpPath); } } else { Console.WriteLine("yt-dlp not found. Downloading latest version..."); await DownloadFileAsync("https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe", ytDlpPath); } _updateCheckDone = true; Console.WriteLine("yt-dlp is up to date."); } private static async Task RunUpdateCommandAsync() { ProcessStartInfo startInfo = new ProcessStartInfo { FileName = ytDlpPath, Arguments = "--update", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using Process updateProcess = Process.Start(startInfo) ?? throw new Exception("Failed to start yt-dlp update process."); await WaitForProcessExit(updateProcess); } private static Task WaitForProcessExit(Process process) { TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(); process.EnableRaisingEvents = true; process.Exited += delegate { tcs.TrySetResult(null); }; if (process.HasExited) { tcs.TrySetResult(null); } return tcs.Task; } private static async Task DownloadFileAsync(string url, string destinationPath) { HttpClient client = new HttpClient(); try { File.WriteAllBytes(destinationPath, await client.GetByteArrayAsync(url)); } finally { ((IDisposable)client)?.Dispose(); } } public static async Task<string> DownloadAudioAsync(string videoUrl) { await InitializeAsync(); string tempFolder = Path.Combine(baseFolder, Guid.NewGuid().ToString()); Directory.CreateDirectory(tempFolder); Console.WriteLine("Downloading audio..."); return await Task.Run(delegate { try { string arguments = "-f \"bestaudio[ext=m4a]\" -o \"" + Path.Combine(tempFolder, "%(title)s.%(ext)s") + "\" " + videoUrl; using (Process process = Process.Start(new ProcessStartInfo { FileName = ytDlpPath, Arguments = arguments, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true })) { if (process == null) { throw new Exception("Failed to start yt-dlp process."); } process.WaitForExit(); if (process.ExitCode != 0) { string text = process.StandardError.ReadToEnd(); throw new Exception("yt-dlp error: " + text); } } return Directory.GetFiles(tempFolder, "*.m4a").FirstOrDefault() ?? throw new Exception("Audio download failed."); } catch (Exception ex) { Directory.Delete(tempFolder, recursive: true); throw new Exception("Error downloading audio: " + ex.Message); } }); } } } namespace SemiBoombox.Patches { [HarmonyPatch(typeof(PlayerAvatar), "Awake")] public class PlayerAvatarPatch { private static void Postfix(PlayerAvatar __instance) { if ((Object)(object)((Component)__instance).GetComponent<Boombox>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<Boombox>(); } } } [HarmonyPatch(typeof(PlayerAvatarTalkAnimation), "Update")] public class PlayerAvatarTalkAnimationPatch { private static Dictionary<int, Boombox> _boomboxCache = new Dictionary<int, Boombox>(); private static void Postfix(PlayerAvatarTalkAnimation __instance) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.objectToRotate == (Object)null || (Object)(object)__instance.playerAvatar == (Object)null) { return; } PhotonView photonView = __instance.playerAvatar.photonView; if (((photonView != null) ? photonView.Owner : null) == null) { return; } Boombox boombox = FindBoomboxForPlayer(__instance.playerAvatar.photonView.Owner.ActorNumber); if (!((Object)(object)boombox == (Object)null) && !((Object)(object)boombox.audioSource == (Object)null) && boombox.audioSource.isPlaying) { float audioLoudness = GetAudioLoudness(boombox.audioSource); if (!(audioLoudness < 0.005f)) { float num = Mathf.Lerp(0f, 0f - __instance.rotationMaxAngle, audioLoudness * 4f); __instance.objectToRotate.transform.localRotation = Quaternion.Slerp(__instance.objectToRotate.transform.localRotation, Quaternion.Euler(num, 0f, 0f), 100f * Time.deltaTime); } } } private static Boombox FindBoomboxForPlayer(int actorNumber) { if (_boomboxCache.TryGetValue(actorNumber, out var value) && (Object)(object)value != (Object)null && (Object)(object)((Component)value).gameObject != (Object)null) { return value; } Boombox[] array = Object.FindObjectsOfType<Boombox>(); foreach (Boombox boombox in array) { if (boombox != null) { PhotonView photonView = boombox.photonView; int? obj; if (photonView == null) { obj = null; } else { Player owner = photonView.Owner; obj = ((owner != null) ? new int?(owner.ActorNumber) : null); } if (obj == actorNumber) { _boomboxCache[actorNumber] = boombox; return boombox; } } } return null; } private static float GetAudioLoudness(AudioSource source) { float[] array = new float[1024]; source.GetOutputData(array, 0); float num = 0f; float[] array2 = array; foreach (float num2 in array2) { num += Mathf.Abs(num2); } return num / (float)array.Length; } } } namespace NAudio.Wave { public class AudioFileReader : WaveStream, ISampleProvider { private WaveStream readerStream; private readonly SampleChannel sampleChannel; private readonly int destBytesPerSample; private readonly int sourceBytesPerSample; private readonly long length; private readonly object lockObject; public string FileName { get; } public override WaveFormat WaveFormat => sampleChannel.WaveFormat; public override long Length => length; public override long Position { get { return SourceToDest(readerStream.Position); } set { lock (lockObject) { readerStream.Position = DestToSource(value); } } } public float Volume { get { return sampleChannel.Volume; } set { sampleChannel.Volume = value; } } public AudioFileReader(string fileName) { lockObject = new object(); FileName = fileName; CreateReaderStream(fileName); sourceBytesPerSample = readerStream.WaveFormat.BitsPerSample / 8 * readerStream.WaveFormat.Channels; sampleChannel = new SampleChannel(readerStream, forceStereo: false); destBytesPerSample = 4 * sampleChannel.WaveFormat.Channels; length = SourceToDest(readerStream.Length); } private void CreateReaderStream(string fileName) { if (fileName.EndsWith(".wav", StringComparison.OrdinalIgnoreCase)) { readerStream = new WaveFileReader(fileName); if (readerStream.WaveFormat.Encoding != WaveFormatEncoding.Pcm && readerStream.WaveFormat.Encoding != WaveFormatEncoding.IeeeFloat) { readerStream = WaveFormatConversionStream.CreatePcmStream(readerStream); readerStream = new BlockAlignReductionStream(readerStream); } } else if (fileName.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase)) { if (Environment.OSVersion.Version.Major < 6) { readerStream = new Mp3FileReader(fileName); } else { readerStream = new MediaFoundationReader(fileName); } } else if (fileName.EndsWith(".aiff", StringComparison.OrdinalIgnoreCase) || fileName.EndsWith(".aif", StringComparison.OrdinalIgnoreCase)) { readerStream = new AiffFileReader(fileName); } else { readerStream = new MediaFoundationReader(fileName); } } public override int Read(byte[] buffer, int offset, int count) { WaveBuffer waveBuffer = new WaveBuffer(buffer); int count2 = count / 4; return Read(waveBuffer.FloatBuffer, offset / 4, count2) * 4; } public int Read(float[] buffer, int offset, int count) { lock (lockObject) { return sampleChannel.Read(buffer, offset, count); } } private long SourceToDest(long sourceBytes) { return destBytesPerSample * (sourceBytes / sourceBytesPerSample); } private long DestToSource(long destBytes) { return sourceBytesPerSample * (destBytes / destBytesPerSample); } protected override void Dispose(bool disposing) { if (disposing && readerStream != null) { readerStream.Dispose(); readerStream = null; } base.Dispose(disposing); } } public class Mp3FileReader : Mp3FileReaderBase { public Mp3FileReader(string mp3FileName) : base(File.OpenRead(mp3FileName), CreateAcmFrameDecompressor, ownInputStream: true) { } public Mp3FileReader(Stream inputStream) : base(inputStream, CreateAcmFrameDecompressor, ownInputStream: false) { } public static IMp3FrameDecompressor CreateAcmFrameDecompressor(WaveFormat mp3Format) { return new AcmMp3FrameDecompressor(mp3Format); } } public class AsioAudioAvailableEventArgs : EventArgs { public IntPtr[] InputBuffers { get; private set; } public IntPtr[] OutputBuffers { get; private set; } public bool WrittenToOutputBuffers { get; set; } public int SamplesPerBuffer { get; private set; } public AsioSampleType AsioSampleType { get; private set; } public AsioAudioAvailableEventArgs(IntPtr[] inputBuffers, IntPtr[] outputBuffers, int samplesPerBuffer, AsioSampleType asioSampleType) { InputBuffers = inputBuffers; OutputBuffers = outputBuffers; SamplesPerBuffer = samplesPerBuffer; AsioSampleType = asioSampleType; } public unsafe int GetAsInterleavedSamples(float[] samples) { int num = InputBuffers.Length; if (samples.Length < SamplesPerBuffer * num) { throw new ArgumentException("Buffer not big enough"); } int num2 = 0; if (AsioSampleType == AsioSampleType.Int32LSB) { for (int i = 0; i < SamplesPerBuffer; i++) { for (int j = 0; j < num; j++) { samples[num2++] = (float)(*(int*)((byte*)(void*)InputBuffers[j] + (nint)i * (nint)4)) / 2.1474836E+09f; } } } else if (AsioSampleType == AsioSampleType.Int16LSB) { for (int k = 0; k < SamplesPerBuffer; k++) { for (int l = 0; l < num; l++) { samples[num2++] = (float)(*(short*)((byte*)(void*)InputBuffers[l] + (nint)k * (nint)2)) / 32767f; } } } else if (AsioSampleType == AsioSampleType.Int24LSB) { for (int m = 0; m < SamplesPerBuffer; m++) { for (int n = 0; n < num; n++) { byte* ptr = (byte*)(void*)InputBuffers[n] + m * 3; int num3 = *ptr | (ptr[1] << 8) | ((sbyte)ptr[2] << 16); samples[num2++] = (float)num3 / 8388608f; } } } else { if (AsioSampleType != AsioSampleType.Float32LSB) { throw new NotImplementedException($"ASIO Sample Type {AsioSampleType} not supported"); } for (int num4 = 0; num4 < SamplesPerBuffer; num4++) { for (int num5 = 0; num5 < num; num5++) { samples[num2++] = *(float*)((byte*)(void*)InputBuffers[num5] + (nint)num4 * (nint)4); } } } return SamplesPerBuffer * num; } [Obsolete("Better performance if you use the overload that takes an array, and reuse the same one")] public float[] GetAsInterleavedSamples() { int num = InputBuffers.Length; float[] array = new float[SamplesPerBuffer * num]; GetAsInterleavedSamples(array); return array; } } public class AsioOut : IWavePlayer, IDisposable { private AsioDriverExt driver; private IWaveProvider sourceStream; private PlaybackState playbackState; private int nbSamples; private byte[] waveBuffer; private AsioSampleConvertor.SampleConvertor convertor; private string driverName; private readonly SynchronizationContext syncContext; private bool isInitialized; public int PlaybackLatency { get { driver.Driver.GetLatencies(out var _, out var outputLatency); return outputLatency; } } public bool AutoStop { get; set; } public bool HasReachedEnd { get; private set; } public PlaybackState PlaybackState => playbackState; public string DriverName => driverName; public int NumberOfOutputChannels { get; private set; } public int NumberOfInputChannels { get; private set; } public int DriverInputChannelCount => driver.Capabilities.NbInputChannels; public int DriverOutputChannelCount => driver.Capabilities.NbOutputChannels; public int FramesPerBuffer { get { if (!isInitialized) { throw new Exception("Not initialized yet. Call this after calling Init"); } return nbSamples; } } public int ChannelOffset { get; set; } public int InputChannelOffset { get; set; } [Obsolete("this function will be removed in a future NAudio as ASIO does not support setting the volume on the device")] public float Volume { get { return 1f; } set { if (value != 1f) { throw new InvalidOperationException("AsioOut does not support setting the device volume"); } } } public WaveFormat OutputWaveFormat { get; private set; } public event EventHandler<StoppedEventArgs> PlaybackStopped; public event EventHandler<AsioAudioAvailableEventArgs> AudioAvailable; public event EventHandler DriverResetRequest; public AsioOut() : this(0) { } public AsioOut(string driverName) { syncContext = SynchronizationContext.Current; InitFromName(driverName); } public AsioOut(int driverIndex) { syncContext = SynchronizationContext.Current; string[] driverNames = GetDriverNames(); if (driverNames.Length == 0) { throw new ArgumentException("There is no ASIO Driver installed on your system"); } if (driverIndex < 0 || driverIndex > driverNames.Length) { throw new ArgumentException($"Invalid device number. Must be in the range [0,{driverNames.Length}]"); } InitFromName(driverNames[driverIndex]); } ~AsioOut() { Dispose(); } public void Dispose() { if (driver != null) { if (playbackState != 0) { driver.Stop(); } driver.ResetRequestCallback = null; driver.ReleaseDriver(); driver = null; } } public static string[] GetDriverNames() { return AsioDriver.GetAsioDriverNames(); } public static bool isSupported() { return GetDriverNames().Length != 0; } public bool IsSampleRateSupported(int sampleRate) { return driver.IsSampleRateSupported(sampleRate); } private void InitFromName(string driverName) { this.driverName = driverName; AsioDriver asioDriverByName = AsioDriver.GetAsioDriverByName(driverName); try { driver = new AsioDriverExt(asioDriverByName); } catch { ReleaseDriver(asioDriverByName); throw; } driver.ResetRequestCallback = OnDriverResetRequest; ChannelOffset = 0; } private void OnDriverResetRequest() { this.DriverResetRequest?.Invoke(this, EventArgs.Empty); } private void ReleaseDriver(AsioDriver driver) { driver.DisposeBuffers(); driver.ReleaseComAsioDriver(); } public void ShowControlPanel() { driver.ShowControlPanel(); } public void Play() { if (playbackState != PlaybackState.Playing) { playbackState = PlaybackState.Playing; HasReachedEnd = false; driver.Start(); } } public void Stop() { playbackState = PlaybackState.Stopped; driver.Stop(); HasReachedEnd = false; RaisePlaybackStopped(null); } public void Pause() { playbackState = PlaybackState.Paused; driver.Stop(); } public void Init(IWaveProvider waveProvider) { InitRecordAndPlayback(waveProvider, 0, -1); } public void InitRecordAndPlayback(IWaveProvider waveProvider, int recordChannels, int recordOnlySampleRate) { if (isInitialized) { throw new InvalidOperationException("Already initialised this instance of AsioOut - dispose and create a new one"); } isInitialized = true; int num = waveProvider?.WaveFormat.SampleRate ?? recordOnlySampleRate; if (waveProvider != null) { sourceStream = waveProvider; NumberOfOutputChannels = waveProvider.WaveFormat.Channels; AsioSampleType type = driver.Capabilities.OutputChannelInfos[0].type; convertor = AsioSampleConvertor.SelectSampleConvertor(waveProvider.WaveFormat, type); switch (type) { case AsioSampleType.Float32LSB: OutputWaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(waveProvider.WaveFormat.SampleRate, waveProvider.WaveFormat.Channels); break; case AsioSampleType.Int32LSB: OutputWaveFormat = new WaveFormat(waveProvider.WaveFormat.SampleRate, 32, waveProvider.WaveFormat.Channels); break; case AsioSampleType.Int16LSB: OutputWaveFormat = new WaveFormat(waveProvider.WaveFormat.SampleRate, 16, waveProvider.WaveFormat.Channels); break; case AsioSampleType.Int24LSB: OutputWaveFormat = new WaveFormat(waveProvider.WaveFormat.SampleRate, 24, waveProvider.WaveFormat.Channels); break; default: throw new NotSupportedException($"{type} not currently supported"); } } else { NumberOfOutputChannels = 0; } if (!driver.IsSampleRateSupported(num)) { throw new ArgumentException("SampleRate is not supported"); } if (driver.Capabilities.SampleRate != (double)num) { driver.SetSampleRate(num); } driver.FillBufferCallback = driver_BufferUpdate; NumberOfInputChannels = recordChannels; nbSamples = driver.CreateBuffers(NumberOfOutputChannels, NumberOfInputChannels, useMaxBufferSize: false); driver.SetChannelOffset(ChannelOffset, InputChannelOffset); if (waveProvider != null) { waveBuffer = new byte[nbSamples * NumberOfOutputChannels * waveProvider.WaveFormat.BitsPerSample / 8]; } } private unsafe void driver_BufferUpdate(IntPtr[] inputChannels, IntPtr[] outputChannels) { if (NumberOfInputChannels > 0) { EventHandler<AsioAudioAvailableEventArgs> audioAvailable = this.AudioAvailable; if (audioAvailable != null) { AsioAudioAvailableEventArgs asioAudioAvailableEventArgs = new AsioAudioAvailableEventArgs(inputChannels, outputChannels, nbSamples, driver.Capabilities.InputChannelInfos[0].type); audioAvailable(this, asioAudioAvailableEventArgs); if (asioAudioAvailableEventArgs.WrittenToOutputBuffers) { return; } } } if (NumberOfOutputChannels <= 0) { return; } int num = sourceStream.Read(waveBuffer, 0, waveBuffer.Length); if (num < waveBuffer.Length) { Array.Clear(waveBuffer, num, waveBuffer.Length - num); } fixed (byte* ptr = &waveBuffer[0]) { void* value = ptr; convertor(new IntPtr(value), outputChannels, NumberOfOutputChannels, nbSamples); } if (num == 0) { if (AutoStop) { Stop(); } HasReachedEnd = true; } } private void RaisePlaybackStopped(Exception e) { EventHandler<StoppedEventArgs> handler = this.PlaybackStopped; if (handler == null) { return; } if (syncContext == null) { handler(this, new StoppedEventArgs(e)); return; } syncContext.Post(delegate { handler(this, new StoppedEventArgs(e)); }, null); } public string AsioInputChannelName(int channel) { if (channel <= DriverInputChannelCount) { return driver.Capabilities.InputChannelInfos[channel].name; } return ""; } public string AsioOutputChannelName(int channel) { if (channel <= DriverOutputChannelCount) { return driver.Capabilities.OutputChannelInfos[channel].name; } return ""; } } } namespace NAudio.Wave.Asio { [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct Asio64Bit { public uint hi; public uint lo; } [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct AsioCallbacks { [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void AsioBufferSwitchCallBack(int doubleBufferIndex, bool directProcess); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void AsioSampleRateDidChangeCallBack(double sRate); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int AsioAsioMessageCallBack(AsioMessageSelector selector, int value, IntPtr message, IntPtr opt); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate IntPtr AsioBufferSwitchTimeInfoCallBack(IntPtr asioTimeParam, int doubleBufferIndex, bool directProcess); public AsioBufferSwitchCallBack pbufferSwitch; public AsioSampleRateDidChangeCallBack psampleRateDidChange; public AsioAsioMessageCallBack pasioMessage; public AsioBufferSwitchTimeInfoCallBack pbufferSwitchTimeInfo; } [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct AsioChannelInfo { public int channel; public bool isInput; public bool isActive; public int channelGroup; [MarshalAs(UnmanagedType.U4)] public AsioSampleType type; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string name; } public class AsioDriver { [StructLayout(LayoutKind.Sequential, Pack = 2)] private class AsioDriverVTable { [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate int ASIOInit(IntPtr _pUnknown, IntPtr sysHandle); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate void ASIOgetDriverName(IntPtr _pUnknown, StringBuilder name); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate int ASIOgetDriverVersion(IntPtr _pUnknown); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate void ASIOgetErrorMessage(IntPtr _pUnknown, StringBuilder errorMessage); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOstart(IntPtr _pUnknown); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOstop(IntPtr _pUnknown); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOgetChannels(IntPtr _pUnknown, out int numInputChannels, out int numOutputChannels); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOgetLatencies(IntPtr _pUnknown, out int inputLatency, out int outputLatency); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOgetBufferSize(IntPtr _pUnknown, out int minSize, out int maxSize, out int preferredSize, out int granularity); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOcanSampleRate(IntPtr _pUnknown, double sampleRate); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOgetSampleRate(IntPtr _pUnknown, out double sampleRate); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOsetSampleRate(IntPtr _pUnknown, double sampleRate); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOgetClockSources(IntPtr _pUnknown, out long clocks, int numSources); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOsetClockSource(IntPtr _pUnknown, int reference); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOgetSamplePosition(IntPtr _pUnknown, out long samplePos, ref Asio64Bit timeStamp); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOgetChannelInfo(IntPtr _pUnknown, ref AsioChannelInfo info); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOcreateBuffers(IntPtr _pUnknown, IntPtr bufferInfos, int numChannels, int bufferSize, IntPtr callbacks); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOdisposeBuffers(IntPtr _pUnknown); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOcontrolPanel(IntPtr _pUnknown); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOfuture(IntPtr _pUnknown, int selector, IntPtr opt); [UnmanagedFunctionPointer(CallingConvention.ThisCall)] public delegate AsioError ASIOoutputReady(IntPtr _pUnknown); public ASIOInit init; public ASIOgetDriverName getDriverName; public ASIOgetDriverVersion getDriverVersion; public ASIOgetErrorMessage getErrorMessage; public ASIOstart start; public ASIOstop stop; public ASIOgetChannels getChannels; public ASIOgetLatencies getLatencies; public ASIOgetBufferSize getBufferSize; public ASIOcanSampleRate canSampleRate; public ASIOgetSampleRate getSampleRate; public ASIOsetSampleRate setSampleRate; public ASIOgetClockSources getClockSources; public ASIOsetClockSource setClockSource; public ASIOgetSamplePosition getSamplePosition; public ASIOgetChannelInfo getChannelInfo; public ASIOcreateBuffers createBuffers; public ASIOdisposeBuffers disposeBuffers; public ASIOcontrolPanel controlPanel; public ASIOfuture future; public ASIOoutputReady outputReady; } private IntPtr pAsioComObject; private IntPtr pinnedcallbacks; private AsioDriverVTable asioDriverVTable; private AsioDriver() { } public static string[] GetAsioDriverNames() { RegistryKey registryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\ASIO"); string[] result = new string[0]; if (registryKey != null) { result = registryKey.GetSubKeyNames(); registryKey.Close(); } return result; } public static AsioDriver GetAsioDriverByName(string name) { return GetAsioDriverByGuid(new Guid((Registry.LocalMachine.OpenSubKey("SOFTWARE\\ASIO\\" + name) ?? throw new ArgumentException("Driver Name " + name + " doesn't exist")).GetValue("CLSID").ToString())); } public static AsioDriver GetAsioDriverByGuid(Guid guid) { AsioDriver asioDriver = new AsioDriver(); asioDriver.InitFromGuid(guid); return asioDriver; } public bool Init(IntPtr sysHandle) { return asioDriverVTable.init(pAsioComObject, sysHandle) == 1; } public string GetDriverName() { StringBuilder stringBuilder = new StringBuilder(256); asioDriverVTable.getDriverName(pAsioComObject, stringBuilder); return stringBuilder.ToString(); } public int GetDriverVersion() { return asioDriverVTable.getDriverVersion(pAsioComObject); } public string GetErrorMessage() { StringBuilder stringBuilder = new StringBuilder(256); asioDriverVTable.getErrorMessage(pAsioComObject, stringBuilder); return stringBuilder.ToString(); } public void Start() { HandleException(asioDriverVTable.start(pAsioComObject), "start"); } public AsioError Stop() { return asioDriverVTable.stop(pAsioComObject); } public void GetChannels(out int numInputChannels, out int numOutputChannels) { HandleException(asioDriverVTable.getChannels(pAsioComObject, out numInputChannels, out numOutputChannels), "getChannels"); } public AsioError GetLatencies(out int inputLatency, out int outputLatency) { return asioDriverVTable.getLatencies(pAsioComObject, out inputLatency, out outputLatency); } public void GetBufferSize(out int minSize, out int maxSize, out int preferredSize, out int granularity) { HandleException(asioDriverVTable.getBufferSize(pAsioComObject, out minSize, out maxSize, out preferredSize, out granularity), "getBufferSize"); } public bool CanSampleRate(double sampleRate) { AsioError asioError = asioDriverVTable.canSampleRate(pAsioComObject, sampleRate); switch (asioError) { case AsioError.ASE_NoClock: return false; case AsioError.ASE_OK: return true; default: HandleException(asioError, "canSampleRate"); return false; } } public double GetSampleRate() { HandleException(asioDriverVTable.getSampleRate(pAsioComObject, out var sampleRate), "getSampleRate"); return sampleRate; } public void SetSampleRate(double sampleRate) { HandleException(asioDriverVTable.setSampleRate(pAsioComObject, sampleRate), "setSampleRate"); } public void GetClockSources(out long clocks, int numSources) { HandleException(asioDriverVTable.getClockSources(pAsioComObject, out clocks, numSources), "getClockSources"); } public void SetClockSource(int reference) { HandleException(asioDriverVTable.setClockSource(pAsioComObject, reference), "setClockSources"); } public void GetSamplePosition(out long samplePos, ref Asio64Bit timeStamp) { HandleException(asioDriverVTable.getSamplePosition(pAsioComObject, out samplePos, ref timeStamp), "getSamplePosition"); } public AsioChannelInfo GetChannelInfo(int channelNumber, bool trueForInputInfo) { AsioChannelInfo asioChannelInfo = default(AsioChannelInfo); asioChannelInfo.channel = channelNumber; asioChannelInfo.isInput = trueForInputInfo; AsioChannelInfo info = asioChannelInfo; HandleException(asioDriverVTable.getChannelInfo(pAsioComObject, ref info), "getChannelInfo"); return info; } public void CreateBuffers(IntPtr bufferInfos, int numChannels, int bufferSize, ref AsioCallbacks callbacks) { pinnedcallbacks = Marshal.AllocHGlobal(Marshal.SizeOf(callbacks)); Marshal.StructureToPtr(callbacks, pinnedcallbacks, fDeleteOld: false); HandleException(asioDriverVTable.createBuffers(pAsioComObject, bufferInfos, numChannels, bufferSize, pinnedcallbacks), "createBuffers"); } public AsioError DisposeBuffers() { AsioError result = asioDriverVTable.disposeBuffers(pAsioComObject); Marshal.FreeHGlobal(pinnedcallbacks); return result; } public void ControlPanel() { HandleException(asioDriverVTable.controlPanel(pAsioComObject), "controlPanel"); } public void Future(int selector, IntPtr opt) { HandleException(asioDriverVTable.future(pAsioComObject, selector, opt), "future"); } public AsioError OutputReady() { return asioDriverVTable.outputReady(pAsioComObject); } public void ReleaseComAsioDriver() { Marshal.Release(pAsioComObject); } private void HandleException(AsioError error, string methodName) { if (error != 0 && error != AsioError.ASE_SUCCESS) { throw new AsioException("Error code [" + AsioException.getErrorName(error) + "] while calling ASIO method <" + methodName + ">, " + GetErrorMessage()) { Error = error }; } } private void InitFromGuid(Guid asioGuid) { int num = CoCreateInstance(ref asioGuid, IntPtr.Zero, 1u, ref asioGuid, out pAsioComObject); if (num != 0) { throw new COMException("Unable to instantiate ASIO. Check if STAThread is set", num); } IntPtr ptr = Marshal.ReadIntPtr(pAsioComObject); asioDriverVTable = new AsioDriverVTable(); FieldInfo[] fields = typeof(AsioDriverVTable).GetFields(); for (int i = 0; i < fields.Length; i++) { FieldInfo fieldInfo = fields[i]; object delegateForFunctionPointer = Marshal.GetDelegateForFunctionPointer(Marshal.ReadIntPtr(ptr, (i + 3) * IntPtr.Size), fieldInfo.FieldType); fieldInfo.SetValue(asioDriverVTable, delegateForFunctionPointer); } } [DllImport("ole32.Dll")] private static extern int CoCreateInstance(ref Guid clsid, IntPtr inner, uint context, ref Guid uuid, out IntPtr rReturnedComObject); } public class AsioDriverCapability { public string DriverName; public int NbInputChannels; public int NbOutputChannels; public int InputLatency; public int OutputLatency; public int BufferMinSize; public int BufferMaxSize; public int BufferPreferredSize; public int BufferGranularity; public double SampleRate; public AsioChannelInfo[] InputChannelInfos; public AsioChannelInfo[] OutputChannelInfos; } public delegate void AsioFillBufferCallback(IntPtr[] inputChannels, IntPtr[] outputChannels); public class AsioDriverExt { private readonly AsioDriver driver; private AsioCallbacks callbacks; private AsioDriverCapability capability; private AsioBufferInfo[] bufferInfos; private bool isOutputReadySupported; private IntPtr[] currentOutputBuffers; private IntPtr[] currentInputBuffers; private int numberOfOutputChannels; private int numberOfInputChannels; private AsioFillBufferCallback fillBufferCallback; private int bufferSize; private int outputChannelOffset; private int inputChannelOffset; public Action ResetRequestCallback; public AsioDriver Driver => driver; public AsioFillBufferCallback FillBufferCallback { get { return fillBufferCallback; } set { fillBufferCallback = value; } } public AsioDriverCapability Capabilities => capability; public AsioDriverExt(AsioDriver driver) { this.driver = driver; if (!driver.Init(IntPtr.Zero)) { throw new InvalidOperationException(driver.GetErrorMessage()); } callbacks = default(AsioCallbacks); callbacks.pasioMessage = AsioMessageCallBack; callbacks.pbufferSwitch = BufferSwitchCallBack; callbacks.pbufferSwitchTimeInfo = BufferSwitchTimeInfoCallBack; callbacks.psampleRateDidChange = SampleRateDidChangeCallBack; BuildCapabilities(); } public void SetChannelOffset(int outputChannelOffset, int inputChannelOffset) { if (outputChannelOffset + numberOfOutputChannels <= Capabilities.NbOutputChannels) { this.outputChannelOffset = outputChannelOffset; if (inputChannelOffset + numberOfInputChannels <= Capabilities.NbInputChannels) { this.inputChannelOffset = inputChannelOffset; return; } throw new ArgumentException("Invalid channel offset"); } throw new ArgumentException("Invalid channel offset"); } public void Start() { driver.Start(); } public void Stop() { driver.Stop(); } public void ShowControlPanel() { driver.ControlPanel(); } public void ReleaseDriver() { try { driver.DisposeBuffers(); } catch (Exception ex) { Console.Out.WriteLine(ex.ToString()); } driver.ReleaseComAsioDriver(); } public bool IsSampleRateSupported(double sampleRate) { return driver.CanSampleRate(sampleRate); } public void SetSampleRate(double sampleRate) { driver.SetSampleRate(sampleRate); BuildCapabilities(); } public unsafe int CreateBuffers(int numberOfOutputChannels, int numberOfInputChannels, bool useMaxBufferSize) { if (numberOfOutputChannels < 0 || numberOfOutputChannels > capability.NbOutputChannels) { throw new ArgumentException($"Invalid number of channels {numberOfOutputChannels}, must be in the range [0,{capability.NbOutputChannels}]"); } if (numberOfInputChannels < 0 || numberOfInputChannels > capability.NbInputChannels) { throw new ArgumentException("numberOfInputChannels", $"Invalid number of input channels {numberOfInputChannels}, must be in the range [0,{capability.NbInputChannels}]"); } this.numberOfOutputChannels = numberOfOutputChannels; this.numberOfInputChannels = numberOfInputChannels; int num = capability.NbInputChannels + capability.NbOutputChannels; bufferInfos = new AsioBufferInfo[num]; currentOutputBuffers = new IntPtr[numberOfOutputChannels]; currentInputBuffers = new IntPtr[numberOfInputChannels]; int num2 = 0; int num3 = 0; while (num3 < capability.NbInputChannels) { bufferInfos[num2].isInput = true; bufferInfos[num2].channelNum = num3; bufferInfos[num2].pBuffer0 = IntPtr.Zero; bufferInfos[num2].pBuffer1 = IntPtr.Zero; num3++; num2++; } int num4 = 0; while (num4 < capability.NbOutputChannels) { bufferInfos[num2].isInput = false; bufferInfos[num2].channelNum = num4; bufferInfos[num2].pBuffer0 = IntPtr.Zero; bufferInfos[num2].pBuffer1 = IntPtr.Zero; num4++; num2++; } if (useMaxBufferSize) { bufferSize = capability.BufferMaxSize; } else { bufferSize = capability.BufferPreferredSize; } fixed (AsioBufferInfo* value = &bufferInfos[0]) { IntPtr intPtr = new IntPtr(value); driver.CreateBuffers(intPtr, num, bufferSize, ref callbacks); } isOutputReadySupported = driver.OutputReady() == AsioError.ASE_OK; return bufferSize; } private void BuildCapabilities() { capability = new AsioDriverCapability(); capability.DriverName = driver.GetDriverName(); driver.GetChannels(out capability.NbInputChannels, out capability.NbOutputChannels); capability.InputChannelInfos = new AsioChannelInfo[capability.NbInputChannels]; capability.OutputChannelInfos = new AsioChannelInfo[capability.NbOutputChannels]; for (int i = 0; i < capability.NbInputChannels; i++) { capability.InputChannelInfos[i] = driver.GetChannelInfo(i, trueForInputInfo: true); } for (int j = 0; j < capability.NbOutputChannels; j++) { capability.OutputChannelInfos[j] = driver.GetChannelInfo(j, trueForInputInfo: false); } capability.SampleRate = driver.GetSampleRate(); AsioError latencies = driver.GetLatencies(out capability.InputLatency, out capability.OutputLatency); if (latencies != 0 && latencies != AsioError.ASE_NotPresent) { throw new AsioException("ASIOgetLatencies") { Error = latencies }; } driver.GetBufferSize(out capability.BufferMinSize, out capability.BufferMaxSize, out capability.BufferPreferredSize, out capability.BufferGranularity); } private void BufferSwitchCallBack(int doubleBufferIndex, bool directProcess) { for (int i = 0; i < numberOfInputChannels; i++) { currentInputBuffers[i] = bufferInfos[i + inputChannelOffset].Buffer(doubleBufferIndex); } for (int j = 0; j < numberOfOutputChannels; j++) { currentOutputBuffers[j] = bufferInfos[j + outputChannelOffset + capability.NbInputChannels].Buffer(doubleBufferIndex); } fillBufferCallback?.Invoke(currentInputBuffers, currentOutputBuffers); if (isOutputReadySupported) { driver.OutputReady(); } } private void SampleRateDidChangeCallBack(double sRate) { capability.SampleRate = sRate; } private int AsioMessageCallBack(AsioMessageSelector selector, int value, IntPtr message, IntPtr opt) { switch (selector) { case AsioMessageSelector.kAsioSelectorSupported: switch ((AsioMessageSelector)Enum.ToObject(typeof(AsioMessageSelector), value)) { case AsioMessageSelector.kAsioEngineVersion: return 1; case AsioMessageSelector.kAsioResetRequest: ResetRequestCallback?.Invoke(); return 0; case AsioMessageSelector.kAsioBufferSizeChange: return 0; case AsioMessageSelector.kAsioResyncRequest: return 0; case AsioMessageSelector.kAsioLatenciesChanged: return 0; case AsioMessageSelector.kAsioSupportsTimeInfo: return 0; case AsioMessageSelector.kAsioSupportsTimeCode: return 0; } break; case AsioMessageSelector.kAsioEngineVersion: return 2; case AsioMessageSelector.kAsioResetRequest: ResetRequestCallback?.Invoke(); return 1; case AsioMessageSelector.kAsioBufferSizeChange: return 0; case AsioMessageSelector.kAsioResyncRequest: return 0; case AsioMessageSelector.kAsioLatenciesChanged: return 0; case AsioMessageSelector.kAsioSupportsTimeInfo: return 0; case AsioMessageSelector.kAsioSupportsTimeCode: return 0; } return 0; } private IntPtr BufferSwitchTimeInfoCallBack(IntPtr asioTimeParam, int doubleBufferIndex, bool directProcess) { return IntPtr.Zero; } } public enum AsioError { ASE_OK = 0, ASE_SUCCESS = 1061701536, ASE_NotPresent = -1000, ASE_HWMalfunction = -999, ASE_InvalidParameter = -998, ASE_InvalidMode = -997, ASE_SPNotAdvancing = -996, ASE_NoClock = -995, ASE_NoMemory = -994 } public enum AsioMessageSelector { kAsioSelectorSupported = 1, kAsioEngineVersion, kAsioResetRequest, kAsioBufferSizeChange, kAsioResyncRequest, kAsioLatenciesChanged, kAsioSupportsTimeInfo, kAsioSupportsTimeCode, kAsioMMCCommand, kAsioSupportsInputMonitor, kAsioSupportsInputGain, kAsioSupportsInputMeter, kAsioSupportsOutputGain, kAsioSupportsOutputMeter, kAsioOverload } internal class AsioSampleConvertor { public delegate void SampleConvertor(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples); public static SampleConvertor SelectSampleConvertor(WaveFormat waveFormat, AsioSampleType asioType) { SampleConvertor result = null; bool flag = waveFormat.Channels == 2; switch (asioType) { case AsioSampleType.Int32LSB: switch (waveFormat.BitsPerSample) { case 16: result = (flag ? new SampleConvertor(ConvertorShortToInt2Channels) : new SampleConvertor(ConvertorShortToIntGeneric)); break; case 32: result = ((waveFormat.Encoding != WaveFormatEncoding.IeeeFloat) ? (flag ? new SampleConvertor(ConvertorIntToInt2Channels) : new SampleConvertor(ConvertorIntToIntGeneric)) : (flag ? new SampleConvertor(ConvertorFloatToInt2Channels) : new SampleConvertor(ConvertorFloatToIntGeneric))); break; } break; case AsioSampleType.Int16LSB: switch (waveFormat.BitsPerSample) { case 16: result = (flag ? new SampleConvertor(ConvertorShortToShort2Channels) : new SampleConvertor(ConvertorShortToShortGeneric)); break; case 32: result = ((waveFormat.Encoding != WaveFormatEncoding.IeeeFloat) ? (flag ? new SampleConvertor(ConvertorIntToShort2Channels) : new SampleConvertor(ConvertorIntToShortGeneric)) : (flag ? new SampleConvertor(ConvertorFloatToShort2Channels) : new SampleConvertor(ConvertorFloatToShortGeneric))); break; } break; case AsioSampleType.Int24LSB: switch (waveFormat.BitsPerSample) { case 16: throw new ArgumentException("Not a supported conversion"); case 32: if (waveFormat.Encoding == WaveFormatEncoding.IeeeFloat) { result = ConverterFloatTo24LSBGeneric; break; } throw new ArgumentException("Not a supported conversion"); } break; case AsioSampleType.Float32LSB: switch (waveFormat.BitsPerSample) { case 16: throw new ArgumentException("Not a supported conversion"); case 32: result = ((waveFormat.Encoding != WaveFormatEncoding.IeeeFloat) ? new SampleConvertor(ConvertorIntToFloatGeneric) : new SampleConvertor(ConverterFloatToFloatGeneric)); break; } break; default: throw new ArgumentException($"ASIO Buffer Type {Enum.GetName(typeof(AsioSampleType), asioType)} is not yet supported."); } return result; } public unsafe static void ConvertorShortToInt2Channels(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { short* ptr = (short*)(void*)inputInterleavedBuffer; short* ptr2 = (short*)(void*)asioOutputBuffers[0]; short* ptr3 = (short*)(void*)asioOutputBuffers[1]; ptr2++; ptr3++; for (int i = 0; i < nbSamples; i++) { *ptr2 = *ptr; *ptr3 = ptr[1]; ptr += 2; ptr2 += 2; ptr3 += 2; } } public unsafe static void ConvertorShortToIntGeneric(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { short* ptr = (short*)(void*)inputInterleavedBuffer; short*[] array = new short*[nbChannels]; for (int i = 0; i < nbChannels; i++) { array[i] = (short*)(void*)asioOutputBuffers[i]; int num = i; short* ptr2 = array[num]; array[num] = ptr2 + 1; } for (int j = 0; j < nbSamples; j++) { for (int k = 0; k < nbChannels; k++) { *array[k] = *(ptr++); short*[] array2 = array; int num = k; array2[num] += 2; } } } public unsafe static void ConvertorFloatToInt2Channels(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { float* ptr = (float*)(void*)inputInterleavedBuffer; int* ptr2 = (int*)(void*)asioOutputBuffers[0]; int* ptr3 = (int*)(void*)asioOutputBuffers[1]; for (int i = 0; i < nbSamples; i++) { *(ptr2++) = clampToInt(*ptr); *(ptr3++) = clampToInt(ptr[1]); ptr += 2; } } public unsafe static void ConvertorFloatToIntGeneric(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { float* ptr = (float*)(void*)inputInterleavedBuffer; int*[] array = new int*[nbChannels]; for (int i = 0; i < nbChannels; i++) { array[i] = (int*)(void*)asioOutputBuffers[i]; } for (int j = 0; j < nbSamples; j++) { for (int k = 0; k < nbChannels; k++) { int num = k; int* ptr2 = array[num]; array[num] = ptr2 + 1; *ptr2 = clampToInt(*(ptr++)); } } } public unsafe static void ConvertorIntToInt2Channels(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { int* ptr = (int*)(void*)inputInterleavedBuffer; int* ptr2 = (int*)(void*)asioOutputBuffers[0]; int* ptr3 = (int*)(void*)asioOutputBuffers[1]; for (int i = 0; i < nbSamples; i++) { *(ptr2++) = *ptr; *(ptr3++) = ptr[1]; ptr += 2; } } public unsafe static void ConvertorIntToIntGeneric(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { int* ptr = (int*)(void*)inputInterleavedBuffer; int*[] array = new int*[nbChannels]; for (int i = 0; i < nbChannels; i++) { array[i] = (int*)(void*)asioOutputBuffers[i]; } for (int j = 0; j < nbSamples; j++) { for (int k = 0; k < nbChannels; k++) { int num = k; int* ptr2 = array[num]; array[num] = ptr2 + 1; *ptr2 = *(ptr++); } } } public unsafe static void ConvertorIntToShort2Channels(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { int* ptr = (int*)(void*)inputInterleavedBuffer; short* ptr2 = (short*)(void*)asioOutputBuffers[0]; short* ptr3 = (short*)(void*)asioOutputBuffers[1]; for (int i = 0; i < nbSamples; i++) { *(ptr2++) = (short)(*ptr / 65536); *(ptr3++) = (short)(ptr[1] / 65536); ptr += 2; } } public unsafe static void ConvertorIntToShortGeneric(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { int* ptr = (int*)(void*)inputInterleavedBuffer; int*[] array = new int*[nbChannels]; for (int i = 0; i < nbChannels; i++) { array[i] = (int*)(void*)asioOutputBuffers[i]; } for (int j = 0; j < nbSamples; j++) { for (int k = 0; k < nbChannels; k++) { int num = k; int* ptr2 = array[num]; array[num] = ptr2 + 1; *ptr2 = (short)(*(ptr++) / 65536); } } } public unsafe static void ConvertorIntToFloatGeneric(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { int* ptr = (int*)(void*)inputInterleavedBuffer; float*[] array = new float*[nbChannels]; for (int i = 0; i < nbChannels; i++) { array[i] = (float*)(void*)asioOutputBuffers[i]; } for (int j = 0; j < nbSamples; j++) { for (int k = 0; k < nbChannels; k++) { int num = k; float* ptr2 = array[num]; array[num] = ptr2 + 1; *ptr2 = *(ptr++) / int.MinValue; } } } public unsafe static void ConvertorShortToShort2Channels(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { short* ptr = (short*)(void*)inputInterleavedBuffer; short* ptr2 = (short*)(void*)asioOutputBuffers[0]; short* ptr3 = (short*)(void*)asioOutputBuffers[1]; for (int i = 0; i < nbSamples; i++) { *(ptr2++) = *ptr; *(ptr3++) = ptr[1]; ptr += 2; } } public unsafe static void ConvertorShortToShortGeneric(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { short* ptr = (short*)(void*)inputInterleavedBuffer; short*[] array = new short*[nbChannels]; for (int i = 0; i < nbChannels; i++) { array[i] = (short*)(void*)asioOutputBuffers[i]; } for (int j = 0; j < nbSamples; j++) { for (int k = 0; k < nbChannels; k++) { int num = k; short* ptr2 = array[num]; array[num] = ptr2 + 1; *ptr2 = *(ptr++); } } } public unsafe static void ConvertorFloatToShort2Channels(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { float* ptr = (float*)(void*)inputInterleavedBuffer; short* ptr2 = (short*)(void*)asioOutputBuffers[0]; short* ptr3 = (short*)(void*)asioOutputBuffers[1]; for (int i = 0; i < nbSamples; i++) { *(ptr2++) = clampToShort(*ptr); *(ptr3++) = clampToShort(ptr[1]); ptr += 2; } } public unsafe static void ConvertorFloatToShortGeneric(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { float* ptr = (float*)(void*)inputInterleavedBuffer; short*[] array = new short*[nbChannels]; for (int i = 0; i < nbChannels; i++) { array[i] = (short*)(void*)asioOutputBuffers[i]; } for (int j = 0; j < nbSamples; j++) { for (int k = 0; k < nbChannels; k++) { int num = k; short* ptr2 = array[num]; array[num] = ptr2 + 1; *ptr2 = clampToShort(*(ptr++)); } } } public unsafe static void ConverterFloatTo24LSBGeneric(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { float* ptr = (float*)(void*)inputInterleavedBuffer; byte*[] array = new byte*[nbChannels]; for (int i = 0; i < nbChannels; i++) { array[i] = (byte*)(void*)asioOutputBuffers[i]; } for (int j = 0; j < nbSamples; j++) { for (int k = 0; k < nbChannels; k++) { int num = clampTo24Bit(*(ptr++)); int num2 = k; *(array[num2]++) = (byte)num; num2 = k; *(array[num2]++) = (byte)(num >> 8); num2 = k; *(array[num2]++) = (byte)(num >> 16); } } } public unsafe static void ConverterFloatToFloatGeneric(IntPtr inputInterleavedBuffer, IntPtr[] asioOutputBuffers, int nbChannels, int nbSamples) { float* ptr = (float*)(void*)inputInterleavedBuffer; float*[] array = new float*[nbChannels]; for (int i = 0; i < nbChannels; i++) { array[i] = (float*)(void*)asioOutputBuffers[i]; } for (int j = 0; j < nbSamples; j++) { for (int k = 0; k < nbChannels; k++) { int num = k; float* ptr2 = array[num]; array[num] = ptr2 + 1; *ptr2 = *(ptr++); } } } private static int clampTo24Bit(double sampleValue) { sampleValue = ((sampleValue < -1.0) ? (-1.0) : ((sampleValue > 1.0) ? 1.0 : sampleValue)); return (int)(sampleValue * 8388607.0); } private static int clampToInt(double sampleValue) { sampleValue = ((sampleValue < -1.0) ? (-1.0) : ((sampleValue > 1.0) ? 1.0 : sampleValue)); return (int)(sampleValue * 2147483647.0); } private static short clampToShort(double sampleValue) { sampleValue = ((sampleValue < -1.0) ? (-1.0) : ((sampleValue > 1.0) ? 1.0 : sampleValue)); return (short)(sampleValue * 32767.0); } } public enum AsioSampleType { Int16MSB = 0, Int24MSB = 1, Int32MSB = 2, Float32MSB = 3, Float64MSB = 4, Int32MSB16 = 8, Int32MSB18 = 9, Int32MSB20 = 10, Int32MSB24 = 11, Int16LSB = 16, Int24LSB = 17, Int32LSB = 18, Float32LSB = 19, Float64LSB = 20, Int32LSB16 = 24, Int32LSB18 = 25, Int32LSB20 = 26, Int32LSB24 = 27, DSDInt8LSB1 = 32, DSDInt8MSB1 = 33, DSDInt8NER8 = 40 } internal class AsioException : Exception { private AsioError error; public AsioError Error { get { return error; } set { error = value; Data["ASIOError"] = error; } } public AsioException() { } public AsioException(string message) : base(message) { } public AsioException(string message, Exception innerException) : base(message, innerException) { } public static string getErrorName(AsioError error) { return Enum.GetName(typeof(AsioError), error); } } [StructLayout(LayoutKind.Sequential, Pack = 4)] internal struct AsioBufferInfo { public bool isInput; public int channelNum; public IntPtr pBuffer0; public IntPtr pBuffer1; public IntPtr Buffer(int bufferIndex) { if (bufferIndex != 0) { return pBuffer1; } return pBuffer0; } } [StructLayout(LayoutKind.Sequential, Pack = 4)] internal struct AsioTimeCode { public double speed; public Asio64Bit timeCodeSamples; public AsioTimeCodeFlags flags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string future; } [Flags] internal enum AsioTimeCodeFlags { kTcValid = 1, kTcRunning = 2, kTcReverse = 4, kTcOnspeed = 8, kTcStill = 0x10, kTcSpeedValid = 0x100 } [StructLayout(LayoutKind.Sequential, Pack = 4)] internal struct AsioTimeInfo { public double speed; public Asio64Bit systemTime; public Asio64Bit samplePosition; public double sampleRate; public AsioTimeInfoFlags flags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 12)] public string reserved; } [Flags] internal enum AsioTimeInfoFlags { kSystemTimeValid = 1, kSamplePositionValid = 2, kSampleRateValid = 4, kSpeedValid = 8, kSampleRateChanged = 0x10, kClockSourceChanged = 0x20 } [StructLayout(LayoutKind.Sequential, Pack = 4)] internal struct AsioTime { public int reserved1; public int reserved2; public int reserved3; public int reserved4; public AsioTimeInfo timeInfo; public AsioTimeCode timeCode; } } namespace NAudio { public enum Manufacturers { Microsoft = 1, Creative = 2, Mediavision = 3, Fujitsu = 4, Artisoft = 20, TurtleBeach = 21, Ibm = 22, Vocaltec = 23, Roland = 24, DspSolutions = 25, Nec = 26, Ati = 27, Wanglabs = 28, Tandy = 29, Voyetra = 30, Antex = 31, IclPS = 32, Intel = 33, Gravis = 34, Val = 35, Interactive = 36, Yamaha = 37, Everex = 38, Echo = 39, Sierra = 40, Cat = 41, Apps = 42, DspGroup = 43, Melabs = 44, ComputerFriends = 45, Ess = 46, Audiofile = 47, Motorola = 48, Canopus = 49, Epson = 50, Truevision = 51, Aztech = 52, Videologic = 53, Scalacs = 54, Korg = 55, Apt = 56, Ics = 57, Iteratedsys = 58, Metheus = 59, Logitech = 60, Winnov = 61, Ncr = 62, Exan = 63, Ast = 64, Willowpond = 65, Sonicfoundry = 66, Vitec = 67, Moscom = 68, Siliconsoft = 69, Supermac = 73, Audiopt = 74, Speechcomp = 76, Ahead = 77, Dolby = 78, Oki = 79, Auravision = 80, Olivetti = 81, Iomagic = 82, Matsushita = 83, Controlres = 84, Xebec = 85, Newmedia = 86, Nms = 87, Lyrrus = 88, Compusic = 89, Opti = 90, Adlacc = 91, Compaq = 92, Dialogic = 93, Insoft = 94, Mptus = 95, Weitek = 96, LernoutAndHauspie = 97, Qciar = 98, Apple = 99, Digital = 100, Motu = 101, Workbit = 102, Ositech = 103, Miro = 104, Cirruslogic = 105, Isolution = 106, Horizons = 107, Concepts = 108, Vtg = 109, Radius = 110, Rockwell = 111, Xyz = 112, Opcode = 113, Voxware = 114, NorthernTelecom = 115, Apicom = 116, Grande = 117, Addx = 118, Wildcat = 119, Rhetorex = 120, Brooktree = 121, Ensoniq = 125, Fast = 126, Nvidia = 127, Oksori = 128, Diacoustics = 129, Gulbransen = 130, KayElemetrics = 131, Crystal = 132, SplashStudios = 133, Quarterdeck = 134, Tdk = 135, DigitalAudioLabs = 136, Seersys = 137, Picturetel = 138, AttMicroelectronics = 139, Osprey = 140, Mediatrix = 141, Soundesigns = 142, Aldigital = 143, SpectrumSignalProcessing = 144, Ecs = 145, Amd = 146, Coredynamics = 147, Canam = 148, Softsound = 149, Norris = 150, Ddd = 151, Euphonics = 152, Precept = 153, CrystalNet = 154, Chromatic = 155, Voiceinfo = 156, Viennasys = 157, Connectix = 158, Gadgetlabs = 159, Frontier = 160, Viona = 161, Casio = 162, Diamondmm = 163, S3 = 164, FraunhoferIis = 172 } public class MmException : Exception { public MmResult Result { get; } public string Function { get; } public MmException(MmResult result, string function) : base(ErrorMessage(result, function)) { Result = result; Function = function; } private static string ErrorMessage(MmResult result, string function) { return $"{result} calling {function}"; } public static void Try(MmResult result, string function) { if (result != 0) { throw new MmException(result, function); } } } public enum MmResult { NoError = 0, UnspecifiedError = 1, BadDeviceId = 2, NotEnabled = 3, AlreadyAllocated = 4, InvalidHandle = 5, NoDriver = 6, MemoryAllocationError = 7, NotSupported = 8, BadErrorNumber = 9, InvalidFlag = 10, InvalidParameter = 11, HandleBusy = 12, InvalidAlias = 13, BadRegistryDatabase = 14, RegistryKeyNotFound = 15, RegistryReadError = 16, RegistryWriteError = 17, RegistryDeleteError = 18, RegistryValueNotFound = 19, NoDriverCallback = 20, MoreData = 21, WaveBadFormat = 32, WaveStillPlaying = 33, WaveHeaderUnprepared = 34, WaveSync = 35, AcmNotPossible = 512, AcmBusy = 513, AcmHeaderUnprepared = 514, AcmCancelled = 515, MixerInvalidLine = 1024, MixerInvalidControl = 1025, MixerInvalidValue = 1026 } } namespace NAudio.CoreAudioApi { public enum CaptureState { Stopped, Starting, Capturing, Stopping } } namespace NAudio.Dmo { public class AudioMediaSubtypes { public static readonly Guid MEDIASUBTYPE_PCM = new Guid("00000001-0000-0010-8000-00AA00389B71"); public static readonly Guid MEDIASUBTYPE_PCMAudioObsolete = new Guid("e436eb8a-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_MPEG1Packet = new Guid("e436eb80-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_MPEG1Payload = new Guid("e436eb81-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_MPEG2_AUDIO = new Guid("e06d802b-db46-11cf-b4d1-00805f6cbbea"); public static readonly Guid MEDIASUBTYPE_DVD_LPCM_AUDIO = new Guid("e06d8032-db46-11cf-b4d1-00805f6cbbea"); public static readonly Guid MEDIASUBTYPE_DRM_Audio = new Guid("00000009-0000-0010-8000-00aa00389b71"); public static readonly Guid MEDIASUBTYPE_IEEE_FLOAT = new Guid("00000003-0000-0010-8000-00aa00389b71"); public static readonly Guid MEDIASUBTYPE_DOLBY_AC3 = new Guid("e06d802c-db46-11cf-b4d1-00805f6cbbea"); public static readonly Guid MEDIASUBTYPE_DOLBY_AC3_SPDIF = new Guid("00000092-0000-0010-8000-00aa00389b71"); public static readonly Guid MEDIASUBTYPE_RAW_SPORT = new Guid("00000240-0000-0010-8000-00aa00389b71"); public static readonly Guid MEDIASUBTYPE_SPDIF_TAG_241h = new Guid("00000241-0000-0010-8000-00aa00389b71"); public static readonly Guid MEDIASUBTYPE_I420 = new Guid("30323449-0000-0010-8000-00AA00389B71"); public static readonly Guid MEDIASUBTYPE_IYUV = new Guid("56555949-0000-0010-8000-00AA00389B71"); public static readonly Guid MEDIASUBTYPE_RGB1 = new Guid("e436eb78-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_RGB24 = new Guid("e436eb7d-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_RGB32 = new Guid("e436eb7e-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_RGB4 = new Guid("e436eb79-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_RGB555 = new Guid("e436eb7c-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_RGB565 = new Guid("e436eb7b-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_RGB8 = new Guid("e436eb7a-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_UYVY = new Guid("59565955-0000-0010-8000-00AA00389B71"); public static readonly Guid MEDIASUBTYPE_VIDEOIMAGE = new Guid("1d4a45f2-e5f6-4b44-8388-f0ae5c0e0c37"); public static readonly Guid MEDIASUBTYPE_YUY2 = new Guid("32595559-0000-0010-8000-00AA00389B71"); public static readonly Guid MEDIASUBTYPE_YV12 = new Guid("31313259-0000-0010-8000-00AA00389B71"); public static readonly Guid MEDIASUBTYPE_YVU9 = new Guid("39555659-0000-0010-8000-00AA00389B71"); public static readonly Guid MEDIASUBTYPE_YVYU = new Guid("55595659-0000-0010-8000-00AA00389B71"); public static readonly Guid WMFORMAT_MPEG2Video = new Guid("e06d80e3-db46-11cf-b4d1-00805f6cbbea"); public static readonly Guid WMFORMAT_Script = new Guid("5C8510F2-DEBE-4ca7-BBA5-F07A104F8DFF"); public static readonly Guid WMFORMAT_VideoInfo = new Guid("05589f80-c356-11ce-bf01-00aa0055595a"); public static readonly Guid WMFORMAT_WaveFormatEx = new Guid("05589f81-c356-11ce-bf01-00aa0055595a"); public static readonly Guid WMFORMAT_WebStream = new Guid("da1e6b13-8359-4050-b398-388e965bf00c"); public static readonly Guid WMMEDIASUBTYPE_ACELPnet = new Guid("00000130-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_Base = new Guid("00000000-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_DRM = new Guid("00000009-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_MP3 = new Guid("00000055-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_MP43 = new Guid("3334504D-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_MP4S = new Guid("5334504D-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_M4S2 = new Guid("3253344D-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_P422 = new Guid("32323450-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_MPEG2_VIDEO = new Guid("e06d8026-db46-11cf-b4d1-00805f6cbbea"); public static readonly Guid WMMEDIASUBTYPE_MSS1 = new Guid("3153534D-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_MSS2 = new Guid("3253534D-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_PCM = new Guid("00000001-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WebStream = new Guid("776257d4-c627-41cb-8f81-7ac7ff1c40cc"); public static readonly Guid WMMEDIASUBTYPE_WMAudio_Lossless = new Guid("00000163-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMAudioV2 = new Guid("00000161-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMAudioV7 = new Guid("00000161-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMAudioV8 = new Guid("00000161-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMAudioV9 = new Guid("00000162-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMSP1 = new Guid("0000000A-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMV1 = new Guid("31564D57-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMV2 = new Guid("32564D57-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMV3 = new Guid("33564D57-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMVA = new Guid("41564D57-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WMVP = new Guid("50564D57-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIASUBTYPE_WVP2 = new Guid("32505657-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIATYPE_Audio = new Guid("73647561-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIATYPE_FileTransfer = new Guid("D9E47579-930E-4427-ADFC-AD80F290E470"); public static readonly Guid WMMEDIATYPE_Image = new Guid("34A50FD8-8AA5-4386-81FE-A0EFE0488E31"); public static readonly Guid WMMEDIATYPE_Script = new Guid("73636d64-0000-0010-8000-00AA00389B71"); public static readonly Guid WMMEDIATYPE_Text = new Guid("9BBA1EA7-5AB2-4829-BA57-0940209BCF3E"); public static readonly Guid WMMEDIATYPE_Video = new Guid("73646976-0000-0010-8000-00AA00389B71"); public static readonly Guid WMSCRIPTTYPE_TwoStrings = new Guid("82f38a70-c29f-11d1-97ad-00a0c95ea850"); public static readonly Guid MEDIASUBTYPE_WAVE = new Guid("e436eb8b-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_AU = new Guid("e436eb8c-524f-11ce-9f53-0020af0ba770"); public static readonly Guid MEDIASUBTYPE_AIFF = new Guid("e436eb8d-524f-11ce-9f53-0020af0ba770"); public static readonly Guid[] AudioSubTypes = new Guid[13] { MEDIASUBTYPE_PCM, MEDIASUBTYPE_PCMAudioObsolete, MEDIASUBTYPE_MPEG1Packet, MEDIASUBTYPE_MPEG1Payload, MEDIASUBTYPE_MPEG2_AUDIO, MEDIASUBTYPE_DVD_LPCM_AUDIO, MEDIASUBTYPE_DRM_Audio, MEDIASUBTYPE_IEEE_FLOAT, MEDIASUBTYPE_DOLBY_AC3, MEDIASUBTYPE_DOLBY_AC3_SPDIF, MEDIASUBTYPE_RAW_SPORT, MEDIASUBTYPE_SPDIF_TAG_241h, WMMEDIASUBTYPE_MP3 }; public static readonly string[] AudioSubTypeNames = new string[13] { "PCM", "PCM Obsolete", "MPEG1Packet", "MPEG1Payload", "MPEG2_AUDIO", "DVD_LPCM_AUDIO", "DRM_Audio", "IEEE_FLOAT", "DOLBY_AC3", "DOLBY_AC3_SPDIF", "RAW_SPORT", "SPDIF_TAG_241h", "MP3" }; public static string GetAudioSubtypeName(Guid subType) { for (int i = 0; i < AudioSubTypes.Length; i++) { if (subType == AudioSubTypes[i]) { return AudioSubTypeNames[i]; } } return subType.ToString(); } } } namespace NAudio.Utils { public static class BufferHelpers { public static byte[] Ensure(byte[] buffer, int bytesRequired) { if (buffer == null || buffer.Length < bytesRequired) { buffer = new byte[bytesRequired]; } return buffer; } public static float[] Ensure(float[] buffer, int samplesRequired) { if (buffer == null || buffer.Length < samplesRequired) { buffer = new float[samplesRequired]; } return buffer; } } public static class ByteArrayExtensions { public static bool IsEntirelyNull(byte[] buffer) { for (int i = 0; i < buffer.Length; i++) { if (buffer[i] != 0) { return false; } } return true; } public static string DescribeAsHex(byte[] buffer, string separator, int bytesPerLine) { StringBuilder stringBuilder = new StringBuilder(); int num = 0; foreach (byte b in buffer) { stringBuilder.AppendFormat("{0:X2}{1}", b, separator); if (++num % bytesPerLine == 0) { stringBuilder.Append("\r\n"); } } stringBuilder.Append("\r\n"); return stringBuilder.ToString(); } public static string DecodeAsString(byte[] buffer, int offset, int length, Encoding encoding) { for (int i = 0; i < length; i++) { if (buffer[offset + i] == 0) { length = i; } } return encoding.GetString(buffer, offset, length); } public static byte[] Concat(params byte[][] byteArrays) { int num = 0; byte[][] array = byteArrays; foreach (byte[] array2 in array) { num += array2.Length; } if (num <= 0) { return new byte[0]; } byte[] array3 = new byte[num]; int num2 = 0; array = byteArrays; foreach (byte[] array4 in array) { Array.Copy(array4, 0, array3, num2, array4.Length); num2 += array4.Length; } return array3; } } public class ByteEncoding : Encoding { public static readonly ByteEncoding Instance = new ByteEncoding(); private ByteEncoding() { } public override int GetByteCount(char[] chars, int index, int count) { return count; } public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) { for (int i = 0; i < charCount; i++) { bytes[byteIndex + i] = (byte)chars[charIndex + i]; } return charCount; } public override int GetCharCount(byte[] bytes, int index, int count) { for (int i = 0; i < count; i++) { if (bytes[index + i] == 0) { return i; } } return count; } public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) { for (int i = 0; i < byteCount; i++) { byte b = bytes[byteIndex + i]; if (b == 0) { return i; } chars[charIndex + i] = (char)b; } return byteCount; } public override int GetMaxCharCount(int byteCount) { return byteCount; } public override int GetMaxByteCount(int charCount) { return charCount; } } public class ChunkIdentifier { public static int ChunkIdentifierToInt32(string s) { if (s.Length != 4) { throw new ArgumentException("Must be a four character string"); } byte[] bytes = Encoding.UTF8.GetBytes(s); if (bytes.Length != 4) { throw new ArgumentException("Must encode to exactly four bytes"); } return BitConverter.ToInt32(bytes, 0); } } public class CircularBuffer { private readonly byte[] buffer; private readonly object lockObject; private int writePosition; private int readPosition; private int byteCount; public int MaxLength => buffer.Length; public int Count { get { lock (lockObject) { return byteCount; } } } public CircularBuffer(int size) { buffer = new byte[size]; lockObject = new object(); } public int Write(byte[] data, int offset, int count) { lock (lockObject) { int num = 0; if (count > buffer.Length - byteCount) { count = buffer.Length - byteCount; } int num2 = Math.Min(buffer.Length - writePosition, count); Array.Copy(data, offset, buffer, writePosition, num2); writePosition += num2; writePosition %= buffer.Length; num += num2; if (num < count) { Array.Copy(data, offset + num, buffer, writePosition, count - num); writePosition += count - num; num = count; } byteCount += num; return num; } } public int Read(byte[] data, int offset, int count) { lock (lockObject) { if (count > byteCount) { count = byteCount; } int num = 0; int num2 = Math.Min(buffer.Length - readPosition, count); Array.Copy(buffer, readPosition, data, offset, num2); num += num2; readPosition += num2; readPosition %= buffer.Length; if (num < count) { Array.Copy(buffer, readPosition, data, offset + num, count - num); readPosition += count - num; num = count; } byteCount -= num; return num; } } public void Reset() { lock (lockObject) { ResetInner(); } } private void ResetInner() { byteCount = 0; readPosition = 0; writePosition = 0; } public void Advance(int count) { lock (lockObject) { if (count >= byteCount) { ResetInner(); return; } byteCount -= count; readPosition += count; readPosition %= MaxLength; } } } public class Decibels { private const double LOG_2_DB = 8.685889638065037; private const double DB_2_LOG = 0.11512925464970228; public static double LinearToDecibels(double lin) { return Math.Log(lin) * 8.685889638065037; } public static double DecibelsToLinear(double dB) { return Math.Exp(dB * 0.11512925464970228); } } [AttributeUsage(AttributeTargets.Field)] public class FieldDescriptionAttribute : Attribute { public string Description { get; } public FieldDescriptionAttribute(string description) { Description = description; } public override string ToString() { return Description; } } public static class FieldDescriptionHelper { public static string Describe(Type t, Guid guid) { FieldInfo[] fields = t.GetFields(BindingFlags.Static | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (!fieldInfo.IsPublic || !fieldInfo.IsStatic || !(fieldInfo.FieldType == typeof(Guid)) || !((Guid)fieldInfo.GetValue(null) == guid)) { continue; } object[] customAttributes = fieldInfo.GetCustomAttributes(inherit: false); for (int j = 0; j < customAttributes.Length; j++) { if (customAttributes[j] is FieldDescriptionAttribute fieldDescriptionAttribute) { return fieldDescriptionAttribute.Description; } } return fieldInfo.Name; } return guid.ToString(); } } public static class HResult { public const int S_OK = 0; public const int S_FALSE = 1; public const int E_INVALIDARG = -2147483645; private const int FACILITY_AAF = 18; private const int FACILITY_ACS = 20; private const int FACILITY_BACKGROUNDCOPY = 32; private const int FACILITY_CERT = 11; private const int FACILITY_COMPLUS = 17; private const int FACILITY_CONFIGURATION = 33; private const int FACILITY_CONTROL = 10; private const int FACILITY_DISPATCH = 2; private const int FACILITY_DPLAY = 21; private const int FACILITY_HTTP = 25; private const int FACILITY_INTERNET = 12; private const int FACILITY_ITF = 4; private const int FACILITY_MEDIASERVER = 13; private const int FACILITY_MSMQ = 14; private const int FACILITY_NULL = 0; private const int FACILITY_RPC = 1; private const int FACILITY_SCARD = 16; private const int FACILITY_SECURITY = 9; private const int FACILITY_SETUPAPI = 15; private const int FACILITY_SSPI = 9; private const int FACILITY_STORAGE = 3; private const int FACILITY_SXS = 23; private const int FACILITY_UMI = 22; private const int FACILITY_URT = 19; private const int FACILITY_WIN32 = 7; private const int FACILITY_WINDOWS = 8; private const int FACILITY_WINDOWS_CE = 24; public static int MAKE_HRESULT(int sev, int fac, int code) { return (sev << 31) | (fac << 16) | code; } public static int GetHResult(this COMException exception) { return exception.ErrorCode; } } public static class IEEE { private static double UnsignedToFloat(ulong u) { return (double)(long)(u - int.MaxValue - 1) + 2147483648.0; } private static double ldexp(double x, int exp) { return x * Math.Pow(2.0, exp); } private static double frexp(double x, out int exp) { exp = (int)Math.Floor(Math.Log(x) / Math.Log(2.0)) + 1; return 1.0 - (Math.Pow(2.0, exp) - x) / Math.Pow(2.0, exp); } private static ulong FloatToUnsigned(double f) { return (ulong)((long)(f - 2147483648.0) + int.MaxValue + 1); } public static byte[] ConvertToIeeeExtended(double num) { int num2; if (num < 0.0) { num2 = 32768; num *= -1.0; } else { num2 = 0; } ulong num4; ulong num5; int num3; if (num == 0.0) { num3 = 0; num4 = 0uL; num5 = 0uL; } else { double num6 = frexp(num, out num3); if (num3 > 16384 || !(num6 < 1.0)) { num3 = num2 | 0x7FFF; num4 = 0uL; num5 = 0uL; } else { num3 += 16382; if (num3 < 0) { num6 = ldexp(num6, num3); num3 = 0; } num3 |= num2; num6 = ldexp(num6, 32); double num7 = Math.Floor(num6); num4 = FloatToUnsigned(num7); num6 = ldexp(num6 - num7, 32); num7 = Math.Floor(num6); num5 = FloatToUnsigned(num7); } } return new byte[10] { (byte)(num3 >> 8), (byte)num3, (byte)(num4 >> 24), (byte)(num4 >> 16), (byte)(num4 >> 8), (byte)num4, (byte)(num5 >> 24), (byte)(num5 >> 16), (byte)(num5 >> 8), (byte)num5 }; } public static double ConvertFromIeeeExtended(byte[] bytes) { if (bytes.Length != 10) { throw new Exception("Incorrect length for IEEE extended."); } int num = ((bytes[0] & 0x7F) << 8) | bytes[1]; uint num2 = (uint)((bytes[2] << 24) | (bytes[3] << 16) | (bytes[4] << 8) | bytes[5]); uint num3 = (uint)((bytes[6] << 24) | (bytes[7] << 16) | (bytes[8] << 8) | bytes[9]); double num4; if (num == 0 && num2 == 0 && num3 == 0) { num4 = 0.0; } else if (num == 32767) { num4 = double.NaN; } else { num -= 16383; num4 = ldexp(UnsignedToFloat(num2), num -= 31); num4 += ldexp(UnsignedToFloat(num3), num -= 32); } if ((bytes[0] & 0x80) == 128) { return 0.0 - num4; } return num4; } } public class IgnoreDisposeStream : Stream { public Stream SourceStream { get; private set; } public bool IgnoreDispose { get; set; } public override bool CanRead => SourceStream.CanRead; public override bool CanSeek => SourceStream.CanSeek; public override bool CanWrite => SourceStream.CanWrite; public override long Length => SourceStream.Length; public override long Position { get { return SourceStream.Position; } set { SourceStream.Position = value; } } public IgnoreDisposeStream(Stream sourceStream) { SourceStream = sourceStream; IgnoreDispose = true; } public override void Flush() { SourceStream.Flush(); } public override int Read(byte[] buffer, int offset, int count) { return SourceStream.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { return SourceStream.Seek(offset, origin); } public override void SetLength(long value) { SourceStream.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { SourceStream.Write(buffer, offset, count); } protected override void Dispose(bool disposing) { if (!IgnoreDispose) { SourceStream.Dispose(); SourceStream = null; } } } public static class NativeMethods { [DllImport("kernel32.dll")] public static extern IntPtr LoadLibrary(string dllToLoad); [DllImport("kernel32.dll")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); [DllImport("kernel32.dll")] public static extern bool FreeLibrary(IntPtr hModule); } public static class WavePositionExtensions { public static TimeSpan GetPositionTimeSpan(this IWavePosition @this) { return TimeSpan.FromMilliseconds((double)(@this.GetPosition() / (@this.OutputWaveFormat.Channels * @this.OutputWaveFormat.BitsPerSample / 8)) * 1000.0 / (double)@this.OutputWaveFormat.SampleRate); } } } namespace NAudio.FileFormats.Wav { public class WaveFileChunkReader { private WaveFormat waveFormat; private long dataChunkPosition; private long dataChunkLength; private List<RiffChunk> riffChunks; private readonly bool strictMode; private bool isRf64; private readonly bool storeAllChunks; private long riffSize; public WaveFormat WaveFormat => waveFormat; public long DataChunkPosition => dataChunkPosition; public long DataChunkLength => dataChunkLength; public List<RiffChunk> RiffChunks => riffChunks; public WaveFileChunkReader() { storeAllChunks = true; strictMode = false; } public void ReadWaveHeader(Stream stream) { dataChunkPosition = -1L; waveFormat = null; riffChunks = new List<RiffChunk>(); dataChunkLength = 0L; BinaryReader binaryReader = new BinaryReader(stream); ReadRiffHeader(binaryReader); riffSize = binaryReader.ReadUInt32(); if (binaryReader.ReadInt32() != ChunkIdentifier.ChunkIdentifierToInt32("WAVE")) { throw new FormatException("Not a WAVE file - no WAVE header"); } if (isRf64) { ReadDs64Chunk(binaryReader); } int num = ChunkIdentifier.ChunkIdentifierToInt32("data"); int num2 = ChunkIdentifier.ChunkIdentifierToInt32("fmt "); long num3 = Math.Min(riffSize + 8, stream.Length); while (stream.Position <= num3 - 8) { int num4 = binaryReader.ReadInt32(); uint num5 = binaryReader.ReadUInt32(); if (num4 == num) { dataChunkPosition = stream.Position; if (!isRf64) { dataChunkLength = num5; } stream.Position += num5; } else if (num4 == num2) { if (num5 > int.MaxValue) { throw new InvalidDataException($"Format chunk length must be between 0 and {int.MaxValue}."); } waveFormat = WaveFormat.FromFormatChunk(binaryReader, (int)num5); } else { if (num5 > stream.Length - stream.Position) { if (!strictMode) { } break; } if (storeAllChunks) { if (num5 > int.MaxValue) { throw new InvalidDataException($"RiffChunk chunk length must be between 0 and {int.MaxValue}."); } riffChunks.Add(GetRiffChunk(stream, num4, (int)num5)); } stream.Position += num5; } if (num5 % 2 != 0 && binaryReader.PeekChar() == 0) { stream.Position++; } } if (waveFormat == null) { throw new FormatException("Invalid WAV file - No fmt chunk found"); } if (dataChunkPosition == -1) { throw new FormatException("Invalid WAV file - No data chunk found"); } } private void ReadDs64Chunk(BinaryReader reader) { int num = ChunkIdentifier.ChunkIdentifierToInt32("ds64"); if (reader.ReadInt32() != num) { throw new FormatException("Invalid RF64 WAV file - No ds64 chunk found"); } int num2 = reader.ReadInt32(); riffSize = reader.ReadInt64(); dataChunkLength = reader.ReadInt64(); reader.ReadInt64(); reader.ReadBytes(num2 - 24); } private static RiffChunk GetRiffChunk(Stream stream, int chunkIdentifier, int chunkLength) { return new RiffChunk(chunkIdentifier, chunkLength, stream.Position); } private void ReadRiffHeader(BinaryReader br) { int num = br.ReadInt32(); if (num == ChunkIdentifier.ChunkIdentifierToInt32("RF64")) { isRf64 = true; } else if (num != ChunkIdentifier.ChunkIdentifierToInt32("RIFF")) { throw new FormatException("Not a WAVE file - no RIFF header"); } } } } namespace NAudio.SoundFont { public class Generator { public GeneratorEnum GeneratorType { get; set; } public ushort UInt16Amount { get; set; } public short Int16Amount { get { return (short)UInt16Amount; } set { UInt16Amount = (ushort)value; } } public byte LowByteAmount { get { return (byte)(UInt16Amount & 0xFFu); } set { UInt16Amount &= 65280; UInt16Amount += value; } } public byte HighByteAmount { get { return (byte)((UInt16Amount & 0xFF00) >> 8); } set { UInt16Amount &= 255; UInt16Amount += (ushort)(value << 8); } } public Instrument Instrument { get; set; } public SampleHeader SampleHeader { get; set; } public override string ToString() { if (GeneratorType == GeneratorEnum.Instrument) { return "Generator Instrument " + Instrument.Name; } if (GeneratorType == GeneratorEnum.SampleID) { return $"Generator SampleID {SampleHeader}"; } return $"Generator {GeneratorType} {UInt16Amount}"; } } internal class GeneratorBuilder : StructureBuilder<Generator> { public override int Length => 4; public Generator[] Generators => data.ToArray(); public override Generator Read(BinaryReader br) { Generator generator = new Generator(); generator.GeneratorType = (GeneratorEnum)br.ReadUInt16(); generator.UInt16Amount = br.ReadUInt16(); data.Add(generator); return generator; } public override void Write(BinaryWriter bw, Generator o) { } public void Load(Instrument[] instruments) { Generator[] generators = Generators; foreach (Generator generator in generators) { if (generator.GeneratorType == GeneratorEnum.Instrument) { generator.Instrument = instruments[generator.UInt16Amount]; } } } public void Load(SampleHeader[] sampleHeaders) { Generator[] generators = Generators; foreach (Generator generator in generators) { if (generator.GeneratorType == GeneratorEnum.SampleID) { generator.SampleHeader = sampleHeaders[generator.UInt16Amount]; } } } } public enum GeneratorEnum { StartAddressOffset, EndAddressOffset, StartLoopAddressOffset, EndLoopAddressOffset, StartAddressCoarseOffset, ModulationLFOToPitch, VibratoLFOToPitch, ModulationEnvelopeToPitch, InitialFilterCutoffFrequency, InitialFilterQ, ModulationLFOToFilterCutoffFrequency, ModulationEnvelopeToFilterCutoffFrequency, EndAddressCoarseOffset, ModulationLFOToVolume, Unused1, ChorusEffectsSend, ReverbEffectsSend, Pan, Unused2, Unused3, Unused4, DelayModulationLFO, FrequencyModulationLFO, DelayVibratoLFO, FrequencyVibratoLFO, DelayModulationEnvelope, AttackModulationEnvelope, HoldModulationEnvelope, DecayModulationEnvelope, SustainModulationEnvelope, ReleaseModulationEnvelope, KeyNumberToModulationEnvelopeHold, KeyNumberToModulationEnvelopeDecay, DelayVolumeEnvelope, AttackVolumeEnvelope, HoldVolumeEnvelope, DecayVolumeEnvelope, SustainVolumeEnvelope, ReleaseVolumeEnvelope, KeyNumberToVolum