Decompiled source of SemiBoombox v1.2.0
SemiBoombox.dll
Decompiled a month 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