Decompiled source of NieRSoundtrack v2024.5.1
CustomSoundtrack.dll
Decompiled 8 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading; using BepInEx; using EntityStates.Missions.BrotherEncounter; using NAudio.Wave; using NAudio.Wave.SampleProviders; using Newtonsoft.Json; using On.EntityStates.Missions.BrotherEncounter; using On.RoR2; using RoR2; using RoR2.ConVar; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("CustomSoundtrack")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+ffc1f4d4a264fb049fc4efce57de29683d1e7201")] [assembly: AssemblyProduct("CustomSoundtrack")] [assembly: AssemblyTitle("CustomSoundtrack")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace CustomSoundtrack; [BepInPlugin("com.speeskees.customsoundtrack", "CustomSoundtrack", "2024.5.1")] public class CustomSoundtrack : BaseUnityPlugin { private string playbackMode = "next"; private string trackPath; private Dictionary<string, List<string>> bossPlaylists = new Dictionary<string, List<string>>(); private Dictionary<string, List<string>> playlists = new Dictionary<string, List<string>>(); private bool bossActive; private string currentScene = ""; private List<string> currentPlaylist = new List<string>(); private string currentTrack = ""; private bool paused; private bool start; private AudioFileReader currentAudioFileReader; private FadeInOutSampleProvider currentFadeInOutSampleProvider; private WaveOutEvent player = new WaveOutEvent(); private bool logging; private string logPath = "log.txt"; private bool locked; public void Awake() { //IL_047c: Unknown result type (might be due to invalid IL or missing references) //IL_0486: Expected O, but got Unknown //IL_048d: Unknown result type (might be due to invalid IL or missing references) //IL_0497: Expected O, but got Unknown //IL_049e: Unknown result type (might be due to invalid IL or missing references) //IL_04a8: Expected O, but got Unknown //IL_04af: Unknown result type (might be due to invalid IL or missing references) //IL_04b9: Expected O, but got Unknown //IL_04c0: Unknown result type (might be due to invalid IL or missing references) //IL_04ca: Expected O, but got Unknown //IL_04d1: Unknown result type (might be due to invalid IL or missing references) //IL_04db: Expected O, but got Unknown string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); logPath = Path.Combine(directoryName, logPath); trackPath = directoryName; if (!File.Exists(logPath)) { logging = true; } Settings settings = null; try { settings = JsonConvert.DeserializeObject<Settings>(File.ReadAllText(Path.Combine(directoryName, "settings.json"))); } catch (Exception ex) { log("could not parse settings.json: " + Environment.NewLine + ex); } if (settings != null) { if (settings.trackPath != null && settings.trackPath != "") { trackPath = settings.trackPath; } if (settings.playbackMode != null) { playbackMode = settings.playbackMode; } if (settings.playlists != null) { foreach (Dictionary<string, List<string>> playlist in settings.playlists) { if (!playlist.ContainsKey("scenes")) { continue; } foreach (string item in playlist["scenes"]) { if (!playlists.Keys.Contains(item)) { playlists.Add(item, new List<string>()); } if (playlist.ContainsKey("tracks")) { foreach (string item2 in playlist["tracks"]) { playlists[item].Add(Path.Combine(trackPath, item2)); } } if (!bossPlaylists.Keys.Contains(item)) { bossPlaylists.Add(item, new List<string>()); } if (!playlist.ContainsKey("bossTracks")) { continue; } foreach (string item3 in playlist["bossTracks"]) { bossPlaylists[item].Add(Path.Combine(trackPath, item3)); } } } } } playlists.Add("_default", new List<string>()); foreach (string item4 in from fileName in Directory.GetFiles(trackPath, "*", SearchOption.AllDirectories) where fileName.ToLower().EndsWith(".aiff") || fileName.ToLower().EndsWith(".mp3") || fileName.ToLower().EndsWith(".wav") select fileName) { playlists["_default"].Add(item4); } if (logging) { log("track directory: " + trackPath); log("playback mode: " + playbackMode); log("playlists: "); foreach (string key in playlists.Keys) { log(" " + key + ": "); foreach (string item5 in playlists[key]) { log(" " + item5); } } log("boss playlists: "); foreach (string key2 in bossPlaylists.Keys) { log(" " + key2 + ": "); foreach (string item6 in bossPlaylists[key2]) { log(" " + item6); } } } SceneManager.sceneLoaded += delegate(Scene scene, LoadSceneMode mode) { if (((Scene)(ref scene)).name == "splash") { start = true; } if (start && currentScene != ((Scene)(ref scene)).name) { bossActive = false; currentPlaylist = playlists["_default"]; currentScene = ((Scene)(ref scene)).name; nextPlaylist(); } if (logging) { log("loaded scene: " + ((Scene)(ref scene)).name); } }; TeleporterInteraction.OnInteractionBegin += (hook_OnInteractionBegin)delegate(orig_OnInteractionBegin orig, TeleporterInteraction self, Interactor activator) { orig.Invoke(self, activator); if (!bossActive) { bossActive = true; nextPlaylist(); } if (logging) { log("activated teleporter"); } }; TeleporterInteraction.RpcClientOnActivated += (hook_RpcClientOnActivated)delegate(orig_RpcClientOnActivated orig, TeleporterInteraction self, GameObject activator) { orig.Invoke(self, activator); if (!bossActive) { bossActive = true; nextPlaylist(); } if (logging) { log("activated teleporter"); } }; Phase1.OnEnter += (hook_OnEnter)delegate(orig_OnEnter orig, Phase1 self) { orig.Invoke(self); if (!bossActive) { bossActive = true; nextPlaylist(); } if (logging) { log("started mithrix fight"); } }; AudioFocusedOnlyConVar.OnApplicationFocus += (hook_OnApplicationFocus)delegate(orig_OnApplicationFocus orig, BaseConVar self, bool isFocussed) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Invalid comparison between Unknown and I4 orig.Invoke(self, isFocussed); if (isFocussed && (int)player.PlaybackState == 2) { paused = false; if (!locked) { player.Play(); } } if (!isFocussed && (int)player.PlaybackState == 1) { paused = true; if (!locked) { player.Pause(); } } }; MusicController.PickCurrentTrack += new hook_PickCurrentTrack(disableOriginalSoundtrack); MusicTrackOverride.PickMusicTrack += new hook_PickMusicTrack(disableOriginalSoundtrack); } private void disableOriginalSoundtrack(orig_PickMusicTrack orig, MusicController self, ref MusicTrackDef track) { orig.Invoke(self, ref track); track = null; } private void disableOriginalSoundtrack(orig_PickCurrentTrack orig, MusicController self, ref MusicTrackDef track) { orig.Invoke(self, ref track); track = null; } public void Update() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (start && !locked && (int)player.PlaybackState == 0) { nextTrack(); } applyVolume(); } private void nextPlaylist() { if (!bossActive) { if (playlists.ContainsKey(currentScene) && playlists[currentScene].Count > 0) { currentPlaylist = playlists[currentScene]; } } else { if (!bossPlaylists.ContainsKey(currentScene) || bossPlaylists[currentScene].Count <= 0) { return; } currentPlaylist = bossPlaylists[currentScene]; } if (playbackMode == "next") { nextTrack(); return; } Random randomizer = new Random(); currentPlaylist = currentPlaylist.OrderBy((string _) => randomizer.Next()).ToList(); if (playbackMode == "shuffle") { nextTrack(); return; } currentPlaylist = currentPlaylist.GetRange(0, 1); nextTrack(); } private void nextTrack() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (currentPlaylist.Count <= 0) { return; } string text = currentPlaylist[0]; if (!(text != currentTrack) && (int)player.PlaybackState != 0) { return; } FadeInOutSampleProvider val = currentFadeInOutSampleProvider; currentTrack = text; currentAudioFileReader = new AudioFileReader(currentTrack); currentFadeInOutSampleProvider = new FadeInOutSampleProvider((ISampleProvider)(object)currentAudioFileReader, false); currentPlaylist.Add(currentTrack); currentPlaylist.RemoveAt(0); applyVolume(); if (logging) { log("current track: " + currentTrack); log("current playlist: "); foreach (string item in currentPlaylist) { log(" " + item); } } if (locked) { return; } locked = true; FadeInOutSampleProvider newTrack = currentFadeInOutSampleProvider; FadeInOutSampleProvider oldTrack = val; new Thread((ThreadStart)delegate { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if ((int)player.PlaybackState != 0) { oldTrack.BeginFadeOut(1500.0); Thread.Sleep(1500); player.Stop(); } WaveExtensionMethods.Init((IWavePlayer)(object)player, (ISampleProvider)(object)newTrack, false); if (!paused) { player.Play(); } locked = false; }).Start(); } private void applyVolume() { if (currentAudioFileReader != null) { float.TryParse(((BaseConVar)AudioManager.cvVolumeMaster).GetString(), out var result); float.TryParse(((BaseConVar)AudioManager.cvVolumeMsx).GetString(), out var result2); result /= 100f; result2 /= 100f; currentAudioFileReader.Volume = result * result2; } } private void log(string message) { File.AppendAllText(logPath, "[" + DateTime.Now.ToString("hh:mm:ss") + "] " + message + Environment.NewLine); } } public class Settings { public string playbackMode { get; set; } public string trackPath { get; set; } public List<Dictionary<string, List<string>>> playlists { get; set; } }
NAudio.dll
Decompiled 8 months 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.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 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.Wave; using NAudio.Wave.Asio; using NAudio.Wave.Compression; using NAudio.Wave.SampleProviders; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("NAudioTests")] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("Mark Heath & Contributors")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("© Mark Heath 2020")] [assembly: AssemblyDescription("NAudio, an audio library for .NET")] [assembly: AssemblyFileVersion("1.10.0.0")] [assembly: AssemblyInformationalVersion("1.10.0")] [assembly: AssemblyProduct("NAudio")] [assembly: AssemblyTitle("NAudio")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.10.0.0")] [module: UnverifiableCode] 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 { private MmResult result; private string function; public MmResult Result => result; public MmException(MmResult result, string function) : base(ErrorMessage(result, function)) { this.result = result; this.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.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 MarshalHelpers { public static int SizeOf<T>() { return Marshal.SizeOf<T>(); } public static IntPtr OffsetOf<T>(string fieldName) { return Marshal.OffsetOf<T>(fieldName); } public static T PtrToStructure<T>(IntPtr pointer) { return Marshal.PtrToStructure<T>(pointer); } } internal class MergeSort { private static void Sort<T>(IList<T> list, int lowIndex, int highIndex, IComparer<T> comparer) { if (lowIndex >= highIndex) { return; } int num = (lowIndex + highIndex) / 2; Sort(list, lowIndex, num, comparer); Sort(list, num + 1, highIndex, comparer); int num2 = num; int num3 = num + 1; while (lowIndex <= num2 && num3 <= highIndex) { if (comparer.Compare(list[lowIndex], list[num3]) <= 0) { lowIndex++; continue; } T value = list[num3]; for (int num4 = num3 - 1; num4 >= lowIndex; num4--) { list[num4 + 1] = list[num4]; } list[lowIndex] = value; lowIndex++; num2++; num3++; } } public static void Sort<T>(IList<T> list) where T : IComparable<T> { Sort(list, 0, list.Count - 1, Comparer<T>.Default); } public static void Sort<T>(IList<T> list, IComparer<T> comparer) { Sort(list, 0, list.Count - 1, comparer); } } internal 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.Mixer { public class BooleanMixerControl : MixerControl { private MixerInterop.MIXERCONTROLDETAILS_BOOLEAN boolDetails; public bool Value { get { GetControlDetails(); return boolDetails.fValue == 1; } set { boolDetails.fValue = (value ? 1 : 0); mixerControlDetails.paDetails = Marshal.AllocHGlobal(Marshal.SizeOf(boolDetails)); Marshal.StructureToPtr(boolDetails, mixerControlDetails.paDetails, fDeleteOld: false); MmException.Try(MixerInterop.mixerSetControlDetails(mixerHandle, ref mixerControlDetails, MixerFlags.Mixer | mixerHandleType), "mixerSetControlDetails"); Marshal.FreeHGlobal(mixerControlDetails.paDetails); } } internal BooleanMixerControl(MixerInterop.MIXERCONTROL mixerControl, IntPtr mixerHandle, MixerFlags mixerHandleType, int nChannels) { base.mixerControl = mixerControl; base.mixerHandle = mixerHandle; base.mixerHandleType = mixerHandleType; base.nChannels = nChannels; mixerControlDetails = default(MixerInterop.MIXERCONTROLDETAILS); GetControlDetails(); } protected override void GetDetails(IntPtr pDetails) { boolDetails = MarshalHelpers.PtrToStructure<MixerInterop.MIXERCONTROLDETAILS_BOOLEAN>(pDetails); } } public class CustomMixerControl : MixerControl { internal CustomMixerControl(MixerInterop.MIXERCONTROL mixerControl, IntPtr mixerHandle, MixerFlags mixerHandleType, int nChannels) { base.mixerControl = mixerControl; base.mixerHandle = mixerHandle; base.mixerHandleType = mixerHandleType; base.nChannels = nChannels; mixerControlDetails = default(MixerInterop.MIXERCONTROLDETAILS); GetControlDetails(); } protected override void GetDetails(IntPtr pDetails) { } } public class ListTextMixerControl : MixerControl { internal ListTextMixerControl(MixerInterop.MIXERCONTROL mixerControl, IntPtr mixerHandle, MixerFlags mixerHandleType, int nChannels) { base.mixerControl = mixerControl; base.mixerHandle = mixerHandle; base.mixerHandleType = mixerHandleType; base.nChannels = nChannels; mixerControlDetails = default(MixerInterop.MIXERCONTROLDETAILS); GetControlDetails(); } protected override void GetDetails(IntPtr pDetails) { } } public class Mixer { private MixerInterop.MIXERCAPS caps; private IntPtr mixerHandle; private MixerFlags mixerHandleType; public static int NumberOfDevices => MixerInterop.mixerGetNumDevs(); public int DestinationCount => (int)caps.cDestinations; public string Name => caps.szPname; public Manufacturers Manufacturer => (Manufacturers)caps.wMid; public int ProductID => caps.wPid; public IEnumerable<MixerLine> Destinations { get { for (int destination = 0; destination < DestinationCount; destination++) { yield return GetDestination(destination); } } } public static IEnumerable<Mixer> Mixers { get { for (int device = 0; device < NumberOfDevices; device++) { yield return new Mixer(device); } } } public Mixer(int mixerIndex) { if (mixerIndex < 0 || mixerIndex >= NumberOfDevices) { throw new ArgumentOutOfRangeException("mixerID"); } caps = default(MixerInterop.MIXERCAPS); MmException.Try(MixerInterop.mixerGetDevCaps((IntPtr)mixerIndex, ref caps, Marshal.SizeOf(caps)), "mixerGetDevCaps"); mixerHandle = (IntPtr)mixerIndex; mixerHandleType = MixerFlags.Mixer; } public MixerLine GetDestination(int destinationIndex) { if (destinationIndex < 0 || destinationIndex >= DestinationCount) { throw new ArgumentOutOfRangeException("destinationIndex"); } return new MixerLine(mixerHandle, destinationIndex, mixerHandleType); } } public abstract class MixerControl { internal MixerInterop.MIXERCONTROL mixerControl; internal MixerInterop.MIXERCONTROLDETAILS mixerControlDetails; protected IntPtr mixerHandle; protected int nChannels; protected MixerFlags mixerHandleType; public string Name => mixerControl.szName; public MixerControlType ControlType => mixerControl.dwControlType; public bool IsBoolean => IsControlBoolean(mixerControl.dwControlType); public bool IsListText => IsControlListText(mixerControl.dwControlType); public bool IsSigned => IsControlSigned(mixerControl.dwControlType); public bool IsUnsigned => IsControlUnsigned(mixerControl.dwControlType); public bool IsCustom => IsControlCustom(mixerControl.dwControlType); public static IList<MixerControl> GetMixerControls(IntPtr mixerHandle, MixerLine mixerLine, MixerFlags mixerHandleType) { List<MixerControl> list = new List<MixerControl>(); if (mixerLine.ControlsCount > 0) { int num = MarshalHelpers.SizeOf<MixerInterop.MIXERCONTROL>(); MixerInterop.MIXERLINECONTROLS mixerLineControls = default(MixerInterop.MIXERLINECONTROLS); IntPtr intPtr = Marshal.AllocHGlobal(num * mixerLine.ControlsCount); mixerLineControls.cbStruct = Marshal.SizeOf(mixerLineControls); mixerLineControls.dwLineID = mixerLine.LineId; mixerLineControls.cControls = mixerLine.ControlsCount; mixerLineControls.pamxctrl = intPtr; mixerLineControls.cbmxctrl = MarshalHelpers.SizeOf<MixerInterop.MIXERCONTROL>(); try { MmResult mmResult = MixerInterop.mixerGetLineControls(mixerHandle, ref mixerLineControls, MixerFlags.Mixer | mixerHandleType); if (mmResult != 0) { throw new MmException(mmResult, "mixerGetLineControls"); } for (int i = 0; i < mixerLineControls.cControls; i++) { MixerInterop.MIXERCONTROL mIXERCONTROL = MarshalHelpers.PtrToStructure<MixerInterop.MIXERCONTROL>((IntPtr)(intPtr.ToInt64() + num * i)); MixerControl item = GetMixerControl(mixerHandle, mixerLine.LineId, mIXERCONTROL.dwControlID, mixerLine.Channels, mixerHandleType); list.Add(item); } } finally { Marshal.FreeHGlobal(intPtr); } } return list; } public static MixerControl GetMixerControl(IntPtr mixerHandle, int nLineId, int controlId, int nChannels, MixerFlags mixerFlags) { MixerInterop.MIXERLINECONTROLS mixerLineControls = default(MixerInterop.MIXERLINECONTROLS); MixerInterop.MIXERCONTROL structure = default(MixerInterop.MIXERCONTROL); IntPtr intPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(structure)); mixerLineControls.cbStruct = Marshal.SizeOf(mixerLineControls); mixerLineControls.cControls = 1; mixerLineControls.dwControlID = controlId; mixerLineControls.cbmxctrl = Marshal.SizeOf(structure); mixerLineControls.pamxctrl = intPtr; mixerLineControls.dwLineID = nLineId; MmResult mmResult = MixerInterop.mixerGetLineControls(mixerHandle, ref mixerLineControls, MixerFlags.ListText | mixerFlags); if (mmResult != 0) { Marshal.FreeCoTaskMem(intPtr); throw new MmException(mmResult, "mixerGetLineControls"); } structure = MarshalHelpers.PtrToStructure<MixerInterop.MIXERCONTROL>(mixerLineControls.pamxctrl); Marshal.FreeCoTaskMem(intPtr); if (IsControlBoolean(structure.dwControlType)) { return new BooleanMixerControl(structure, mixerHandle, mixerFlags, nChannels); } if (IsControlSigned(structure.dwControlType)) { return new SignedMixerControl(structure, mixerHandle, mixerFlags, nChannels); } if (IsControlUnsigned(structure.dwControlType)) { return new UnsignedMixerControl(structure, mixerHandle, mixerFlags, nChannels); } if (IsControlListText(structure.dwControlType)) { return new ListTextMixerControl(structure, mixerHandle, mixerFlags, nChannels); } if (IsControlCustom(structure.dwControlType)) { return new CustomMixerControl(structure, mixerHandle, mixerFlags, nChannels); } throw new InvalidOperationException($"Unknown mixer control type {structure.dwControlType}"); } protected void GetControlDetails() { mixerControlDetails.cbStruct = Marshal.SizeOf(mixerControlDetails); mixerControlDetails.dwControlID = mixerControl.dwControlID; if (IsCustom) { mixerControlDetails.cChannels = 0; } else if ((mixerControl.fdwControl & (true ? 1u : 0u)) != 0) { mixerControlDetails.cChannels = 1; } else { mixerControlDetails.cChannels = nChannels; } if ((mixerControl.fdwControl & 2u) != 0) { mixerControlDetails.hwndOwner = (IntPtr)mixerControl.cMultipleItems; } else if (IsCustom) { mixerControlDetails.hwndOwner = IntPtr.Zero; } else { mixerControlDetails.hwndOwner = IntPtr.Zero; } if (IsBoolean) { mixerControlDetails.cbDetails = MarshalHelpers.SizeOf<MixerInterop.MIXERCONTROLDETAILS_BOOLEAN>(); } else if (IsListText) { mixerControlDetails.cbDetails = MarshalHelpers.SizeOf<MixerInterop.MIXERCONTROLDETAILS_LISTTEXT>(); } else if (IsSigned) { mixerControlDetails.cbDetails = MarshalHelpers.SizeOf<MixerInterop.MIXERCONTROLDETAILS_SIGNED>(); } else if (IsUnsigned) { mixerControlDetails.cbDetails = MarshalHelpers.SizeOf<MixerInterop.MIXERCONTROLDETAILS_UNSIGNED>(); } else { mixerControlDetails.cbDetails = mixerControl.Metrics.customData; } int num = mixerControlDetails.cbDetails * mixerControlDetails.cChannels; if ((mixerControl.fdwControl & 2u) != 0) { num *= (int)mixerControl.cMultipleItems; } IntPtr intPtr = Marshal.AllocCoTaskMem(num); mixerControlDetails.paDetails = intPtr; MmResult mmResult = MixerInterop.mixerGetControlDetails(mixerHandle, ref mixerControlDetails, MixerFlags.Mixer | mixerHandleType); if (mmResult == MmResult.NoError) { GetDetails(mixerControlDetails.paDetails); } Marshal.FreeCoTaskMem(intPtr); if (mmResult != 0) { throw new MmException(mmResult, "mixerGetControlDetails"); } } protected abstract void GetDetails(IntPtr pDetails); private static bool IsControlBoolean(MixerControlType controlType) { switch (controlType) { case MixerControlType.BooleanMeter: case MixerControlType.Boolean: case MixerControlType.OnOff: case MixerControlType.Mute: case MixerControlType.Mono: case MixerControlType.Loudness: case MixerControlType.StereoEnhance: case MixerControlType.Button: case MixerControlType.SingleSelect: case MixerControlType.Mux: case MixerControlType.MultipleSelect: case MixerControlType.Mixer: return true; default: return false; } } private static bool IsControlListText(MixerControlType controlType) { if (controlType == MixerControlType.Equalizer || (uint)(controlType - 1879113728) <= 1u || (uint)(controlType - 1895890944) <= 1u) { return true; } return false; } private static bool IsControlSigned(MixerControlType controlType) { switch (controlType) { case MixerControlType.SignedMeter: case MixerControlType.PeakMeter: case MixerControlType.Signed: case MixerControlType.Decibels: case MixerControlType.Slider: case MixerControlType.Pan: case MixerControlType.QSoundPan: return true; default: return false; } } private static bool IsControlUnsigned(MixerControlType controlType) { switch (controlType) { case MixerControlType.UnsignedMeter: case MixerControlType.Unsigned: case MixerControlType.Percent: case MixerControlType.Fader: case MixerControlType.Volume: case MixerControlType.Bass: case MixerControlType.Treble: case MixerControlType.Equalizer: case MixerControlType.MicroTime: case MixerControlType.MilliTime: return true; default: return false; } } private static bool IsControlCustom(MixerControlType controlType) { return controlType == MixerControlType.Custom; } public override string ToString() { return $"{Name} {ControlType}"; } } [Flags] internal enum MixerControlClass { Custom = 0, Meter = 0x10000000, Switch = 0x20000000, Number = 0x30000000, Slider = 0x40000000, Fader = 0x50000000, Time = 0x60000000, List = 0x70000000, Mask = 0x70000000 } [Flags] internal enum MixerControlSubclass { SwitchBoolean = 0, SwitchButton = 0x1000000, MeterPolled = 0, TimeMicrosecs = 0, TimeMillisecs = 0x1000000, ListSingle = 0, ListMultiple = 0x1000000, Mask = 0xF000000 } [Flags] internal enum MixerControlUnits { Custom = 0, Boolean = 0x10000, Signed = 0x20000, Unsigned = 0x30000, Decibels = 0x40000, Percent = 0x50000, Mask = 0xFF0000 } public enum MixerControlType { Custom = 0, BooleanMeter = 268500992, SignedMeter = 268566528, PeakMeter = 268566529, UnsignedMeter = 268632064, Boolean = 536936448, OnOff = 536936449, Mute = 536936450, Mono = 536936451, Loudness = 536936452, StereoEnhance = 536936453, Button = 553713664, Decibels = 805568512, Signed = 805437440, Unsigned = 805502976, Percent = 805634048, Slider = 1073872896, Pan = 1073872897, QSoundPan = 1073872898, Fader = 1342373888, Volume = 1342373889, Bass = 1342373890, Treble = 1342373891, Equalizer = 1342373892, SingleSelect = 1879113728, Mux = 1879113729, MultipleSelect = 1895890944, Mixer = 1895890945, MicroTime = 1610809344, MilliTime = 1627586560 } [Flags] public enum MixerFlags { Handle = int.MinValue, Mixer = 0, MixerHandle = int.MinValue, WaveOut = 0x10000000, WaveOutHandle = -1879048192, WaveIn = 0x20000000, WaveInHandle = -1610612736, MidiOut = 0x30000000, MidiOutHandle = -1342177280, MidiIn = 0x40000000, MidiInHandle = -1073741824, Aux = 0x50000000, Value = 0, ListText = 1, QueryMask = 0xF, All = 0, OneById = 1, OneByType = 2, GetLineInfoOfDestination = 0, GetLineInfoOfSource = 1, GetLineInfoOfLineId = 2, GetLineInfoOfComponentType = 3, GetLineInfoOfTargetType = 4, GetLineInfoOfQueryMask = 0xF } internal class MixerInterop { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)] public struct MIXERCONTROLDETAILS { public int cbStruct; public int dwControlID; public int cChannels; public IntPtr hwndOwner; public int cbDetails; public IntPtr paDetails; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct MIXERCAPS { public ushort wMid; public ushort wPid; public uint vDriverVersion; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szPname; public uint fdwSupport; public uint cDestinations; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct MIXERLINECONTROLS { public int cbStruct; public int dwLineID; public int dwControlID; public int cControls; public int cbmxctrl; public IntPtr pamxctrl; } [Flags] public enum MIXERLINE_LINEF { MIXERLINE_LINEF_ACTIVE = 1, MIXERLINE_LINEF_DISCONNECTED = 0x8000, MIXERLINE_LINEF_SOURCE = int.MinValue } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct MIXERLINE { public int cbStruct; public int dwDestination; public int dwSource; public int dwLineID; public MIXERLINE_LINEF fdwLine; public IntPtr dwUser; public MixerLineComponentType dwComponentType; public int cChannels; public int cConnections; public int cControls; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string szShortName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string szName; public uint dwType; public uint dwDeviceID; public ushort wMid; public ushort wPid; public uint vDriverVersion; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szPname; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Bounds { public int minimum; public int maximum; public int reserved2; public int reserved3; public int reserved4; public int reserved5; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Metrics { public int step; public int customData; public int reserved2; public int reserved3; public int reserved4; public int reserved5; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct MIXERCONTROL { public uint cbStruct; public int dwControlID; public MixerControlType dwControlType; public uint fdwControl; public uint cMultipleItems; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)] public string szShortName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string szName; public Bounds Bounds; public Metrics Metrics; } public struct MIXERCONTROLDETAILS_BOOLEAN { public int fValue; } public struct MIXERCONTROLDETAILS_SIGNED { public int lValue; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct MIXERCONTROLDETAILS_LISTTEXT { public uint dwParam1; public uint dwParam2; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string szName; } public struct MIXERCONTROLDETAILS_UNSIGNED { public uint dwValue; } public const uint MIXERCONTROL_CONTROLF_UNIFORM = 1u; public const uint MIXERCONTROL_CONTROLF_MULTIPLE = 2u; public const uint MIXERCONTROL_CONTROLF_DISABLED = 2147483648u; public const int MAXPNAMELEN = 32; public const int MIXER_SHORT_NAME_CHARS = 16; public const int MIXER_LONG_NAME_CHARS = 64; [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern int mixerGetNumDevs(); [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern MmResult mixerOpen(out IntPtr hMixer, int uMxId, IntPtr dwCallback, IntPtr dwInstance, MixerFlags dwOpenFlags); [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern MmResult mixerClose(IntPtr hMixer); [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern MmResult mixerGetControlDetails(IntPtr hMixer, ref MIXERCONTROLDETAILS mixerControlDetails, MixerFlags dwDetailsFlags); [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern MmResult mixerGetDevCaps(IntPtr nMixerID, ref MIXERCAPS mixerCaps, int mixerCapsSize); [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern MmResult mixerGetID(IntPtr hMixer, out int mixerID, MixerFlags dwMixerIDFlags); [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern MmResult mixerGetLineControls(IntPtr hMixer, ref MIXERLINECONTROLS mixerLineControls, MixerFlags dwControlFlags); [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern MmResult mixerGetLineInfo(IntPtr hMixer, ref MIXERLINE mixerLine, MixerFlags dwInfoFlags); [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern MmResult mixerMessage(IntPtr hMixer, uint nMessage, IntPtr dwParam1, IntPtr dwParam2); [DllImport("winmm.dll", CharSet = CharSet.Ansi)] public static extern MmResult mixerSetControlDetails(IntPtr hMixer, ref MIXERCONTROLDETAILS mixerControlDetails, MixerFlags dwDetailsFlags); } public class MixerLine { private MixerInterop.MIXERLINE mixerLine; private IntPtr mixerHandle; private MixerFlags mixerHandleType; public string Name => mixerLine.szName; public string ShortName => mixerLine.szShortName; public int LineId => mixerLine.dwLineID; public MixerLineComponentType ComponentType => mixerLine.dwComponentType; public string TypeDescription => mixerLine.dwComponentType switch { MixerLineComponentType.DestinationUndefined => "Undefined Destination", MixerLineComponentType.DestinationDigital => "Digital Destination", MixerLineComponentType.DestinationLine => "Line Level Destination", MixerLineComponentType.DestinationMonitor => "Monitor Destination", MixerLineComponentType.DestinationSpeakers => "Speakers Destination", MixerLineComponentType.DestinationHeadphones => "Headphones Destination", MixerLineComponentType.DestinationTelephone => "Telephone Destination", MixerLineComponentType.DestinationWaveIn => "Wave Input Destination", MixerLineComponentType.DestinationVoiceIn => "Voice Recognition Destination", MixerLineComponentType.SourceUndefined => "Undefined Source", MixerLineComponentType.SourceDigital => "Digital Source", MixerLineComponentType.SourceLine => "Line Level Source", MixerLineComponentType.SourceMicrophone => "Microphone Source", MixerLineComponentType.SourceSynthesizer => "Synthesizer Source", MixerLineComponentType.SourceCompactDisc => "Compact Disk Source", MixerLineComponentType.SourceTelephone => "Telephone Source", MixerLineComponentType.SourcePcSpeaker => "PC Speaker Source", MixerLineComponentType.SourceWaveOut => "Wave Out Source", MixerLineComponentType.SourceAuxiliary => "Auxiliary Source", MixerLineComponentType.SourceAnalog => "Analog Source", _ => "Invalid Component Type", }; public int Channels => mixerLine.cChannels; public int SourceCount => mixerLine.cConnections; public int ControlsCount => mixerLine.cControls; public bool IsActive => (mixerLine.fdwLine & MixerInterop.MIXERLINE_LINEF.MIXERLINE_LINEF_ACTIVE) != 0; public bool IsDisconnected => (mixerLine.fdwLine & MixerInterop.MIXERLINE_LINEF.MIXERLINE_LINEF_DISCONNECTED) != 0; public bool IsSource => (mixerLine.fdwLine & MixerInterop.MIXERLINE_LINEF.MIXERLINE_LINEF_SOURCE) != 0; public IEnumerable<MixerControl> Controls => MixerControl.GetMixerControls(mixerHandle, this, mixerHandleType); public IEnumerable<MixerLine> Sources { get { for (int source = 0; source < SourceCount; source++) { yield return GetSource(source); } } } public string TargetName => mixerLine.szPname; public MixerLine(IntPtr mixerHandle, int destinationIndex, MixerFlags mixerHandleType) { this.mixerHandle = mixerHandle; this.mixerHandleType = mixerHandleType; mixerLine = default(MixerInterop.MIXERLINE); mixerLine.cbStruct = Marshal.SizeOf(mixerLine); mixerLine.dwDestination = destinationIndex; MmException.Try(MixerInterop.mixerGetLineInfo(mixerHandle, ref mixerLine, mixerHandleType | MixerFlags.Mixer), "mixerGetLineInfo"); } public MixerLine(IntPtr mixerHandle, int destinationIndex, int sourceIndex, MixerFlags mixerHandleType) { this.mixerHandle = mixerHandle; this.mixerHandleType = mixerHandleType; mixerLine = default(MixerInterop.MIXERLINE); mixerLine.cbStruct = Marshal.SizeOf(mixerLine); mixerLine.dwDestination = destinationIndex; mixerLine.dwSource = sourceIndex; MmException.Try(MixerInterop.mixerGetLineInfo(mixerHandle, ref mixerLine, mixerHandleType | MixerFlags.ListText), "mixerGetLineInfo"); } public static int GetMixerIdForWaveIn(int waveInDevice) { int mixerID = -1; MmException.Try(MixerInterop.mixerGetID((IntPtr)waveInDevice, out mixerID, MixerFlags.WaveIn), "mixerGetID"); return mixerID; } public MixerLine GetSource(int sourceIndex) { if (sourceIndex < 0 || sourceIndex >= SourceCount) { throw new ArgumentOutOfRangeException("sourceIndex"); } return new MixerLine(mixerHandle, mixerLine.dwDestination, sourceIndex, mixerHandleType); } public override string ToString() { return $"{Name} {TypeDescription} ({ControlsCount} controls, ID={mixerLine.dwLineID})"; } } public enum MixerLineComponentType { DestinationUndefined = 0, DestinationDigital = 1, DestinationLine = 2, DestinationMonitor = 3, DestinationSpeakers = 4, DestinationHeadphones = 5, DestinationTelephone = 6, DestinationWaveIn = 7, DestinationVoiceIn = 8, SourceUndefined = 4096, SourceDigital = 4097, SourceLine = 4098, SourceMicrophone = 4099, SourceSynthesizer = 4100, SourceCompactDisc = 4101, SourceTelephone = 4102, SourcePcSpeaker = 4103, SourceWaveOut = 4104, SourceAuxiliary = 4105, SourceAnalog = 4106 } public class SignedMixerControl : MixerControl { private MixerInterop.MIXERCONTROLDETAILS_SIGNED signedDetails; public int Value { get { GetControlDetails(); return signedDetails.lValue; } set { signedDetails.lValue = value; mixerControlDetails.paDetails = Marshal.AllocHGlobal(Marshal.SizeOf(signedDetails)); Marshal.StructureToPtr(signedDetails, mixerControlDetails.paDetails, fDeleteOld: false); MmException.Try(MixerInterop.mixerSetControlDetails(mixerHandle, ref mixerControlDetails, MixerFlags.Mixer | mixerHandleType), "mixerSetControlDetails"); Marshal.FreeHGlobal(mixerControlDetails.paDetails); } } public int MinValue => mixerControl.Bounds.minimum; public int MaxValue => mixerControl.Bounds.maximum; public double Percent { get { return 100.0 * (double)(Value - MinValue) / (double)(MaxValue - MinValue); } set { Value = (int)((double)MinValue + value / 100.0 * (double)(MaxValue - MinValue)); } } internal SignedMixerControl(MixerInterop.MIXERCONTROL mixerControl, IntPtr mixerHandle, MixerFlags mixerHandleType, int nChannels) { base.mixerControl = mixerControl; base.mixerHandle = mixerHandle; base.mixerHandleType = mixerHandleType; base.nChannels = nChannels; mixerControlDetails = default(MixerInterop.MIXERCONTROLDETAILS); GetControlDetails(); } protected override void GetDetails(IntPtr pDetails) { signedDetails = MarshalHelpers.PtrToStructure<MixerInterop.MIXERCONTROLDETAILS_SIGNED>(mixerControlDetails.paDetails); } public override string ToString() { return $"{base.ToString()} {Percent}%"; } } public class UnsignedMixerControl : MixerControl { private MixerInterop.MIXERCONTROLDETAILS_UNSIGNED[] unsignedDetails; public uint Value { get { GetControlDetails(); return unsignedDetails[0].dwValue; } set { int num = Marshal.SizeOf(unsignedDetails[0]); mixerControlDetails.paDetails = Marshal.AllocHGlobal(num * nChannels); for (int i = 0; i < nChannels; i++) { unsignedDetails[i].dwValue = value; long num2 = mixerControlDetails.paDetails.ToInt64() + num * i; Marshal.StructureToPtr(unsignedDetails[i], (IntPtr)num2, fDeleteOld: false); } MmException.Try(MixerInterop.mixerSetControlDetails(mixerHandle, ref mixerControlDetails, MixerFlags.Mixer | mixerHandleType), "mixerSetControlDetails"); Marshal.FreeHGlobal(mixerControlDetails.paDetails); } } public uint MinValue => (uint)mixerControl.Bounds.minimum; public uint MaxValue => (uint)mixerControl.Bounds.maximum; public double Percent { get { return 100.0 * (double)(Value - MinValue) / (double)(MaxValue - MinValue); } set { Value = (uint)((double)MinValue + value / 100.0 * (double)(MaxValue - MinValue)); } } internal UnsignedMixerControl(MixerInterop.MIXERCONTROL mixerControl, IntPtr mixerHandle, MixerFlags mixerHandleType, int nChannels) { base.mixerControl = mixerControl; base.mixerHandle = mixerHandle; base.mixerHandleType = mixerHandleType; base.nChannels = nChannels; mixerControlDetails = default(MixerInterop.MIXERCONTROLDETAILS); GetControlDetails(); } protected override void GetDetails(IntPtr pDetails) { unsignedDetails = new MixerInterop.MIXERCONTROLDETAILS_UNSIGNED[nChannels]; for (int i = 0; i < nChannels; i++) { unsignedDetails[i] = MarshalHelpers.PtrToStructure<MixerInterop.MIXERCONTROLDETAILS_UNSIGNED>(mixerControlDetails.paDetails); } } public override string ToString() { return $"{base.ToString()} {Percent}%"; } } } namespace NAudio.Midi { public class ChannelAfterTouchEvent : MidiEvent { private byte afterTouchPressure; public int AfterTouchPressure { get { return afterTouchPressure; } set { if (value < 0 || value > 127) { throw new ArgumentOutOfRangeException("value", "After touch pressure must be in the range 0-127"); } afterTouchPressure = (byte)value; } } public ChannelAfterTouchEvent(BinaryReader br) { afterTouchPressure = br.ReadByte(); if ((afterTouchPressure & 0x80u) != 0) { throw new FormatException("Invalid afterTouchPressure"); } } public ChannelAfterTouchEvent(long absoluteTime, int channel, int afterTouchPressure) : base(absoluteTime, channel, MidiCommandCode.ChannelAfterTouch) { AfterTouchPressure = afterTouchPressure; } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write(afterTouchPressure); } } public class ControlChangeEvent : MidiEvent { private MidiController controller; private byte controllerValue; public MidiController Controller { get { return controller; } set { if ((int)value < 0 || (int)value > 127) { throw new ArgumentOutOfRangeException("value", "Controller number must be in the range 0-127"); } controller = value; } } public int ControllerValue { get { return controllerValue; } set { if (value < 0 || value > 127) { throw new ArgumentOutOfRangeException("value", "Controller Value must be in the range 0-127"); } controllerValue = (byte)value; } } public ControlChangeEvent(BinaryReader br) { byte b = br.ReadByte(); controllerValue = br.ReadByte(); if ((b & 0x80u) != 0) { throw new InvalidDataException("Invalid controller"); } controller = (MidiController)b; if ((controllerValue & 0x80u) != 0) { throw new InvalidDataException($"Invalid controllerValue {controllerValue} for controller {controller}, Pos 0x{br.BaseStream.Position:X}"); } } public ControlChangeEvent(long absoluteTime, int channel, MidiController controller, int controllerValue) : base(absoluteTime, channel, MidiCommandCode.ControlChange) { Controller = controller; ControllerValue = controllerValue; } public override string ToString() { return $"{base.ToString()} Controller {controller} Value {controllerValue}"; } public override int GetAsShortMessage() { byte b = (byte)controller; return base.GetAsShortMessage() + (b << 8) + (controllerValue << 16); } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write((byte)controller); writer.Write(controllerValue); } } public class KeySignatureEvent : MetaEvent { private readonly byte sharpsFlats; private readonly byte majorMinor; public int SharpsFlats => (sbyte)sharpsFlats; public int MajorMinor => majorMinor; public KeySignatureEvent(BinaryReader br, int length) { if (length != 2) { throw new FormatException("Invalid key signature length"); } sharpsFlats = br.ReadByte(); majorMinor = br.ReadByte(); } public KeySignatureEvent(int sharpsFlats, int majorMinor, long absoluteTime) : base(MetaEventType.KeySignature, 2, absoluteTime) { this.sharpsFlats = (byte)sharpsFlats; this.majorMinor = (byte)majorMinor; } public override MidiEvent Clone() { return (KeySignatureEvent)MemberwiseClone(); } public override string ToString() { return $"{base.ToString()} {SharpsFlats} {majorMinor}"; } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write(sharpsFlats); writer.Write(majorMinor); } } public class MetaEvent : MidiEvent { private MetaEventType metaEvent; internal int metaDataLength; public MetaEventType MetaEventType => metaEvent; protected MetaEvent() { } public MetaEvent(MetaEventType metaEventType, int metaDataLength, long absoluteTime) : base(absoluteTime, 1, MidiCommandCode.MetaEvent) { metaEvent = metaEventType; this.metaDataLength = metaDataLength; } public override MidiEvent Clone() { return new MetaEvent(metaEvent, metaDataLength, base.AbsoluteTime); } public static MetaEvent ReadMetaEvent(BinaryReader br) { MetaEventType metaEventType = (MetaEventType)br.ReadByte(); int num = MidiEvent.ReadVarInt(br); MetaEvent metaEvent = new MetaEvent(); if (metaEventType <= MetaEventType.SetTempo) { if (metaEventType <= MetaEventType.DeviceName) { if (metaEventType != 0) { if (metaEventType - 1 > MetaEventType.ProgramName) { goto IL_00a6; } metaEvent = new TextEvent(br, num); } else { metaEvent = new TrackSequenceNumberEvent(br, num); } } else if (metaEventType != MetaEventType.EndTrack) { if (metaEventType != MetaEventType.SetTempo) { goto IL_00a6; } metaEvent = new TempoEvent(br, num); } else if (num != 0) { throw new FormatException("End track length"); } } else if (metaEventType <= MetaEventType.TimeSignature) { if (metaEventType != MetaEventType.SmpteOffset) { if (metaEventType != MetaEventType.TimeSignature) { goto IL_00a6; } metaEvent = new TimeSignatureEvent(br, num); } else { metaEvent = new SmpteOffsetEvent(br, num); } } else if (metaEventType != MetaEventType.KeySignature) { if (metaEventType != MetaEventType.SequencerSpecific) { goto IL_00a6; } metaEvent = new SequencerSpecificEvent(br, num); } else { metaEvent = new KeySignatureEvent(br, num); } metaEvent.metaEvent = metaEventType; metaEvent.metaDataLength = num; return metaEvent; IL_00a6: byte[] array = br.ReadBytes(num); if (array.Length != num) { throw new FormatException("Failed to read metaevent's data fully"); } return new RawMetaEvent(metaEventType, 0L, array); } public override string ToString() { return $"{base.AbsoluteTime} {metaEvent}"; } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write((byte)metaEvent); MidiEvent.WriteVarInt(writer, metaDataLength); } } public enum MetaEventType : byte { TrackSequenceNumber = 0, TextEvent = 1, Copyright = 2, SequenceTrackName = 3, TrackInstrumentName = 4, Lyric = 5, Marker = 6, CuePoint = 7, ProgramName = 8, DeviceName = 9, MidiChannel = 32, MidiPort = 33, EndTrack = 47, SetTempo = 81, SmpteOffset = 84, TimeSignature = 88, KeySignature = 89, SequencerSpecific = 127 } public enum MidiCommandCode : byte { NoteOff = 128, NoteOn = 144, KeyAfterTouch = 160, ControlChange = 176, PatchChange = 192, ChannelAfterTouch = 208, PitchWheelChange = 224, Sysex = 240, Eox = 247, TimingClock = 248, StartSequence = 250, ContinueSequence = 251, StopSequence = 252, AutoSensing = 254, MetaEvent = byte.MaxValue } public enum MidiController : byte { BankSelect = 0, Modulation = 1, BreathController = 2, FootController = 4, MainVolume = 7, Pan = 10, Expression = 11, BankSelectLsb = 32, Sustain = 64, Portamento = 65, Sostenuto = 66, SoftPedal = 67, LegatoFootswitch = 68, ResetAllControllers = 121, AllNotesOff = 123 } public class MidiEvent : ICloneable { private MidiCommandCode commandCode; private int channel; private int deltaTime; private long absoluteTime; public virtual int Channel { get { return channel; } set { if (value < 1 || value > 16) { throw new ArgumentOutOfRangeException("value", value, $"Channel must be 1-16 (Got {value})"); } channel = value; } } public int DeltaTime => deltaTime; public long AbsoluteTime { get { return absoluteTime; } set { absoluteTime = value; } } public MidiCommandCode CommandCode => commandCode; public static MidiEvent FromRawMessage(int rawMessage) { long num = 0L; int num2 = rawMessage & 0xFF; int num3 = (rawMessage >> 8) & 0xFF; int num4 = (rawMessage >> 16) & 0xFF; int num5 = 1; MidiCommandCode midiCommandCode; if ((num2 & 0xF0) == 240) { midiCommandCode = (MidiCommandCode)num2; } else { midiCommandCode = (MidiCommandCode)((uint)num2 & 0xF0u); num5 = (num2 & 0xF) + 1; } switch (midiCommandCode) { case MidiCommandCode.NoteOff: case MidiCommandCode.NoteOn: case MidiCommandCode.KeyAfterTouch: if (num4 > 0 && midiCommandCode == MidiCommandCode.NoteOn) { return new NoteOnEvent(num, num5, num3, num4, 0); } return new NoteEvent(num, num5, midiCommandCode, num3, num4); case MidiCommandCode.ControlChange: return new ControlChangeEvent(num, num5, (MidiController)num3, num4); case MidiCommandCode.PatchChange: return new PatchChangeEvent(num, num5, num3); case MidiCommandCode.ChannelAfterTouch: return new ChannelAfterTouchEvent(num, num5, num3); case MidiCommandCode.PitchWheelChange: return new PitchWheelChangeEvent(num, num5, num3 + (num4 << 7)); case MidiCommandCode.TimingClock: case MidiCommandCode.StartSequence: case MidiCommandCode.ContinueSequence: case MidiCommandCode.StopSequence: case MidiCommandCode.AutoSensing: return new MidiEvent(num, num5, midiCommandCode); default: throw new FormatException($"Unsupported MIDI Command Code for Raw Message {midiCommandCode}"); } } public static MidiEvent ReadNextEvent(BinaryReader br, MidiEvent previous) { int num = ReadVarInt(br); int num2 = 1; byte b = br.ReadByte(); MidiCommandCode midiCommandCode; if ((b & 0x80) == 0) { midiCommandCode = previous.CommandCode; num2 = previous.Channel; br.BaseStream.Position--; } else if ((b & 0xF0) == 240) { midiCommandCode = (MidiCommandCode)b; } else { midiCommandCode = (MidiCommandCode)(b & 0xF0u); num2 = (b & 0xF) + 1; } MidiEvent midiEvent; switch (midiCommandCode) { case MidiCommandCode.NoteOn: midiEvent = new NoteOnEvent(br); break; case MidiCommandCode.NoteOff: case MidiCommandCode.KeyAfterTouch: midiEvent = new NoteEvent(br); break; case MidiCommandCode.ControlChange: midiEvent = new ControlChangeEvent(br); break; case MidiCommandCode.PatchChange: midiEvent = new PatchChangeEvent(br); break; case MidiCommandCode.ChannelAfterTouch: midiEvent = new ChannelAfterTouchEvent(br); break; case MidiCommandCode.PitchWheelChange: midiEvent = new PitchWheelChangeEvent(br); break; case MidiCommandCode.TimingClock: case MidiCommandCode.StartSequence: case MidiCommandCode.ContinueSequence: case MidiCommandCode.StopSequence: midiEvent = new MidiEvent(); break; case MidiCommandCode.Sysex: midiEvent = SysexEvent.ReadSysexEvent(br); break; case MidiCommandCode.MetaEvent: midiEvent = MetaEvent.ReadMetaEvent(br); break; default: throw new FormatException($"Unsupported MIDI Command Code {(byte)midiCommandCode:X2}"); } midiEvent.channel = num2; midiEvent.deltaTime = num; midiEvent.commandCode = midiCommandCode; return midiEvent; } public virtual int GetAsShortMessage() { return channel - 1 + (int)commandCode; } protected MidiEvent() { } public MidiEvent(long absoluteTime, int channel, MidiCommandCode commandCode) { this.absoluteTime = absoluteTime; Channel = channel; this.commandCode = commandCode; } public virtual MidiEvent Clone() { return (MidiEvent)MemberwiseClone(); } object ICloneable.Clone() { return Clone(); } public static bool IsNoteOff(MidiEvent midiEvent) { if (midiEvent != null) { if (midiEvent.CommandCode == MidiCommandCode.NoteOn) { return ((NoteEvent)midiEvent).Velocity == 0; } return midiEvent.CommandCode == MidiCommandCode.NoteOff; } return false; } public static bool IsNoteOn(MidiEvent midiEvent) { if (midiEvent != null && midiEvent.CommandCode == MidiCommandCode.NoteOn) { return ((NoteEvent)midiEvent).Velocity > 0; } return false; } public static bool IsEndTrack(MidiEvent midiEvent) { if (midiEvent != null && midiEvent is MetaEvent metaEvent) { return metaEvent.MetaEventType == MetaEventType.EndTrack; } return false; } public override string ToString() { if ((int)commandCode >= 240) { return $"{absoluteTime} {commandCode}"; } return $"{absoluteTime} {commandCode} Ch: {channel}"; } public static int ReadVarInt(BinaryReader br) { int num = 0; for (int i = 0; i < 4; i++) { byte b = br.ReadByte(); num <<= 7; num += b & 0x7F; if ((b & 0x80) == 0) { return num; } } throw new FormatException("Invalid Var Int"); } public static void WriteVarInt(BinaryWriter writer, int value) { if (value < 0) { throw new ArgumentOutOfRangeException("value", value, "Cannot write a negative Var Int"); } if (value > 268435455) { throw new ArgumentOutOfRangeException("value", value, "Maximum allowed Var Int is 0x0FFFFFFF"); } int num = 0; byte[] array = new byte[4]; do { array[num++] = (byte)((uint)value & 0x7Fu); value >>= 7; } while (value > 0); while (num > 0) { num--; if (num > 0) { writer.Write((byte)(array[num] | 0x80u)); } else { writer.Write(array[num]); } } } public virtual void Export(ref long absoluteTime, BinaryWriter writer) { if (this.absoluteTime < absoluteTime) { throw new FormatException("Can't export unsorted MIDI events"); } WriteVarInt(writer, (int)(this.absoluteTime - absoluteTime)); absoluteTime = this.absoluteTime; int num = (int)commandCode; if (commandCode != MidiCommandCode.MetaEvent) { num += channel - 1; } writer.Write((byte)num); } } public class MidiEventCollection : IEnumerable<IList<MidiEvent>>, IEnumerable { private int midiFileType; private readonly List<IList<MidiEvent>> trackEvents; public int Tracks => trackEvents.Count; public long StartAbsoluteTime { get; set; } public int DeltaTicksPerQuarterNote { get; } public IList<MidiEvent> this[int trackNumber] => trackEvents[trackNumber]; public int MidiFileType { get { return midiFileType; } set { if (midiFileType != value) { midiFileType = value; if (value == 0) { FlattenToOneTrack(); } else { ExplodeToManyTracks(); } } } } public MidiEventCollection(int midiFileType, int deltaTicksPerQuarterNote) { this.midiFileType = midiFileType; DeltaTicksPerQuarterNote = deltaTicksPerQuarterNote; StartAbsoluteTime = 0L; trackEvents = new List<IList<MidiEvent>>(); } public IList<MidiEvent> GetTrackEvents(int trackNumber) { return trackEvents[trackNumber]; } public IList<MidiEvent> AddTrack() { return AddTrack(null); } public IList<MidiEvent> AddTrack(IList<MidiEvent> initialEvents) { List<MidiEvent> list = new List<MidiEvent>(); if (initialEvents != null) { list.AddRange(initialEvents); } trackEvents.Add(list); return list; } public void RemoveTrack(int track) { trackEvents.RemoveAt(track); } public void Clear() { trackEvents.Clear(); } public void AddEvent(MidiEvent midiEvent, int originalTrack) { if (midiFileType == 0) { EnsureTracks(1); trackEvents[0].Add(midiEvent); } else if (originalTrack == 0) { switch (midiEvent.CommandCode) { case MidiCommandCode.NoteOff: case MidiCommandCode.NoteOn: case MidiCommandCode.KeyAfterTouch: case MidiCommandCode.ControlChange: case MidiCommandCode.PatchChange: case MidiCommandCode.ChannelAfterTouch: case MidiCommandCode.PitchWheelChange: EnsureTracks(midiEvent.Channel + 1); trackEvents[midiEvent.Channel].Add(midiEvent); break; default: EnsureTracks(1); trackEvents[0].Add(midiEvent); break; } } else { EnsureTracks(originalTrack + 1); trackEvents[originalTrack].Add(midiEvent); } } private void EnsureTracks(int count) { for (int i = trackEvents.Count; i < count; i++) { trackEvents.Add(new List<MidiEvent>()); } } private void ExplodeToManyTracks() { IList<MidiEvent> list = trackEvents[0]; Clear(); foreach (MidiEvent item in list) { AddEvent(item, 0); } PrepareForExport(); } private void FlattenToOneTrack() { bool flag = false; for (int i = 1; i < trackEvents.Count; i++) { foreach (MidiEvent item in trackEvents[i]) { if (!MidiEvent.IsEndTrack(item)) { trackEvents[0].Add(item); flag = true; } } } for (int num = trackEvents.Count - 1; num > 0; num--) { RemoveTrack(num); } if (flag) { PrepareForExport(); } } public void PrepareForExport() { MidiEventComparer comparer = new MidiEventComparer(); foreach (IList<MidiEvent> trackEvent in trackEvents) { MergeSort.Sort(trackEvent, comparer); int num = 0; while (num < trackEvent.Count - 1) { if (MidiEvent.IsEndTrack(trackEvent[num])) { trackEvent.RemoveAt(num); } else { num++; } } } int num2 = 0; while (num2 < trackEvents.Count) { IList<MidiEvent> list = trackEvents[num2]; if (list.Count == 0) { RemoveTrack(num2); continue; } if (list.Count == 1 && MidiEvent.IsEndTrack(list[0])) { RemoveTrack(num2); continue; } if (!MidiEvent.IsEndTrack(list[list.Count - 1])) { list.Add(new MetaEvent(MetaEventType.EndTrack, 0, list[list.Count - 1].AbsoluteTime)); } num2++; } } public IEnumerator<IList<MidiEvent>> GetEnumerator() { return trackEvents.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return trackEvents.GetEnumerator(); } } public class MidiEventComparer : IComparer<MidiEvent> { public int Compare(MidiEvent x, MidiEvent y) { long num = x.AbsoluteTime; long num2 = y.AbsoluteTime; if (num == num2) { MetaEvent metaEvent = x as MetaEvent; MetaEvent metaEvent2 = y as MetaEvent; if (metaEvent != null) { num = ((metaEvent.MetaEventType != MetaEventType.EndTrack) ? long.MinValue : long.MaxValue); } if (metaEvent2 != null) { num2 = ((metaEvent2.MetaEventType != MetaEventType.EndTrack) ? long.MinValue : long.MaxValue); } } return num.CompareTo(num2); } } public class MidiFile { private readonly MidiEventCollection events; private readonly ushort fileFormat; private readonly ushort deltaTicksPerQuarterNote; private readonly bool strictChecking; public int FileFormat => fileFormat; public MidiEventCollection Events => events; public int Tracks => events.Tracks; public int DeltaTicksPerQuarterNote => deltaTicksPerQuarterNote; public MidiFile(string filename) : this(filename, strictChecking: true) { } public MidiFile(string filename, bool strictChecking) : this(File.OpenRead(filename), strictChecking, ownInputStream: true) { } public MidiFile(Stream inputStream, bool strictChecking) : this(inputStream, strictChecking, ownInputStream: false) { } private MidiFile(Stream inputStream, bool strictChecking, bool ownInputStream) { this.strictChecking = strictChecking; BinaryReader binaryReader = new BinaryReader(inputStream); try { if (Encoding.UTF8.GetString(binaryReader.ReadBytes(4)) != "MThd") { throw new FormatException("Not a MIDI file - header chunk missing"); } uint num = SwapUInt32(binaryReader.ReadUInt32()); if (num != 6) { throw new FormatException("Unexpected header chunk length"); } fileFormat = SwapUInt16(binaryReader.ReadUInt16()); int num2 = SwapUInt16(binaryReader.ReadUInt16()); deltaTicksPerQuarterNote = SwapUInt16(binaryReader.ReadUInt16()); events = new MidiEventCollection((fileFormat != 0) ? 1 : 0, deltaTicksPerQuarterNote); for (int i = 0; i < num2; i++) { events.AddTrack(); } long num3 = 0L; for (int j = 0; j < num2; j++) { if (fileFormat == 1) { num3 = 0L; } if (Encoding.UTF8.GetString(binaryReader.ReadBytes(4)) != "MTrk") { throw new FormatException("Invalid chunk header"); } num = SwapUInt32(binaryReader.ReadUInt32()); long position = binaryReader.BaseStream.Position; MidiEvent midiEvent = null; List<NoteOnEvent> list = new List<NoteOnEvent>(); while (binaryReader.BaseStream.Position < position + num) { try { midiEvent = MidiEvent.ReadNextEvent(binaryReader, midiEvent); } catch (InvalidDataException) { if (strictChecking) { throw; } continue; } catch (FormatException) { if (strictChecking) { throw; } continue; } num3 = (midiEvent.AbsoluteTime = num3 + midiEvent.DeltaTime); events[j].Add(midiEvent); if (midiEvent.CommandCode == MidiCommandCode.NoteOn) { NoteEvent noteEvent = (NoteEvent)midiEvent; if (noteEvent.Velocity > 0) { list.Add((NoteOnEvent)noteEvent); } else { FindNoteOn(noteEvent, list); } } else if (midiEvent.CommandCode == MidiCommandCode.NoteOff) { FindNoteOn((NoteEvent)midiEvent, list); } else if (midiEvent.CommandCode == MidiCommandCode.MetaEvent && ((MetaEvent)midiEvent).MetaEventType == MetaEventType.EndTrack && strictChecking && binaryReader.BaseStream.Position < position + num) { throw new FormatException($"End Track event was not the last MIDI event on track {j}"); } } if (list.Count > 0 && strictChecking) { throw new FormatException($"Note ons without note offs {list.Count} (file format {fileFormat})"); } if (binaryReader.BaseStream.Position != position + num) { throw new FormatException($"Read too far {num}+{position}!={binaryReader.BaseStream.Position}"); } } } finally { if (ownInputStream) { binaryReader.Dispose(); } } } private void FindNoteOn(NoteEvent offEvent, List<NoteOnEvent> outstandingNoteOns) { bool flag = false; foreach (NoteOnEvent outstandingNoteOn in outstandingNoteOns) { if (outstandingNoteOn.Channel == offEvent.Channel && outstandingNoteOn.NoteNumber == offEvent.NoteNumber) { outstandingNoteOn.OffEvent = offEvent; outstandingNoteOns.Remove(outstandingNoteOn); flag = true; break; } } if (!flag && strictChecking) { throw new FormatException($"Got an off without an on {offEvent}"); } } private static uint SwapUInt32(uint i) { return ((i & 0xFF000000u) >> 24) | ((i & 0xFF0000) >> 8) | ((i & 0xFF00) << 8) | ((i & 0xFF) << 24); } private static ushort SwapUInt16(ushort i) { return (ushort)(((i & 0xFF00) >> 8) | ((i & 0xFF) << 8)); } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendFormat("Format {0}, Tracks {1}, Delta Ticks Per Quarter Note {2}\r\n", fileFormat, Tracks, deltaTicksPerQuarterNote); for (int i = 0; i < Tracks; i++) { foreach (MidiEvent item in events[i]) { stringBuilder.AppendFormat("{0}\r\n", item); } } return stringBuilder.ToString(); } public static void Export(string filename, MidiEventCollection events) { if (events.MidiFileType == 0 && events.Tracks > 1) { throw new ArgumentException("Can't export more than one track to a type 0 file"); } using BinaryWriter binaryWriter = new BinaryWriter(File.Create(filename)); binaryWriter.Write(Encoding.UTF8.GetBytes("MThd")); binaryWriter.Write(SwapUInt32(6u)); binaryWriter.Write(SwapUInt16((ushort)events.MidiFileType)); binaryWriter.Write(SwapUInt16((ushort)events.Tracks)); binaryWriter.Write(SwapUInt16((ushort)events.DeltaTicksPerQuarterNote)); for (int i = 0; i < events.Tracks; i++) { IList<MidiEvent> list = events[i]; binaryWriter.Write(Encoding.UTF8.GetBytes("MTrk")); long position = binaryWriter.BaseStream.Position; binaryWriter.Write(SwapUInt32(0u)); long absoluteTime = events.StartAbsoluteTime; MergeSort.Sort(list, new MidiEventComparer()); _ = list.Count; _ = 0; foreach (MidiEvent item in list) { item.Export(ref absoluteTime, binaryWriter); } uint num = (uint)((int)(binaryWriter.BaseStream.Position - position) - 4); binaryWriter.BaseStream.Position = position; binaryWriter.Write(SwapUInt32(num)); binaryWriter.BaseStream.Position += num; } } } public class MidiIn : IDisposable { private IntPtr hMidiIn = IntPtr.Zero; private bool disposed; private MidiInterop.MidiInCallback callback; public static int NumberOfDevices => MidiInterop.midiInGetNumDevs(); public event EventHandler<MidiInMessageEventArgs> MessageReceived; public event EventHandler<MidiInMessageEventArgs> ErrorReceived; public MidiIn(int deviceNo) { callback = Callback; MmException.Try(MidiInterop.midiInOpen(out hMidiIn, (IntPtr)deviceNo, callback, IntPtr.Zero, 196608), "midiInOpen"); } public void Close() { Dispose(); } public void Dispose() { GC.KeepAlive(callback); Dispose(disposing: true); GC.SuppressFinalize(this); } public void Start() { MmException.Try(MidiInterop.midiInStart(hMidiIn), "midiInStart"); } public void Stop() { MmException.Try(MidiInterop.midiInStop(hMidiIn), "midiInStop"); } public void Reset() { MmException.Try(MidiInterop.midiInReset(hMidiIn), "midiInReset"); } private void Callback(IntPtr midiInHandle, MidiInterop.MidiInMessage message, IntPtr userData, IntPtr messageParameter1, IntPtr messageParameter2) { switch (message) { case MidiInterop.MidiInMessage.Data: if (this.MessageReceived != null) { this.MessageReceived(this, new MidiInMessageEventArgs(messageParameter1.ToInt32(), messageParameter2.ToInt32())); } break; case MidiInterop.MidiInMessage.Error: if (this.ErrorReceived != null) { this.ErrorReceived(this, new MidiInMessageEventArgs(messageParameter1.ToInt32(), messageParameter2.ToInt32())); } break; case MidiInterop.MidiInMessage.Open: case MidiInterop.MidiInMessage.Close: case MidiInterop.MidiInMessage.LongData: case MidiInterop.MidiInMessage.LongError: case (MidiInterop.MidiInMessage)967: case (MidiInterop.MidiInMessage)968: case (MidiInterop.MidiInMessage)969: case (MidiInterop.MidiInMessage)970: case (MidiInterop.MidiInMessage)971: case MidiInterop.MidiInMessage.MoreData: break; } } public static MidiInCapabilities DeviceInfo(int midiInDeviceNumber) { MidiInCapabilities capabilities = default(MidiInCapabilities); int size = Marshal.SizeOf(capabilities); MmException.Try(MidiInterop.midiInGetDevCaps((IntPtr)midiInDeviceNumber, out capabilities, size), "midiInGetDevCaps"); return capabilities; } protected virtual void Dispose(bool disposing) { if (!disposed) { MidiInterop.midiInClose(hMidiIn); } disposed = true; } ~MidiIn() { Dispose(disposing: false); } } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct MidiInCapabilities { private ushort manufacturerId; private ushort productId; private uint driverVersion; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] private string productName; private int support; private const int MaxProductNameLength = 32; public Manufacturers Manufacturer => (Manufacturers)manufacturerId; public int ProductId => productId; public string ProductName => productName; } public class MidiInMessageEventArgs : EventArgs { public int RawMessage { get; private set; } public MidiEvent MidiEvent { get; private set; } public int Timestamp { get; private set; } public MidiInMessageEventArgs(int message, int timestamp) { RawMessage = message; Timestamp = timestamp; try { MidiEvent = MidiEvent.FromRawMessage(message); } catch (Exception) { } } } internal class MidiInterop { public enum MidiInMessage { Open = 961, Close = 962, Data = 963, LongData = 964, Error = 965, LongError = 966, MoreData = 972 } public enum MidiOutMessage { Open = 967, Close, Done } public delegate void MidiInCallback(IntPtr midiInHandle, MidiInMessage message, IntPtr userData, IntPtr messageParameter1, IntPtr messageParameter2); public delegate void MidiOutCallback(IntPtr midiInHandle, MidiOutMessage message, IntPtr userData, IntPtr messageParameter1, IntPtr messageParameter2); public struct MMTIME { public int wType; public int u; } public struct MIDIEVENT { public int dwDeltaTime; public int dwStreamID; public int dwEvent; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public int dwParms; } public struct MIDIHDR { public IntPtr lpData; public int dwBufferLength; public int dwBytesRecorded; public IntPtr dwUser; public int dwFlags; public IntPtr lpNext; public IntPtr reserved; public int dwOffset; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] public IntPtr[] dwReserved; } public struct MIDIPROPTEMPO { public int cbStruct; public int dwTempo; } public const int CALLBACK_FUNCTION = 196608; public const int CALLBACK_NULL = 0; [DllImport("winmm.dll")] public static extern MmResult midiConnect(IntPtr hMidiIn, IntPtr hMidiOut, IntPtr pReserved); [DllImport("winmm.dll")] public static extern MmResult midiDisconnect(IntPtr hMidiIn, IntPtr hMidiOut, IntPtr pReserved); [DllImport("winmm.dll")] public static extern MmResult midiInAddBuffer(IntPtr hMidiIn, ref MIDIHDR lpMidiInHdr, int uSize); [DllImport("winmm.dll")] public static extern MmResult midiInClose(IntPtr hMidiIn); [DllImport("winmm.dll", CharSet = CharSet.Auto)] public static extern MmResult midiInGetDevCaps(IntPtr deviceId, out MidiInCapabilities capabilities, int size); [DllImport("winmm.dll")] public static extern MmResult midiInGetErrorText(int err, string lpText, int uSize); [DllImport("winmm.dll")] public static extern MmResult midiInGetID(IntPtr hMidiIn, out int lpuDeviceId); [DllImport("winmm.dll")] public static extern int midiInGetNumDevs(); [DllImport("winmm.dll")] public static extern MmResult midiInMessage(IntPtr hMidiIn, int msg, IntPtr dw1, IntPtr dw2); [DllImport("winmm.dll")] public static extern MmResult midiInOpen(out IntPtr hMidiIn, IntPtr uDeviceID, MidiInCallback callback, IntPtr dwInstance, int dwFlags); [DllImport("winmm.dll", EntryPoint = "midiInOpen")] public static extern MmResult midiInOpenWindow(out IntPtr hMidiIn, IntPtr uDeviceID, IntPtr callbackWindowHandle, IntPtr dwInstance, int dwFlags); [DllImport("winmm.dll")] public static extern MmResult midiInPrepareHeader(IntPtr hMidiIn, ref MIDIHDR lpMidiInHdr, int uSize); [DllImport("winmm.dll")] public static extern MmResult midiInReset(IntPtr hMidiIn); [DllImport("winmm.dll")] public static extern MmResult midiInStart(IntPtr hMidiIn); [DllImport("winmm.dll")] public static extern MmResult midiInStop(IntPtr hMidiIn); [DllImport("winmm.dll")] public static extern MmResult midiInUnprepareHeader(IntPtr hMidiIn, ref MIDIHDR lpMidiInHdr, int uSize); [DllImport("winmm.dll")] public static extern MmResult midiOutCacheDrumPatches(IntPtr hMidiOut, int uPatch, IntPtr lpKeyArray, int uFlags); [DllImport("winmm.dll")] public static extern MmResult midiOutCachePatches(IntPtr hMidiOut, int uBank, IntPtr lpPatchArray, int uFlags); [DllImport("winmm.dll")] public static extern MmResult midiOutClose(IntPtr hMidiOut); [DllImport("winmm.dll", CharSet = CharSet.Auto)] public static extern MmResult midiOutGetDevCaps(IntPtr deviceNumber, out MidiOutCapabilities caps, int uSize); [DllImport("winmm.dll")] public static extern MmResult midiOutGetErrorText(IntPtr err, string lpText, int uSize); [DllImport("winmm.dll")] public static extern MmResult midiOutGetID(IntPtr hMidiOut, out int lpuDeviceID); [DllImport("winmm.dll")] public static extern int midiOutGetNumDevs(); [DllImport("winmm.dll")] public static extern MmResult midiOutGetVolume(IntPtr uDeviceID, ref int lpdwVolume); [DllImport("winmm.dll")] public static extern MmResult midiOutLongMsg(IntPtr hMidiOut, ref MIDIHDR lpMidiOutHdr, int uSize); [DllImport("winmm.dll")] public static extern MmResult midiOutMessage(IntPtr hMidiOut, int msg, IntPtr dw1, IntPtr dw2); [DllImport("winmm.dll")] public static extern MmResult midiOutOpen(out IntPtr lphMidiOut, IntPtr uDeviceID, MidiOutCallback dwCallback, IntPtr dwInstance, int dwFlags); [DllImport("winmm.dll")] public static extern MmResult midiOutPrepareHeader(IntPtr hMidiOut, ref MIDIHDR lpMidiOutHdr, int uSize); [DllImport("winmm.dll")] public static extern MmResult midiOutReset(IntPtr hMidiOut); [DllImport("winmm.dll")] public static extern MmResult midiOutSetVolume(IntPtr hMidiOut, int dwVolume); [DllImport("winmm.dll")] public static extern MmResult midiOutShortMsg(IntPtr hMidiOut, int dwMsg); [DllImport("winmm.dll")] public static extern MmResult midiOutUnprepareHeader(IntPtr hMidiOut, ref MIDIHDR lpMidiOutHdr, int uSize); [DllImport("winmm.dll")] public static extern MmResult midiStreamClose(IntPtr hMidiStream); [DllImport("winmm.dll")] public static extern MmResult midiStreamOpen(out IntPtr hMidiStream, IntPtr puDeviceID, int cMidi, IntPtr dwCallback, IntPtr dwInstance, int fdwOpen); [DllImport("winmm.dll")] public static extern MmResult midiStreamOut(IntPtr hMidiStream, ref MIDIHDR pmh, int cbmh); [DllImport("winmm.dll")] public static extern MmResult midiStreamPause(IntPtr hMidiStream); [DllImport("winmm.dll")] public static extern MmResult midiStreamPosition(IntPtr hMidiStream, ref MMTIME lpmmt, int cbmmt); [DllImport("winmm.dll")] public static extern MmResult midiStreamProperty(IntPtr hMidiStream, IntPtr lppropdata, int dwProperty); [DllImport("winmm.dll")] public static extern MmResult midiStreamRestart(IntPtr hMidiStream); [DllImport("winmm.dll")] public static extern MmResult midiStreamStop(IntPtr hMidiStream); } public class MidiMessage { private int rawData; public int RawData => rawData; public MidiMessage(int status, int data1, int data2) { rawData = status + (data1 << 8) + (data2 << 16); } public MidiMessage(int rawData) { this.rawData = rawData; } public static MidiMessage StartNote(int note, int volume, int channel) { ValidateNoteParameters(note, volume, channel); return new MidiMessage(144 + channel - 1, note, volume); } private static void ValidateNoteParameters(int note, int volume, int channel) { ValidateChannel(channel); if (note < 0 || note > 127) { throw new ArgumentOutOfRangeException("note", "Note number must be in the range 0-127"); } if (volume < 0 || volume > 127) { throw new ArgumentOutOfRangeException("volume", "Velocity must be in the range 0-127"); } } private static void ValidateChannel(int channel) { if (channel < 1 || channel > 16) { throw new ArgumentOutOfRangeException("channel", channel, $"Channel must be 1-16 (Got {channel})"); } } public static MidiMessage StopNote(int note, int volume, int channel) { ValidateNoteParameters(note, volume, channel); return new MidiMessage(128 + channel - 1, note, volume); } public static MidiMessage ChangePatch(int patch, int channel) { ValidateChannel(channel); return new MidiMessage(192 + channel - 1, patch, 0); } public static MidiMessage ChangeControl(int controller, int value, int channel) { ValidateChannel(channel); return new MidiMessage(176 + channel - 1, controller, value); } } public class MidiOut : IDisposable { private IntPtr hMidiOut = IntPtr.Zero; private bool disposed; private MidiInterop.MidiOutCallback callback; public static int NumberOfDevices => MidiInterop.midiOutGetNumDevs(); public int Volume { get { int lpdwVolume = 0; MmException.Try(MidiInterop.midiOutGetVolume(hMidiOut, ref lpdwVolume), "midiOutGetVolume"); return lpdwVolume; } set { MmException.Try(MidiInterop.midiOutSetVolume(hMidiOut, value), "midiOutSetVolume"); } } public static MidiOutCapabilities DeviceInfo(int midiOutDeviceNumber) { MidiOutCapabilities caps = default(MidiOutCapabilities); int uSize = Marshal.SizeOf(caps); MmException.Try(MidiInterop.midiOutGetDevCaps((IntPtr)midiOutDeviceNumber, out caps, uSize), "midiOutGetDevCaps"); return caps; } public MidiOut(int deviceNo) { callback = Callback; MmException.Try(MidiInterop.midiOutOpen(out hMidiOut, (IntPtr)deviceNo, callback, IntPtr.Zero, 196608), "midiOutOpen"); } public void Close() { Dispose(); } public void Dispose() { GC.KeepAlive(callback); Dispose(disposing: true); GC.SuppressFinalize(this); } public void Reset() { MmException.Try(MidiInterop.midiOutReset(hMidiOut), "midiOutReset"); } public void SendDriverMessage(int message, int param1, int param2) { MmException.Try(MidiInterop.midiOutMessage(hMidiOut, message, (IntPtr)param1, (IntPtr)param2), "midiOutMessage"); } public void Send(int message) { MmException.Try(MidiInterop.midiOutShortMsg(hMidiOut, message), "midiOutShortMsg"); } protected virtual void Dispose(bool disposing) { if (!disposed) { MidiInterop.midiOutClose(hMidiOut); } disposed = true; } private void Callback(IntPtr midiInHandle, MidiInterop.MidiOutMessage message, IntPtr userData, IntPtr messageParameter1, IntPtr messageParameter2) { } public void SendBuffer(byte[] byteBuffer) { MidiInterop.MIDIHDR lpMidiOutHdr = default(MidiInterop.MIDIHDR); lpMidiOutHdr.lpData = Marshal.AllocHGlobal(byteBuffer.Length); Marshal.Copy(byteBuffer, 0, lpMidiOutHdr.lpData, byteBuffer.Length); lpMidiOutHdr.dwBufferLength = byteBuffer.Length; lpMidiOutHdr.dwBytesRecorded = byteBuffer.Length; int uSize = Marshal.SizeOf(lpMidiOutHdr); MidiInterop.midiOutPrepareHeader(hMidiOut, ref lpMidiOutHdr, uSize); if (MidiInterop.midiOutLongMsg(hMidiOut, ref lpMidiOutHdr, uSize) != 0) { MidiInterop.midiOutUnprepareHeader(hMidiOut, ref lpMidiOutHdr, uSize); } Marshal.FreeHGlobal(lpMidiOutHdr.lpData); } ~MidiOut() { Dispose(disposing: false); } } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] public struct MidiOutCapabilities { [Flags] private enum MidiOutCapabilityFlags { Volume = 1, LeftRightVolume = 2, PatchCaching = 4, Stream = 8 } private short manufacturerId; private short productId; private int driverVersion; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] private string productName; private short wTechnology; private short wVoices; private short wNotes; private ushort wChannelMask; private MidiOutCapabilityFlags dwSupport; private const int MaxProductNameLength = 32; public Manufacturers Manufacturer => (Manufacturers)manufacturerId; public short ProductId => productId; public string ProductName => productName; public int Voices => wVoices; public int Notes => wNotes; public bool SupportsAllChannels => wChannelMask == ushort.MaxValue; public bool SupportsPatchCaching => (dwSupport & MidiOutCapabilityFlags.PatchCaching) != 0; public bool SupportsSeparateLeftAndRightVolume => (dwSupport & MidiOutCapabilityFlags.LeftRightVolume) != 0; public bool SupportsMidiStreamOut => (dwSupport & MidiOutCapabilityFlags.Stream) != 0; public bool SupportsVolumeControl => (dwSupport & MidiOutCapabilityFlags.Volume) != 0; public MidiOutTechnology Technology => (MidiOutTechnology)wTechnology; public bool SupportsChannel(int channel) { return (wChannelMask & (1 << channel - 1)) > 0; } } public enum MidiOutTechnology { MidiPort = 1, Synth, SquareWaveSynth, FMSynth, MidiMapper, WaveTableSynth, SoftwareSynth } public class NoteEvent : MidiEvent { private int noteNumber; private int velocity; private static readonly string[] NoteNames = new string[12] { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; public virtual int NoteNumber { get { return noteNumber; } set { if (value < 0 || value > 127) { throw new ArgumentOutOfRangeException("value", "Note number must be in the range 0-127"); } noteNumber = value; } } public int Velocity { get { return velocity; } set { if (value < 0 || value > 127) { throw new ArgumentOutOfRangeException("value", "Velocity must be in the range 0-127"); } velocity = value; } } public string NoteName { get { if (Channel == 16 || Channel == 10) { return noteNumber switch { 35 => "Acoustic Bass Drum", 36 => "Bass Drum 1", 37 => "Side Stick", 38 => "Acoustic Snare", 39 => "Hand Clap", 40 => "Electric Snare", 41 => "Low Floor Tom", 42 => "Closed Hi-Hat", 43 => "High Floor Tom", 44 => "Pedal Hi-Hat", 45 => "Low Tom", 46 => "Open Hi-Hat", 47 => "Low-Mid Tom", 48 => "Hi-Mid Tom", 49 => "Crash Cymbal 1", 50 => "High Tom", 51 => "Ride Cymbal 1", 52 => "Chinese Cymbal", 53 => "Ride Bell", 54 => "Tambourine", 55 => "Splash Cymbal", 56 => "Cowbell", 57 => "Crash Cymbal 2", 58 => "Vibraslap", 59 => "Ride Cymbal 2", 60 => "Hi Bongo", 61 => "Low Bongo", 62 => "Mute Hi Conga", 63 => "Open Hi Conga", 64 => "Low Conga", 65 => "High Timbale", 66 => "Low Timbale", 67 => "High Agogo", 68 => "Low Agogo", 69 => "Cabasa", 70 => "Maracas", 71 => "Short Whistle", 72 => "Long Whistle", 73 => "Short Guiro", 74 => "Long Guiro", 75 => "Claves", 76 => "Hi Wood Block", 77 => "Low Wood Block", 78 => "Mute Cuica", 79 => "Open Cuica", 80 => "Mute Triangle", 81 => "Open Triangle", _ => $"Drum {noteNumber}", }; } int num = noteNumber / 12; return $"{NoteNames[noteNumber % 12]}{num}"; } } public NoteEvent(BinaryReader br) { NoteNumber = br.ReadByte(); velocity = br.ReadByte(); if (velocity > 127) { velocity = 127; } } public NoteEvent(long absoluteTime, int channel, MidiCommandCode commandCode, int noteNumber, int velocity) : base(absoluteTime, channel, commandCode) { NoteNumber = noteNumber; Velocity = velocity; } public override int GetAsShortMessage() { return base.GetAsShortMessage() + (noteNumber << 8) + (velocity << 16); } public override string ToString() { return $"{base.ToString()} {NoteName} Vel:{Velocity}"; } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write((byte)noteNumber); writer.Write((byte)velocity); } } public class NoteOnEvent : NoteEvent { private NoteEvent offEvent; public NoteEvent OffEvent { get { return offEvent; } set { if (!MidiEvent.IsNoteOff(value)) { throw new ArgumentException("OffEvent must be a valid MIDI note off event"); } if (value.NoteNumber != NoteNumber) { throw new ArgumentException("Note Off Event must be for the same note number"); } if (value.Channel != Channel) { throw new ArgumentException("Note Off Event must be for the same channel"); } offEvent = value; } } public override int NoteNumber { get { return base.NoteNumber; } set { base.NoteNumber = value; if (OffEvent != null) { OffEvent.NoteNumber = NoteNumber; } } } public override int Channel { get { return base.Channel; } set { base.Channel = value; if (OffEvent != null) { OffEvent.Channel = Channel; } } } public int NoteLength { get { return (int)(offEvent.AbsoluteTime - base.AbsoluteTime); } set { if (value < 0) { throw new ArgumentException("NoteLength must be 0 or greater"); } offEvent.AbsoluteTime = base.AbsoluteTime + value; } } public NoteOnEvent(BinaryReader br) : base(br) { } public NoteOnEvent(long absoluteTime, int channel, int noteNumber, int velocity, int duration) : base(absoluteTime, channel, MidiCommandCode.NoteOn, noteNumber, velocity) { OffEvent = new NoteEvent(absoluteTime, channel, MidiCommandCode.NoteOff, noteNumber, 0); NoteLength = duration; } public override MidiEvent Clone() { return new NoteOnEvent(base.AbsoluteTime, Channel, NoteNumber, base.Velocity, NoteLength); } public override string ToString() { if (base.Velocity == 0 && OffEvent == null) { return $"{base.ToString()} (Note Off)"; } return string.Format("{0} Len: {1}", base.ToString(), (OffEvent == null) ? "?" : NoteLength.ToString()); } } public class PatchChangeEvent : MidiEvent { private byte patch; private static readonly string[] patchNames = new string[128] { "Acoustic Grand", "Bright Acoustic", "Electric Grand", "Honky-Tonk", "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clav", "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", "Dulcimer", "Drawbar Organ", "Percussive Organ", "Rock Organ", "Church Organ", "Reed Organ", "Accoridan", "Harmonica", "Tango Accordian", "Acoustic Guitar(nylon)", "Acoustic Guitar(steel)", "Electric Guitar(jazz)", "Electric Guitar(clean)", "Electric Guitar(muted)", "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics", "Acoustic Bass", "Electric Bass(finger)", "Electric Bass(pick)", "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", "Orchestral Strings", "Timpani", "String Ensemble 1", "String Ensemble 2", "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2", "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Skakuhachi", "Whistle", "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)", "Lead 4 (chiff)", "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)", "Lead 8 (bass+lead)", "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)", "Pad 8 (sweep)", "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)", "FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bagpipe", "Fiddle", "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", "Applause", "Gunshot" }; public int Patch { get { return patch; } set { if (value < 0 || value > 127) { throw new ArgumentOutOfRangeException("value", "Patch number must be in the range 0-127"); } patch = (byte)value; } } public static string GetPatchName(int patchNumber) { return patchNames[patchNumber]; } public PatchChangeEvent(BinaryReader br) { patch = br.ReadByte(); if ((patch & 0x80u) != 0) { throw new FormatException("Invalid patch"); } } public PatchChangeEvent(long absoluteTime, int channel, int patchNumber) : base(absoluteTime, channel, MidiCommandCode.PatchChange) { Patch = patchNumber; } public override string ToString() { return $"{base.ToString()} {GetPatchName(patch)}"; } public override int GetAsShortMessage() { return base.GetAsShortMessage() + (patch << 8); } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write(patch); } } public class PitchWheelChangeEvent : MidiEvent { private int pitch; public int Pitch { get { return pitch; } set { if (value < 0 || value >= 16384) { throw new ArgumentOutOfRangeException("value", "Pitch value must be in the range 0 - 0x3FFF"); } pitch = value; } } public PitchWheelChangeEvent(BinaryReader br) { byte b = br.ReadByte(); byte b2 = br.ReadByte(); if ((b & 0x80u) != 0) { throw new FormatException("Invalid pitchwheelchange byte 1"); } if ((b2 & 0x80u) != 0) { throw new FormatException("Invalid pitchwheelchange byte 2"); } pitch = b + (b2 << 7); } public PitchWheelChangeEvent(long absoluteTime, int channel, int pitchWheel) : base(absoluteTime, channel, MidiCommandCode.PitchWheelChange) { Pitch = pitchWheel; } public override string ToString() { return $"{base.ToString()} Pitch {pitch} ({pitch - 8192})"; } public override int GetAsShortMessage() { return base.GetAsShortMessage() + ((pitch & 0x7F) << 8) + (((pitch >> 7) & 0x7F) << 16); } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write((byte)((uint)pitch & 0x7Fu)); writer.Write((byte)((uint)(pitch >> 7) & 0x7Fu)); } } public class RawMetaEvent : MetaEvent { public byte[] Data { get; set; } public RawMetaEvent(MetaEventType metaEventType, long absoluteTime, byte[] data) : base(metaEventType, (data != null) ? data.Length : 0, absoluteTime) { Data = data; } public override MidiEvent Clone() { return new RawMetaEvent(base.MetaEventType, base.AbsoluteTime, (byte[])Data?.Clone()); } public override string ToString() { StringBuilder stringBuilder = new StringBuilder().Append(base.ToString()); byte[] data = Data; foreach (byte b in data) { stringBuilder.AppendFormat(" {0:X2}", b); } return stringBuilder.ToString(); } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); if (Data != null) { writer.Write(Data, 0, Data.Length); } } } public class SequencerSpecificEvent : MetaEvent { private byte[] data; public byte[] Data { get { return data; } set { data = value; metaDataLength = data.Length; } } public SequencerSpecificEvent(BinaryReader br, int length) { data = br.ReadBytes(length); } public SequencerSpecificEvent(byte[] data, long absoluteTime) : base(MetaEventType.SequencerSpecific, data.Length, absoluteTime) { this.data = data; } public override MidiEvent Clone() { return new SequencerSpecificEvent((byte[])data.Clone(), base.AbsoluteTime); } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(base.ToString()); stringBuilder.Append(" "); byte[] array = data; foreach (byte b in array) { stringBuilder.AppendFormat("{0:X2} ", b); } stringBuilder.Length--; return stringBuilder.ToString(); } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write(data); } } public class SmpteOffsetEvent : MetaEvent { private readonly byte hours; private readonly byte minutes; private readonly byte seconds; private readonly byte frames; private readonly byte subFrames; public int Hours => hours; public int Minutes => minutes; public int Seconds => seconds; public int Frames => frames; public int SubFrames => subFrames; public SmpteOffsetEvent(byte hours, byte minutes, byte seconds, byte frames, byte subFrames) { this.hours = hours; this.minutes = minutes; this.seconds = seconds; this.frames = frames; this.subFrames = subFrames; } public SmpteOffsetEvent(BinaryReader br, int length) { if (length != 5) { throw new FormatException($"Invalid SMPTE Offset length: Got {length}, expected 5"); } hours = br.ReadByte(); minutes = br.ReadByte(); seconds = br.ReadByte(); frames = br.ReadByte(); subFrames = br.ReadByte(); } public override MidiEvent Clone() { return (SmpteOffsetEvent)MemberwiseClone(); } public override string ToString() { return $"{base.ToString()} {hours}:{minutes}:{seconds}:{frames}:{subFrames}"; } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write(hours); writer.Write(minutes); writer.Write(seconds); writer.Write(frames); writer.Write(subFrames); } } public class SysexEvent : MidiEvent { private byte[] data; public static SysexEvent ReadSysexEvent(BinaryReader br) { SysexEvent sysexEvent = new SysexEvent(); List<byte> list = new List<byte>(); bool flag = true; while (flag) { byte b = br.ReadByte(); if (b == 247) { flag = false; } else { list.Add(b); } } sysexEvent.data = list.ToArray(); return sysexEvent; } public override MidiEvent Clone() { return new SysexEvent { data = (byte[])data?.Clone() }; } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); byte[] array = data; foreach (byte b in array) { stringBuilder.AppendFormat("{0:X2} ", b); } return $"{base.AbsoluteTime} Sysex: {data.Length} bytes\r\n{stringBuilder.ToString()}"; } public override void Export(ref long absoluteTime, BinaryWriter writer) { base.Export(ref absoluteTime, writer); writer.Write(data, 0, data.Length); writer.Write((byte)247); } } public class TempoEvent : MetaEvent { private int microsecondsPerQuarterNote; public int MicrosecondsPerQuarterNote { get { return microsecondsPerQuarterNote; } set { microsecondsPerQuarterNote = value; } } public double Tempo { get { return 60000000.0 / (double)microsecondsPerQuarterNote; } set { microsecondsPerQuarterNote = (int)(60000000.0 / value); } } public TempoEvent(BinaryReader br, int length) { if (length != 3) { throw new FormatExcept