diff --git a/docs/preview-apis.md b/docs/preview-apis.md index 7e6ad5448848..77a79e881736 100644 --- a/docs/preview-apis.md +++ b/docs/preview-apis.md @@ -102,11 +102,19 @@ The [AppStore.RequestReview](https://developer.apple.com/documentation/storekit/appstore/3954432-requestreview/) method is Swift API we've bound manually, and as such it's marked as experimental until .NET 10. -[1]: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute?view=net-8.0 - ## Rgen (APL0003) Rgen is the new Roslyn codegenerator based binding tool. The tool is underdevelopment and its API is open to change until a stable release is announced. The diagnostic id for Rgen is APL0003. + +## CoreMidi.MidiDriver (APL0004) + +The [MIDIDevice](https://developer.apple.com/documentation/coremidi/midi-drivers) API is untested, and as such it's marked experimental until .NET 11. + +The diagnostic id for MidiDevice is APL0004. + + + +[1]: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute?view=net-8.0 \ No newline at end of file diff --git a/src/CoreFoundation/CFUuidBytes.cs b/src/CoreFoundation/CFUuidBytes.cs new file mode 100644 index 000000000000..125a830479ff --- /dev/null +++ b/src/CoreFoundation/CFUuidBytes.cs @@ -0,0 +1,23 @@ +namespace CoreFoundation { + // This struct is only used for P/Invokes. + struct CFUuidBytes { +#pragma warning disable CS0649 // Field '...' is never assigned to, and will always have its default value 0 + public byte Byte0; + public byte Byte1; + public byte Byte2; + public byte Byte3; + public byte Byte4; + public byte Byte5; + public byte Byte6; + public byte Byte7; + public byte Byte8; + public byte Byte9; + public byte Byte10; + public byte Byte11; + public byte Byte12; + public byte Byte13; + public byte Byte14; + public byte Byte15; +#pragma warning restore CS0649 + } +} diff --git a/src/CoreMidi/MidiBluetoothDriver.cs b/src/CoreMidi/MidiBluetoothDriver.cs index 4a72bf41ec88..5ca73b3fc193 100644 --- a/src/CoreMidi/MidiBluetoothDriver.cs +++ b/src/CoreMidi/MidiBluetoothDriver.cs @@ -1,5 +1,4 @@ -#if !TVOS -// + // MidiBluetoothDriver.cs // // Authors: TJ Lambert (TJ.Lambert@microsoft.com) @@ -38,4 +37,3 @@ public static int Disconnect (NSString uuid) } } } -#endif diff --git a/src/CoreMidi/MidiDriverInterface.cs b/src/CoreMidi/MidiDriverInterface.cs new file mode 100644 index 000000000000..2fc4784151e7 --- /dev/null +++ b/src/CoreMidi/MidiDriverInterface.cs @@ -0,0 +1,373 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Let's hope that by .NET 11 we've ironed out all the bugs in the API. +// This can of course be adjusted as needed (until we've released as stable). +#if NET110_0_OR_GREATER +#define STABLE_MIDIDRIVER +#endif + + +#if !__TVOS__ + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Threading; + +using CoreFoundation; +using ObjCRuntime; + +using MidiObjectRef = System.Int32; +using MidiClientRef = System.Int32; +using MidiDeviceRef = System.Int32; +using MidiDeviceListRef = System.Int32; +using MidiDriverRef = System.IntPtr; +using MidiPortRef = System.Int32; +using MidiEndpointRef = System.Int32; +using MidiEntityRef = System.Int32; + +using MidiEventListPointer = System.IntPtr; +using MidiPacketListPointer = System.IntPtr; + +using HRESULT = System.Int32; + +namespace CoreMidi { +#if !STABLE_MIDIDRIVER + [Experimental ("APL0004")] +#endif + [SupportedOSPlatform ("ios")] + [SupportedOSPlatform ("maccatalyst")] + [SupportedOSPlatform ("macos")] + public abstract class MidiDriver { +#if !COREBUILD + unsafe MidiDriverInterface* driverInterface; + + unsafe internal MidiDriverInterface* DriverInterface { get => driverInterface; } + + unsafe protected MidiDriver () + { + driverInterface = CreateDriver (); + } + + unsafe MidiDriverInterface* CreateDriver () + { + var iface = (MidiDriverInterface*) Marshal.AllocHGlobal (sizeof (MidiDriverInterface)); + iface->QueryInterface = &QueryInterface; + iface->AddRef = &AddRef; + iface->Release = &Release; + iface->FindDevices = &FindDevices; + iface->Start = &Start; + iface->Stop = &Stop; + iface->Configure = &Configure; + iface->Send = &Send; + iface->EnableSource = &EnableSource; + iface->Flush = &Flush; + iface->Monitor = &Monitor; + iface->SendPackets = &SendPackets; + iface->MonitorEvents = &MonitorEvents; + iface->gchandle = (IntPtr) GCHandle.Alloc (this, GCHandleType.Weak); + iface->referenceCount = 1; // managed code has one reference + return iface; + } + + ~MidiDriver () + { + Release (); // release managed code's reference + } + + [UnmanagedCallersOnly] + unsafe static HRESULT QueryInterface (MidiDriverInterface* self, CFUuidBytes iid, void* ppv) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.QueryInterface ({(IntPtr) self}, {iid}, {(IntPtr) ppv}) => {driver}"); + return driver?.QueryInterface (iid, (IntPtr) ppv) ?? 0; + } + + internal virtual HRESULT QueryInterface (CFUuidBytes iid, IntPtr ppv) + { + Console.WriteLine ($"{GetType ()}.QueryInterface ({iid}, {(IntPtr) ppv})"); + return 0; + } + + static List strongReferences = new List (); + + [UnmanagedCallersOnly] + unsafe static uint AddRef (MidiDriverInterface* self) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.AddRef ({(IntPtr) self}) => {driver}"); + return driver?.AddRef () ?? 0; + } + + unsafe internal virtual uint AddRef () + { + Console.WriteLine ($"{GetType ()}.AddRef () => referenceCount={driverInterface->referenceCount}"); + uint referenceCount; + lock (strongReferences) { + referenceCount = Interlocked.Increment (ref driverInterface->referenceCount); + if (referenceCount == 2) { + strongReferences.Add (this); + } + } + return referenceCount; + } + + [UnmanagedCallersOnly] + unsafe static uint Release (MidiDriverInterface* self) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.Release ({(IntPtr) self}) => {driver}"); + return driver?.AddRef () ?? 0; + } + + unsafe internal virtual uint Release () + { + Console.WriteLine ($"{GetType ()}.Release () => referenceCount={driverInterface->referenceCount}"); + uint referenceCount; + lock (strongReferences) { + referenceCount = Interlocked.Decrement (ref driverInterface->referenceCount); + if (referenceCount == 1) { + strongReferences.Remove (this); + } else if (referenceCount == 0) { + var gchandle = GCHandle.FromIntPtr (driverInterface->gchandle); + gchandle.Free (); + driverInterface->gchandle = IntPtr.Zero; + Marshal.FreeHGlobal ((IntPtr) driverInterface); + driverInterface = null; + } + } + return referenceCount; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus FindDevices (MidiDriverInterface* self, MidiDeviceListRef devList) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.FindDevices ({(IntPtr) self}, {devList}) => {driver}"); + return driver?.FindDevices (devList) ?? 0; + } + + /// The server requests that the driver detects any present devices. For each detected device, call and , and then add the device to the supplied . + protected virtual OSStatus FindDevices (MidiDeviceListRef deviceList /* FIXME: strongly typed */) + { + return 0; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus Start (MidiDriverInterface* self, MidiDeviceListRef devList) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.Start ({(IntPtr) self}, {devList}) => {driver}"); + return driver?.Start (devList) ?? 0; + } + + /// Start MIDI I/O. + protected virtual OSStatus Start (MidiDeviceListRef deviceList /* FIXME: strongly typed */) + { + return 0; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus Stop (MidiDriverInterface* self) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.Stop ({(IntPtr) self}) => {driver}"); + return driver?.Stop () ?? 0; + } + + /// Stop MIDI I/O. + protected virtual OSStatus Stop () + { + return 0; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus Configure (MidiDriverInterface* self, MidiDeviceRef device) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.Configure ({(IntPtr) self}, {device}) => {driver}"); + return driver?.Configure (device) ?? 0; + } + + /// Not used at the moment. + protected virtual OSStatus Configure (MidiDeviceRef device) + { + return 0; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus Send (MidiDriverInterface* self, MidiPacketListPointer pktList, void* destRefCon1, void* destRefCon2) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.Send ({(IntPtr) self}, {pktList}, {(IntPtr) destRefCon1}, {(IntPtr) destRefCon2}) => {driver}"); + return driver?.Send (pktList, destRefCon1, destRefCon2) ?? 0; + } + + /// Send a MidiPacketList to the destination endpoint. + protected unsafe virtual OSStatus Send (MidiPacketListPointer pktList, void* destRefCon1, void* destRefCon2) + { + return 0; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus EnableSource (MidiDriverInterface* self, MidiEndpointRef src, byte enabled) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.EnableSource ({(IntPtr) self}, {src}, {enabled}) => {driver}"); + return driver?.EnableSource (src, enabled != 0) ?? 0; + } + + /// Lets the driver know if a particular source has any listeners or not. + protected unsafe virtual OSStatus EnableSource (MidiEndpointRef src, bool enabled) + { + return 0; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus Flush (MidiDriverInterface* self, MidiEndpointRef dest, void* destRefCon1, void* destRefCon2) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.Flush ({(IntPtr) self}, {dest}, {(IntPtr) destRefCon1}, {(IntPtr) destRefCon2}) => {driver}"); + return driver?.Flush (dest, destRefCon1, destRefCon2) ?? 0; + } + + /// Unschedule all pending output to the specified destination endpoint (or all endpoints if null). + protected unsafe virtual OSStatus Flush (MidiEndpointRef src, void* destRefCon1, void* destRefCon2) + { + return 0; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus Monitor (MidiDriverInterface* self, MidiEndpointRef dest, MidiPacketListPointer pktList) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.Monitor ({(IntPtr) self}, {dest}, {pktList}) => {driver}"); + return driver?.Monitor (dest, pktList) ?? 0; + } + + /// If monitoring is enabled, this method will be called with all outgoing MIDI messages. + protected unsafe virtual OSStatus Monitor (MidiEndpointRef src, MidiPacketListPointer packetList) + { + return 0; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus SendPackets (MidiDriverInterface* self, MidiEventListPointer pktList, void* destRefCon1, void* destRefCon2) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.SendPackets ({(IntPtr) self}, {pktList}, {(IntPtr) destRefCon1}, {(IntPtr) destRefCon2}) => {driver}"); + return driver?.SendPackets (pktList, destRefCon1, destRefCon2) ?? 0; + } + + /// Send a to the destination endpoint. + protected unsafe virtual OSStatus SendPackets (MidiEventListPointer pktList, void* destRefCon1, void* destRefCon2) + { + return 0; + } + + [UnmanagedCallersOnly] + unsafe static OSStatus MonitorEvents (MidiDriverInterface* self, MidiEndpointRef dest, MidiEventListPointer pktList) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.MonitorEvents ({(IntPtr) self}, {dest}, {pktList}) => {driver}"); + return driver?.MonitorEvents (dest, pktList) ?? 0; + } + + /// Same as , but sending a instead of a MidiPacketList. + protected unsafe virtual OSStatus MonitorEvents (MidiEndpointRef dest, MidiEventListPointer pktList) + { + return 0; + } + + [DllImport (Constants.CoreMidiLibrary)] + unsafe static extern MidiDeviceListRef MIDIGetDriverDeviceList (MidiDriverInterface** driver); + + /// Get the devices this driver owns or created. + /// If successful, a list of the device this driver owns or created. Otherwise null. + public unsafe MidiDeviceList? GetDeviceList () + { + fixed (MidiDriverInterface** driverInterfacePtr = &driverInterface) { + var rv = MIDIGetDriverDeviceList (driverInterfacePtr); + if (rv == MidiObject.InvalidRef) + return null; + return new MidiDeviceList (rv); + } + } + + [DllImport (Constants.CoreMidiLibrary)] + static extern IntPtr /* CFRunLoopRef */ MIDIGetDriverIORunLoop (); + + /// Get the high (realtime) priority run loop that can be used for asynchronous I/O completion callbacks. + /// If successful, the IO run loop. Otherwise null. + public static CFRunLoop? GetIORunLoop () + { + var rv = MIDIGetDriverIORunLoop (); + if (rv == IntPtr.Zero) + return null; + return new CFRunLoop (rv, false); + } + +#if MONOMAC + [SupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("ios")] + [UnsupportedOSPlatform ("tvos")] + [UnsupportedOSPlatform ("maccatalyst")] + [DllImport (Constants.CoreMidiLibrary)] + unsafe static extern OSStatus MIDIDriverEnableMonitoring (MidiDriverInterface** driver, byte enabled); + + /// A driver can call this method to receive all the outgoing MIDI packets in the system. + /// Whether to enable or disable monitoring. + /// A status code that describes the result of the operation. This will be in case of success. + [SupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("ios")] + [UnsupportedOSPlatform ("tvos")] + [UnsupportedOSPlatform ("maccatalyst")] + public unsafe MidiError EnableMonitoring (bool enabled) + { + fixed (MidiDriverInterface **driver = &driverInterface) { + return (MidiError) MIDIDriverEnableMonitoring (driver, enabled.AsByte ()); + } + } +#endif // MONOMAC +#endif // COREBUILD + } + +#if !COREBUILD +#if !STABLE_MIDIDRIVER + [Experimental ("APL0004")] +#endif + struct MidiDriverInterface { +#pragma warning disable CS0169 // The field 'MidiDriverInterface._reserved' is never used + IntPtr _reserved; +#pragma warning restore CS0169 + internal unsafe delegate* unmanaged QueryInterface; + internal unsafe delegate* unmanaged AddRef; + internal unsafe delegate* unmanaged Release; + internal unsafe delegate* unmanaged FindDevices; + internal unsafe delegate* unmanaged Start; + internal unsafe delegate* unmanaged Stop; + internal unsafe delegate* unmanaged Configure; + internal unsafe delegate* unmanaged Send; + internal unsafe delegate* unmanaged EnableSource; + internal unsafe delegate* unmanaged Flush; + internal unsafe delegate* unmanaged Monitor; + internal unsafe delegate* unmanaged SendPackets; + internal unsafe delegate* unmanaged MonitorEvents; + + internal IntPtr gchandle; // this is our own + internal uint referenceCount; // this is our own + + internal MidiDriver? GetObject () + { + var gchandle = this.gchandle; + if (gchandle == IntPtr.Zero) + return null; + return (MidiDriver?) GCHandle.FromIntPtr (gchandle).Target; + } + } +#endif // COREBUILD +} + +#endif // !__TVOS__ + diff --git a/src/CoreMidi/MidiEventList.cs b/src/CoreMidi/MidiEventList.cs new file mode 100644 index 000000000000..59c889c230a1 --- /dev/null +++ b/src/CoreMidi/MidiEventList.cs @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +using Foundation; +using ObjCRuntime; + +using MidiEndpointRef = System.Int32; +using MidiPortRef = System.Int32; + +#nullable enable + +namespace CoreMidi { + /// This class represents the Objective-C struct MIDIEventList, which is a list of packets. + [SupportedOSPlatform ("ios14.0")] + [SupportedOSPlatform ("tvos15.0")] + [SupportedOSPlatform ("macos")] + [SupportedOSPlatform ("maccatalyst")] + // [NativeName ("MIDIEventList")] + public sealed class MidiEventList : IEnumerable, IDisposable { + /* This is a variable sized struct, so store all the data in a byte array. + * struct MIDIEventList + * { + * MIDIProtocolID protocol; + * UInt32 numPackets; + * MIDIEventPacket packet[1]; + * }; + */ + + // this struct is just used internally to avoid some manual pointer math + struct MIDIEventList { +#pragma warning disable CS0649 // Field '...' is never assigned to, and will always have its default value +#pragma warning disable CS0169 // The field '...' is never used + internal MidiProtocolId protocol; + internal uint numPackets; + internal MidiEventPacket packet; +#pragma warning restore CS0169 +#pragma warning restore CS0649 + } + + unsafe MIDIEventList* midiDataPointer; + int midiDataSize; + bool owns; + unsafe MidiEventPacket* currentPacket; + + const int MinimumSize = 276; /* 4 + 4 + sizeof (MidiEventPacket) */ + + /// The protocol for the packets in this list of packets. + /// The protocol for the packets in this list of packets. + public unsafe MidiProtocolId Protocol { + get { + return midiDataPointer->protocol; + } + } + + /// The number of packets in this list. + /// The number of packets in this list. + public unsafe uint PacketCount { + get { + return midiDataPointer->numPackets; + } + } + + unsafe internal void* MidiData { get => midiDataPointer; } + + /// Create a new list with the minimum size. + /// The protocol for the packets in the created list. + /// A newly created , or an exception in case of failure. + public MidiEventList (MidiProtocolId protocol) + : this (protocol, MinimumSize) + { + } + + /// Create a new for the specified protocol and size. + /// The protocol for the event list. + /// The size, in number of bytes, of the event list. Minimum size is 276 bytes. + /// A newly created , or an exception in case of failure. + public MidiEventList (MidiProtocolId protocol, int size) + { + if (size < MinimumSize) + throw new ArgumentOutOfRangeException ($"{nameof (size)} must be at least {MinimumSize}."); + + midiDataSize = size; + owns = true; + unsafe { + midiDataPointer = (MIDIEventList*) Marshal.AllocHGlobal (midiDataSize); + currentPacket = MIDIEventListInit (midiDataPointer, protocol); + if (currentPacket is null) + throw new Exception ($"Failed to create midi event list."); + } + } + + /// Create a new for a given block of memory. + /// A pointer to a block of memory with the event list. + /// A newly created , or an exception in case of failure. + public MidiEventList (IntPtr eventListPointer) + { + unsafe { + midiDataPointer = (MIDIEventList*) eventListPointer; + owns = false; + midiDataSize = -1; + } + } + + public void Dispose () + { + Dispose (true); + } + + void Dispose (bool disposing) + { + if (owns) { + unsafe { + Marshal.FreeHGlobal ((IntPtr) midiDataPointer); + midiDataPointer = null; + } + } + GC.SuppressFinalize (this); + } + + ~MidiEventList () + { + Dispose (false); + } + +#if !__TVOS__ + /// Send the packets in this list to the specified . + /// The port through which the packets are sent. + /// The destination where the packets are sent. + /// A non-zero error code in case of failure, otherwise zero (which indicates success). + [SupportedOSPlatform ("ios14.0")] + [UnsupportedOSPlatform ("tvos")] + [SupportedOSPlatform ("macos")] + [SupportedOSPlatform ("maccatalyst")] + public unsafe int /* OSStatus */ Send (MidiPort port, MidiEndpoint destination) + { + return MIDISendEventList (port.Handle, destination.Handle, midiDataPointer); + } + + /// Distribute the packets from the specified . + /// The endpoint where the packates come from. + /// A non-zero error code in case of failure, otherwise zero (which indicates success). + [SupportedOSPlatform ("ios14.0")] + [UnsupportedOSPlatform ("tvos")] + [SupportedOSPlatform ("macos")] + [SupportedOSPlatform ("maccatalyst")] + public unsafe int /* OSStatus */ Receive (MidiEndpoint source) + { + return MIDIReceivedEventList (source.Handle, midiDataPointer); + } +#endif + + /// Add a new to this lis. + /// The timestamp for the new packet. + /// The data for the midi event to add. + /// True if successful, otherwise false (which typically means there's not enough space for the new packet). + public unsafe bool Add (ulong time, uint [] words) + { + if (midiDataSize < 0) + throw new InvalidOperationException ($"Can't add to a MidiEventList initialized from a raw pointer."); + + fixed (uint* wordsPtr = words) { + var rv = MIDIEventListAdd (midiDataPointer, (ulong) midiDataSize, currentPacket, time, (ulong) words.Length, (byte*) wordsPtr); + if (rv is not null) { + currentPacket = rv; + return true; + } + return false; + } + } + + [DllImport (Constants.CoreMidiLibrary)] + unsafe static extern MidiEventPacket* MIDIEventListInit (MIDIEventList* evtlist, MidiProtocolId /* MIDIProtocolID */ protocol); + + [DllImport (Constants.CoreMidiLibrary)] + unsafe static extern MidiEventPacket* MIDIEventListAdd ( + MIDIEventList* evtlist, + ulong /* ByteCount = unsigned long */ listSize, + MidiEventPacket* curPacket, + ulong /* MIDITimeStamp */ time, + ulong /* ByteCount = unsigned long */ wordCount, + byte* /* const UInt32 * */ words); + +#if !__TVOS__ + [SupportedOSPlatform ("ios14.0")] + [UnsupportedOSPlatform ("tvos")] + [SupportedOSPlatform ("macos")] + [SupportedOSPlatform ("maccatalyst")] + [DllImport (Constants.CoreMidiLibrary)] + unsafe static extern int /* OSStatus */ MIDISendEventList (MidiPortRef port, MidiEndpointRef dest, MIDIEventList* evtList); + + [SupportedOSPlatform ("ios14.0")] + [UnsupportedOSPlatform ("tvos")] + [SupportedOSPlatform ("macos")] + [SupportedOSPlatform ("maccatalyst")] + [DllImport (Constants.CoreMidiLibrary)] + unsafe static extern int /* OSStatus */ MIDIReceivedEventList (MidiEndpointRef src, MIDIEventList* evtlist); +#endif // !__TVOS__ + + IEnumerator IEnumerable.GetEnumerator () + { + MidiEventPacket packetToYield; + IntPtr packetPtr; + + if (PacketCount == 0) + yield break; + + unsafe { + MidiEventPacket* packet = &midiDataPointer->packet; + packetToYield = *packet; + packetPtr = (IntPtr) packet; + } + yield return packetToYield; + + for (var i = 1; i < PacketCount; i++) { + unsafe { + MidiEventPacket* packet = (MidiEventPacket*) packetPtr; + uint* wordPointer = &packet->word_00; + packet = (MidiEventPacket*) wordPointer [packet->WordCount]; + packetToYield = *packet; + packetPtr = (IntPtr) packet; + } + yield return packetToYield; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () + { + return ((IEnumerable) this).GetEnumerator (); + } + + /// Iterate over each in this list without allocating or copying memory. + /// The function to call for each packet. + public unsafe void Iterate (MidiEventListIterator callback) + { + if (PacketCount == 0) + return; + + MidiEventPacket* packet = &midiDataPointer->packet; + callback (ref Unsafe.AsRef (packet)); + for (var i = 1; i < PacketCount; i++) { + uint* wordPointer = &packet->word_00; + packet = (MidiEventPacket*) wordPointer [packet->WordCount]; + callback (ref Unsafe.AsRef (packet)); + } + } + } + + /// The delegate type used by . + /// The current packet found when iterating. + public delegate void MidiEventListIterator (ref MidiEventPacket packet); +} diff --git a/src/CoreMidi/MidiEventPacket.cs b/src/CoreMidi/MidiEventPacket.cs new file mode 100644 index 000000000000..bf8977fd2d44 --- /dev/null +++ b/src/CoreMidi/MidiEventPacket.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +using Foundation; +using ObjCRuntime; + +using MidiEndpointRef = System.Int32; +using MidiPortRef = System.Int32; + +#nullable enable + +namespace CoreMidi { + /// This class represents the Objective-C struct MIDIEventPacket, which is a variable-sized struct. + [NativeName ("MIDIEventPacket")] + public struct MidiEventPacket { + ulong /* MIDITimeStamp */ timeStamp; + uint /* UInt32 */ wordCount; + + /* UInt32 words[64]; */ + internal uint word_00; + uint word_01; + uint word_02; + uint word_03; + uint word_04; + uint word_05; + uint word_06; + uint word_07; + uint word_08; + uint word_09; + uint word_10; + uint word_11; + uint word_12; + uint word_13; + uint word_14; + uint word_15; + uint word_16; + uint word_17; + uint word_18; + uint word_19; + uint word_20; + uint word_21; + uint word_22; + uint word_23; + uint word_24; + uint word_25; + uint word_26; + uint word_27; + uint word_28; + uint word_29; + uint word_30; + uint word_31; + uint word_32; + uint word_33; + uint word_34; + uint word_35; + uint word_36; + uint word_37; + uint word_38; + uint word_39; + uint word_40; + uint word_41; + uint word_42; + uint word_43; + uint word_44; + uint word_45; + uint word_46; + uint word_47; + uint word_48; + uint word_49; + uint word_50; + uint word_51; + uint word_52; + uint word_53; + uint word_54; + uint word_55; + uint word_56; + uint word_57; + uint word_58; + uint word_59; + uint word_60; + uint word_61; + uint word_62; + uint word_63; + + /// The timestamp for this packet. + /// The timestamp for this packet. + public ulong Timestamp { + get => timeStamp; + set => timeStamp = value; + } + + /// The number of 32-bit Midi words in this packet. + /// The number of 32-bit Midi words in this packet. + public uint WordCount { + get => wordCount; + set { + if (value > 64) + throw new ArgumentOutOfRangeException ($"WordCount can't be higher than 64."); + wordCount = value; + } + } + + /// All the 32-bit Midi words in this packet. + /// All the 32-bit Midi words in this packet. + public uint [] Words { + get { + var wc = wordCount; + if (wc > 64) + throw new ArgumentOutOfRangeException ($"WordCount can't be higher than 64."); + var rv = new uint [wc]; + unsafe { + fixed (uint* destination = rv) { + fixed (uint* source = &word_00) { + Buffer.MemoryCopy (source, destination, rv.Length * sizeof (uint), wc * sizeof (uint)); + } + } + } + return rv; + } + set { + if (value is null) + ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (value)); + + if (value.Length > 64) + throw new ArgumentOutOfRangeException ($"WordCount can't be higher than 64."); + wordCount = (uint) value.Length; + unsafe { + fixed (uint* destination = &word_00) { + fixed (uint* source = value) { + Buffer.MemoryCopy (source, destination, 64 * sizeof (uint), value.Length * sizeof (uint)); + } + } + } + } + } + + /// An indexer for the 32-bit Midi words in this packet. + /// The index of the 32-bit Midi word to set or get. + /// The 32-bit Midi words for specified index. + public uint this [int index] { + get { + if (index < 0) + throw new ArgumentOutOfRangeException ($"index must be positive."); + if (index >= 64) + throw new ArgumentOutOfRangeException ($"index must be less than 64."); + if (index + 1 > wordCount) + throw new ArgumentOutOfRangeException ($"index must be less than WordCount."); + unsafe { + fixed (uint* firstWord = &word_00) + return firstWord [index]; + } + } + set { + if (index < 0) + throw new ArgumentOutOfRangeException ($"index must be positive."); + if (index >= 64) + throw new ArgumentOutOfRangeException ($"index must be less than 64."); + if (index + 1 > wordCount) + throw new ArgumentOutOfRangeException ($"index must be less than WordCount."); + unsafe { + fixed (uint* firstWord = &word_00) + firstWord [index] = value; + } + } + } + +#if !__TVOS__ + + [SupportedOSPlatform ("ios17.0")] + [SupportedOSPlatform ("maccatalyst17.0")] + [SupportedOSPlatform ("macos14.0")] + [UnsupportedOSPlatform ("tvos")] + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDIEventPacketSysexBytesForGroup (MidiEventPacket* pkt, byte /* UInt8 */ groupIndex, IntPtr* /* CFDataRef __nullable * __mononull */ outData); + + /// Get MIDI 1.0 sysex bytes on the specified group. + /// The index of the target group. + /// A status code that describes the result of the operation. This will be in case of success. + /// An that contains the requested byte stream. + [SupportedOSPlatform ("ios17.0")] + [SupportedOSPlatform ("maccatalyst17.0")] + [SupportedOSPlatform ("macos14.0")] + [UnsupportedOSPlatform ("tvos")] + public unsafe NSData? GetSysexBytes (byte groupIndex, out MidiError status) + { + var handle = default (IntPtr); + + fixed (MidiEventPacket* self = &this) { + status = (MidiError) MIDIEventPacketSysexBytesForGroup (self, groupIndex, &handle); + } + if (handle == IntPtr.Zero) + return null; + return Runtime.GetNSObject (handle, false); + } +#endif // !__TVOS__ + } +} diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index 29d2476ee654..7ae00370c235 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -1,4 +1,3 @@ -#if !TVOS // // MidiServices.cs: Implementation of the MidiObject base class and its derivates // @@ -39,10 +38,21 @@ #nullable enable +// Let's hope that by .NET 11 we've ironed out all the bugs in the API. +// This can of course be adjusted as needed (until we've released as stable). +#if NET110_0_OR_GREATER +#define STABLE_MIDIDRIVER +#endif + using System; using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + using ObjCRuntime; using CoreFoundation; using Foundation; @@ -57,6 +67,7 @@ namespace CoreMidi { +#if !TVOS // anonymous enum - MIDIServices.h /// Errors raised by the CoreMIDI stack. /// @@ -95,9 +106,11 @@ public enum MidiError : int { /// To be added. NotPermitted = -10844, } +#endif // TVOS [Flags] // SInt32 - MIDIServices.h + [NativeName ("MIDIObjectType")] enum MidiObjectType : int { Other = -1, Device, @@ -111,6 +124,7 @@ enum MidiObjectType : int { ExternalDestination = ExternalMask | Destination, } +#if !TVOS public static partial class Midi { #if !COREBUILD [DllImport (Constants.CoreMidiLibrary)] @@ -205,6 +219,80 @@ public static nint DeviceCount { return null; return new MidiDevice (h); } + + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDIExternalDeviceCreate (IntPtr /* CFStringRef */ name, IntPtr /* CFStringRef */ manufacturer, IntPtr /* CFStringRef */ model, MidiDeviceRef* outDevice); + + /// Create a new external MIDI device. + /// The name for the new device. + /// The manufacturer for the new device. + /// The model for the new device. + /// A status code that describes the result of creating the new external device. This will be in case of success. + /// A newly created external instance, null otherwise. + public static MidiDevice? CreateExternalDevice (string name, string manufacturer, string model, out MidiError status) + { + using var namePtr = new TransientCFString (name); + using var manufacturerPtr = new TransientCFString (manufacturer); + using var modelPtr = new TransientCFString (model); + + var handle = default (MidiDeviceRef); + unsafe { + status = (MidiError) MIDIExternalDeviceCreate (namePtr, manufacturerPtr, modelPtr, &handle); + } + if (status != MidiError.Ok) + return null; + return new MidiDevice (handle); + } +#endif // !COREBUILD + } + + /// This class consists of functions that customize the global state of the MIDI system. + public static class MidiSetup { +#if !COREBUILD + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDISetupAddDevice (MidiDeviceRef device); + + /// Add a device to the current MIDI setup. + /// The device to add to the current MIDI setup. + /// A status code that describes the result of the operation. This will be in case of success. + public static MidiError AddDevice (MidiDevice device) + { + return (MidiError) MIDISetupAddDevice (device.GetCheckedHandle ()); + } + + + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDISetupRemoveDevice (MidiDeviceRef device); + + /// Remove a device from the current MIDI setup. + /// The device to remove from the current MIDI setup. + /// A status code that describes the result of the operation. This will be in case of success. + public static MidiError RemoveDevice (MidiDevice device) + { + return (MidiError) MIDISetupRemoveDevice (device.GetCheckedHandle ()); + } + + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDISetupAddExternalDevice (MidiDeviceRef device); + + /// Remove a device from the current MIDI setup. + /// The device to remove from the current MIDI setup. + /// A status code that describes the result of the operation. This will be in case of success. + public static MidiError AddExternalDevice (MidiDevice device) + { + return (MidiError) MIDISetupAddExternalDevice (device.GetCheckedHandle ()); + } + + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDISetupRemoveExternalDevice (MidiDeviceRef device); + + /// Remove a device from the current MIDI setup. + /// The device to remove from the current MIDI setup. + /// A status code that describes the result of the operation. This will be in case of success. + public static MidiError RemoveExternalDevice (MidiDevice device) + { + return (MidiError) MIDISetupRemoveExternalDevice (device.GetCheckedHandle ()); + } #endif // !COREBUILD } @@ -239,6 +327,14 @@ internal MidiObjectRef MidiHandle { get { return handle; } } + internal MidiObjectRef GetCheckedHandle () + { + if (handle == MidiObject.InvalidRef) + throw new ObjectDisposedException ("handle"); + + return handle; + } + internal MidiObject () { owns = true; @@ -539,11 +635,18 @@ public class MidiClient : MidiObject { [SupportedOSPlatform ("ios")] [SupportedOSPlatform ("maccatalyst")] [SupportedOSPlatform ("macos")] - [ObsoletedOSPlatform ("macos11.0")] - [ObsoletedOSPlatform ("ios14.0")] - [ObsoletedOSPlatform ("maccatalyst14.0")] + [ObsoletedOSPlatform ("macos11.0", "Call 'MIDISourceCreateWithProtocol' instead.")] + [ObsoletedOSPlatform ("ios14.0", "Call 'MIDISourceCreateWithProtocol' instead.")] + [ObsoletedOSPlatform ("maccatalyst14.0", "Call 'MIDISourceCreateWithProtocol' instead.")] [DllImport (Constants.CoreMidiLibrary)] - unsafe extern static int /* OSStatus = SInt32 */ MIDISourceCreate (MidiObjectRef handle, IntPtr name, MidiObjectRef* endpoint); + unsafe extern static int /* OSStatus = SInt32 */ MIDISourceCreate (MidiObjectRef handle, IntPtr name, MidiEndpointRef* endpoint); + + [SupportedOSPlatform ("ios14.0")] + [SupportedOSPlatform ("maccatalyst")] + [SupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("tvos")] + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDISourceCreateWithProtocol (MidiClientRef client, IntPtr /* CFStringRef */ name, MidiProtocolId protocol, MidiEndpointRef* outSrc); GCHandle gch; @@ -597,45 +700,59 @@ public override string ToString () return Name; } - /// To be added. - /// To be added. - /// To be added. - /// To be added. - /// To be added. + /// Create a virtual source for this client. + /// The name for the virtual source. + /// A status code that describes the result of this operation. This will be in case of success. + /// A newly created if successful, otherwise null. [SupportedOSPlatform ("ios")] [SupportedOSPlatform ("maccatalyst")] [SupportedOSPlatform ("macos")] - [ObsoletedOSPlatform ("macos11.0")] - [ObsoletedOSPlatform ("ios14.0")] - [ObsoletedOSPlatform ("maccatalyst14.0")] + [ObsoletedOSPlatform ("macos11.0", "Call 'CreateVirtualSource (string, MidiProtocolId, out MidiError)' instead.")] + [ObsoletedOSPlatform ("ios14.0", "Call 'CreateVirtualSource (string, MidiProtocolId, out MidiError)' instead.")] + [ObsoletedOSPlatform ("maccatalyst14.0", "Call 'CreateVirtualSource (string, MidiProtocolId, out MidiError)' instead.")] public MidiEndpoint? CreateVirtualSource (string name, out MidiError statusCode) { - using (var nsstr = new NSString (name)) { - MidiObjectRef ret; - int code; - unsafe { - code = MIDISourceCreate (handle, nsstr.Handle, &ret); - } - if (code != 0) { - statusCode = (MidiError) code; - return null; - } - statusCode = MidiError.Ok; - return new MidiEndpoint (ret, true); + using var namePtr = new TransientCFString (name); + var handle = default (MidiEndpointRef); + unsafe { + statusCode = (MidiError) MIDISourceCreate (handle, namePtr, &handle); } + if (handle == MidiObject.InvalidRef) + return null; + return new MidiEndpoint (handle, true); } - /// To be added. - /// To be added. - /// To be added. - /// To be added. - /// To be added. + /// Create a virtual source for this client. + /// The name for the virtual source. + /// The MIDI protocol for the data this source will produce. + /// A status code that describes the result of this operation. This will be in case of success. + /// A newly created if successful, otherwise null. + [SupportedOSPlatform ("ios14.0")] + [SupportedOSPlatform ("maccatalyst")] + [SupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("tvos")] + public MidiEndpoint? CreateVirtualSource (string name, MidiProtocolId protocol, out MidiError status) + { + using var namePtr = new TransientCFString (name); + var handle = default (MidiEndpointRef); + unsafe { + status = (MidiError) MIDISourceCreateWithProtocol (GetCheckedHandle (), namePtr, protocol, &handle); + } + if (handle == MidiObject.InvalidRef) + return null; + return new MidiEndpoint (handle, true); + } + + /// Create a virtual destination for this client. + /// The name for the virtual destination. + /// A status code that describes the result of this operation. This will be in case of success. + /// A newly created if successful, otherwise null. [SupportedOSPlatform ("ios")] [SupportedOSPlatform ("maccatalyst")] [SupportedOSPlatform ("macos")] - [ObsoletedOSPlatform ("macos11.0")] - [ObsoletedOSPlatform ("ios14.0")] - [ObsoletedOSPlatform ("maccatalyst14.0")] + [ObsoletedOSPlatform ("macos11.0", "Call the other 'CreateVirtualDestination' overload instead.")] + [ObsoletedOSPlatform ("ios14.0", "Call the other 'CreateVirtualDestination' overload instead.")] + [ObsoletedOSPlatform ("maccatalyst14.0", "Call the other 'CreateVirtualDestination' overload instead.")] public MidiEndpoint? CreateVirtualDestination (string name, out MidiError status) { var m = new MidiEndpoint (this, name, out status); @@ -646,6 +763,32 @@ public override string ToString () return null; } + /// Create a virtual destination for this client. + /// The name for the virtual destination. + /// The MIDI protocol for the data this destination will receive. + /// The callback that will be called when the destination receives MIDI data. + /// A status code that describes the result of this operation. This will be in case of success. + /// A newly created if successful, otherwise null. + /// FIXME: ADD BETTER DOCS HERE + public unsafe MidiEndpoint? CreateVirtualDestination (string name, MidiProtocolId protocol, delegate* unmanaged readBlock, out MidiError status) + { + using var namePtr = new TransientCFString (name); + var handle = default (MidiEndpointRef); + unsafe { + status = (MidiError) MIDIDestinationCreateWithProtocol (GetCheckedHandle (), namePtr, protocol, &handle, readBlock); + } + if (handle == MidiObject.InvalidRef) + return null; + return new MidiEndpoint (handle, name, true); + } + + [SupportedOSPlatform ("ios14.0")] + [SupportedOSPlatform ("maccatalyst")] + [SupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("tvos")] + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDIDestinationCreateWithProtocol (MidiClientRef client, IntPtr /* CFStringRef */ name, MidiProtocolId protocol, MidiEndpointRef* outSrc, delegate* unmanaged readBlock); + /// name for the input port. /// Creates a new MIDI input port. /// @@ -666,6 +809,41 @@ public MidiPort CreateOutputPort (string name) return new MidiPort (this, name, false); } + [SupportedOSPlatform ("ios14.0")] + [SupportedOSPlatform ("maccatalyst")] + [SupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("tvos")] + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDIInputPortCreateWithProtocol ( + MidiClientRef client, + IntPtr /* CFStringRef */ name, + MidiProtocolId protocol, + MidiPortRef* outPort, + delegate* unmanaged receiveBlock); + + /// Create a input port for this client. + /// The name for the port. + /// The MIDI protocol for the data this port will receive. + /// The callback that will be called when the port receives MIDI data. + /// A status code that describes the result of this operation. This will be in case of success. + /// A newly created if successful, otherwise null. + /// FIXME: ADD BETTER DOCS HERE + [SupportedOSPlatform ("ios14.0")] + [SupportedOSPlatform ("maccatalyst")] + [SupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("tvos")] + public unsafe MidiPort? CreateInputPort (string name, MidiProtocolId protocol, delegate* unmanaged readBlock, out MidiError status) + { + using var namePtr = new TransientCFString (name); + var handle = default (MidiEndpointRef); + unsafe { + status = (MidiError) MIDIInputPortCreateWithProtocol (GetCheckedHandle (), namePtr, protocol, &handle, readBlock); + } + if (handle == MidiObject.InvalidRef) + return null; + return new MidiPort (handle, true, this, name); + } + public event EventHandler? SetupChanged; public event EventHandler? ObjectAdded; public event EventHandler? ObjectRemoved; @@ -1048,6 +1226,13 @@ public class MidiPort : MidiObject { GCHandle gch; bool input; + internal MidiPort (MidiPortRef handle, bool owns, MidiClient client, string portName) + : base (handle, owns) + { + Client = client; + PortName = portName; + } + internal MidiPort (MidiClient client, string portName, bool input) { using (var nsstr = new NSString (portName)) { @@ -1783,6 +1968,19 @@ public bool UmpCanTransmitGroupless { SetInt (MidiPropertyExtensions.kMIDIPropertyUMPCanTransmitGroupless, value ? 1 : 0); } } + + [DllImport (Constants.CoreMidiLibrary)] + extern static OSStatus MIDIEntityAddOrRemoveEndpoints (MidiEntityRef entity, nuint numSourceEndpoints, nuint numDestinationEndpoints); + + /// Sets the number of endpoints for this entity. + /// The number of source endpoints this entity will have. + /// The number of destination endpoints this entity will have. + /// if successful, an error code otherwise. + public MidiError AddOrRemoveEndpoints (nuint numberOfSourceEndpoints, nuint numberOfDestinationEndpoints) + { + return (MidiError) MIDIEntityAddOrRemoveEndpoints (GetCheckedHandle (), numberOfSourceEndpoints, numberOfDestinationEndpoints); + } + #endif // !COREBUILD } // MidiEntity @@ -1809,20 +2007,44 @@ public class MidiDevice : MidiObject { [DllImport (Constants.CoreMidiLibrary)] extern static MidiEntityRef MIDIDeviceGetEntity (MidiDeviceRef handle, nint item); + [SupportedOSPlatform ("ios14.0")] + [SupportedOSPlatform ("maccatalyst14.0")] + [SupportedOSPlatform ("macos")] + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDIDeviceNewEntity (MidiDeviceRef device, /* CFString */ IntPtr name, /* MIDIProtocolId */ MidiProtocolId protocol, byte embedded, /* ItemCount */ nuint numSourceEndpoints, /* ItemCount */ nuint numDestinationEndpoints, MidiEntityRef* newEntity); + + /// Create and add a new entity to an external device. + /// The name for the new entity. + /// The protocol in use for the new entity. + /// Whether the new entity is inside the device (true), or if it consists of external connectors (false). + /// The number of source endpoints in the new entity. + /// The number of destination endpoints in the new entity. + /// A status code that describes the result of creating the new entity. This will be in case of success. + /// A newly created entity in case of success, null otherwise. In case of failure, will contain an error code. + public MidiEntity? CreateEntity (string name, MidiProtocolId protocol, bool embedded, nuint numberOfSourceEndpoints, nuint numberOfDestinationEndpoints, out MidiError status) + { + using var namePtr = new TransientCFString (name); + var handle = default (MidiEntityRef); + unsafe { + status = (MidiError) MIDIDeviceNewEntity (GetCheckedHandle (), namePtr, protocol, embedded.AsByte (), numberOfSourceEndpoints, numberOfDestinationEndpoints, &handle); + } + if (handle == MidiObject.InvalidRef) + return null; + return new MidiEntity (handle); + } + [SupportedOSPlatform ("ios")] [SupportedOSPlatform ("maccatalyst")] [SupportedOSPlatform ("macos")] - [ObsoletedOSPlatform ("macos11.0")] - [ObsoletedOSPlatform ("ios14.0")] - [ObsoletedOSPlatform ("maccatalyst14.0")] + [ObsoletedOSPlatform ("macos11.0", "Call 'CreateEntity' instead.")] + [ObsoletedOSPlatform ("ios14.0", "Call 'CreateEntity' instead.")] + [ObsoletedOSPlatform ("maccatalyst14.0", "Call 'CreateEntity' instead.")] [DllImport (Constants.CoreMidiLibrary)] extern static int MIDIDeviceAddEntity (MidiDeviceRef device, /* CFString */ IntPtr name, byte embedded, nuint numSourceEndpoints, nuint numDestinationEndpoints, MidiEntityRef newEntity); public MidiEntity? GetEntity (nint entityIndex) { - if (handle == MidiObject.InvalidRef) - throw new ObjectDisposedException ("handle"); - var h = MIDIDeviceGetEntity (handle, entityIndex); + var h = MIDIDeviceGetEntity (GetCheckedHandle (), entityIndex); if (h == MidiObject.InvalidRef) return null; return new MidiEntity (h); @@ -1831,18 +2053,79 @@ public class MidiDevice : MidiObject { [SupportedOSPlatform ("ios")] [SupportedOSPlatform ("maccatalyst")] [SupportedOSPlatform ("macos")] - [ObsoletedOSPlatform ("ios14.0")] - [ObsoletedOSPlatform ("maccatalyst14.0")] - [ObsoletedOSPlatform ("macos11.0")] + [ObsoletedOSPlatform ("macos11.0", "Call 'CreateEntity' instead.")] + [ObsoletedOSPlatform ("ios14.0", "Call 'CreateEntity' instead.")] + [ObsoletedOSPlatform ("maccatalyst14.0", "Call 'CreateEntity' instead.")] public int Add (string name, bool embedded, nuint numSourceEndpoints, nuint numDestinationEndpoints, MidiEntity newEntity) { - if (handle == MidiObject.InvalidRef) - throw new ObjectDisposedException ("handle"); using (NSString nsName = new NSString (name)) { - return MIDIDeviceAddEntity (handle, nsName.Handle, embedded ? (byte) 1 : (byte) 0, numSourceEndpoints, numDestinationEndpoints, newEntity.Handle); + return MIDIDeviceAddEntity (GetCheckedHandle (), nsName.Handle, embedded ? (byte) 1 : (byte) 0, numSourceEndpoints, numDestinationEndpoints, newEntity.Handle); } } + [SupportedOSPlatform ("ios")] + [SupportedOSPlatform ("maccatalyst")] + [SupportedOSPlatform ("macos")] + [DllImport (Constants.CoreMidiLibrary)] + extern static OSStatus MIDIDeviceRemoveEntity (MidiDeviceRef device, MidiEntityRef entity); + + /// Remove the specified entity from this device. + /// The entity to remove. + /// if successful, an error code otherwise. + public MidiError Remove (MidiEntity entity) + { + return (MidiError) MIDIDeviceRemoveEntity (GetCheckedHandle (), entity.GetCheckedHandle ()); + } + +#if !STABLE_MIDIDRIVER + [Experimental ("APL0004")] +#endif + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDIDeviceCreate (MidiDriverInterface** /* MidiDriverRef _nullable */ owner, IntPtr /* CFStringRef */ name, IntPtr /* CFStringRef */ manufacturer, IntPtr /* CFStringRef */ model, MidiDeviceRef* outDevice); + + /// Create a new device corresponding to a specific piece of hardware. + /// The driver that owns the new device. Pass null if the owner isn't a driver. + /// The name for the new device. + /// The manufacturer for the new device. + /// The model for the new device. + /// A status code that describes the result of creating the new device. This will be in case of success. + /// A newly created instance, null otherwise. +#if !STABLE_MIDIDRIVER + [Experimental ("APL0004")] +#endif + public static MidiDevice? Create (MidiDriver? owner, string name, string manufacturer, string model, out MidiError status) + { + var handle = default (MidiDeviceRef); + using var namePtr = new TransientCFString (name); + using var manufacturerPtr = new TransientCFString (manufacturer); + using var modelPtr = new TransientCFString (model); + + unsafe { + MidiDriverInterface* driverInterfacePtr = owner is null ? null : owner.DriverInterface; + MidiDriverInterface** driverPtr = null; + if (owner is not null) + driverPtr = &driverInterfacePtr; + status = (MidiError) MIDIDeviceCreate (driverPtr, namePtr, manufacturerPtr, modelPtr, &handle); + } + + GC.KeepAlive (owner); + + if (handle == MidiObject.InvalidRef) + return null; + return new MidiDevice (handle); + } + + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDIDeviceDispose (MidiDeviceRef device); + + /// Dispose of devices that haven't yet been added to the system with . + /// if successful, an error code otherwise. + /// Only drivers can call this method, and only before calling . Once has been called, use instead to destroy the device. + public MidiError DisposeDevice () + { + return (MidiError) MIDIDeviceDispose (GetCheckedHandle ()); + } + /// Returns the number of MIDI entities in this device. /// /// @@ -1906,19 +2189,33 @@ public int UniqueID { } } +#if !XAMCORE_5_0 || __MACOS__ /// To be added. /// To be added. /// To be added. + [UnsupportedOSPlatform ("tvos")] + [UnsupportedOSPlatform ("ios")] + [UnsupportedOSPlatform ("maccatalyst")] + [SupportedOSPlatform ("macos")] +#if !__MACOS__ + [EditorBrowsable (EditorBrowsableState.Never)] + [Obsolete ("This API does not do anything on this platform.")] +#endif public bool UsesSerial { get { - var kMIDIDriverPropertyUsesSerial = Dlfcn.GetIntPtr (Libraries.CoreMidi.Handle, "kMIDIDriverPropertyUsesSerial"); - return GetInt (kMIDIDriverPropertyUsesSerial) != 0; +#if __MACOS__ + return GetInt (MidiDriverPropertyExtensions.kMIDIDriverPropertyUsesSerial) != 0; +#else + return false; +#endif } set { - var kMIDIDriverPropertyUsesSerial = Dlfcn.GetIntPtr (Libraries.CoreMidi.Handle, "kMIDIDriverPropertyUsesSerial"); - SetInt (kMIDIDriverPropertyUsesSerial, value ? 1 : 0); +#if __MACOS__ + SetInt (MidiDriverPropertyExtensions.kMIDIDriverPropertyUsesSerial, value ? 1 : 0); +#endif } } +#endif // !XAMCORE_5_0 || __MACOS__ #if !XAMCORE_5_0 || __MACOS__ /// To be added. @@ -2512,16 +2809,12 @@ internal MidiDeviceList (MidiDeviceListRef handle, bool owns) : base (handle, ow /// To be added. public nuint GetNumberOfDevices () { - if (handle == MidiObject.InvalidRef) - throw new ObjectDisposedException ("handle"); - return MIDIDeviceListGetNumberOfDevices (handle); + return MIDIDeviceListGetNumberOfDevices (GetCheckedHandle ()); } public MidiDevice? Get (nuint index) { - if (handle == MidiObject.InvalidRef) - throw new ObjectDisposedException ("handle"); - var h = MIDIDeviceListGetDevice (handle, index); + var h = MIDIDeviceListGetDevice (GetCheckedHandle (), index); if (h == MidiObject.InvalidRef) return null; return new MidiDevice (h); @@ -2533,9 +2826,7 @@ public nuint GetNumberOfDevices () /// To be added. public int Add (MidiDevice device) { - if (handle == MidiObject.InvalidRef) - throw new ObjectDisposedException ("handle"); - return MIDIDeviceListAddDevice (handle, device.Handle); + return MIDIDeviceListAddDevice (GetCheckedHandle (), device.Handle); } internal override void DisposeHandle () @@ -2637,17 +2928,21 @@ internal MidiEndpoint (MidiEndpointRef handle, string endpointName, bool owns) : return new MidiEndpoint (h, "Destination" + destinationIndex, false); } - internal MidiEndpoint (MidiClient client, string name, out MidiError code) + internal MidiEndpoint (MidiClient client, string name, out MidiError status) { - using (var nsstr = new NSString (name)) { - GCHandle gch = GCHandle.Alloc (this); - unsafe { - MidiEndpointRef tempHandle; - code = MIDIDestinationCreate (client.handle, nsstr.Handle, &Read, GCHandle.ToIntPtr (gch), &tempHandle); - handle = tempHandle; - } - EndpointName = name; + EndpointName = name; + + using var namePtr = new TransientCFString (name); + var handle = default (MidiEndpointRef); + gch = GCHandle.Alloc (this); + unsafe { + status = (MidiError) MIDIDestinationCreate (client.GetCheckedHandle (), namePtr, &Read, GCHandle.ToIntPtr (gch), &handle); + } + if (handle == MidiObject.InvalidRef) { + gch.Free (); + return; } + this.handle = handle; } /// @@ -2984,11 +3279,259 @@ public int AssociatedEndpoint { SetInt (MidiPropertyExtensions.kMIDIPropertyAssociatedEndpoint, value); } } + + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDIEndpointGetRefCons (MidiEndpointRef endpoint, IntPtr* ref1, IntPtr* ref2); + + [DllImport (Constants.CoreMidiLibrary)] + extern static OSStatus MIDIEndpointSetRefCons (MidiEndpointRef endpoint, IntPtr ref1, IntPtr ref2); + + /// Get the refcons for this endpoint. + /// Returns the first refcon if successful. + /// Returns the second refcon if successful. + /// if successful, an error code otherwise. + public MidiError GetRefCons (out IntPtr ref1, out IntPtr ref2) + { + ref1 = IntPtr.Zero; + ref2 = IntPtr.Zero; + unsafe { + return (MidiError) MIDIEndpointGetRefCons (GetCheckedHandle (), (IntPtr*) Unsafe.AsRef (ref ref1), (IntPtr*) Unsafe.AsRef (ref ref2)); + } + } + + /// Set the refcons for this endpoint. + /// The first refcon. + /// The second refcon. + /// if successful, an error code otherwise. + public MidiError SetRefCons (IntPtr ref1, IntPtr ref2) + { + unsafe { + return (MidiError) MIDIEndpointSetRefCons (GetCheckedHandle (), ref1, ref2); + } + } + + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDISendSysex (MidiSysexSendRequest* request); + + /// Asynchronously sends a single system-exclusive event. + /// The data to send. + /// An optional cancellation token that can be used to cancel the request. + /// A value for the request. This will be if the request was successful, an error code otherwise. + public unsafe Task SendSysexAsync (byte [] data, CancellationToken? cancellationToken = null) + { + if (data is null) + ThrowHelper.ThrowArgumentNullException (nameof (data)); + + var tcs = new TaskCompletionSource (); + var request = new SysexRequest (this, data, tcs); + var rv = (MidiError) MIDISendSysex (request.GetSysexRequestStruct (cancellationToken)); + if (rv != MidiError.Ok) { + request.Dispose (); + tcs.TrySetResult (rv); + } + + return tcs.Task; + } + + [DllImport (Constants.CoreMidiLibrary)] + [SupportedOSPlatform ("ios17.0")] + [SupportedOSPlatform ("maccatalyst17.0")] + [SupportedOSPlatform ("macos14.0")] + [UnsupportedOSPlatform ("tvos")] + unsafe extern static OSStatus MIDISendUMPSysex (MidiSysexSendRequestUmp* request); + + /// Asynchronously sends a single UMP system-exclusive event. + /// The data to send. + /// An optional cancellation token that can be used to cancel the request. + /// A value for the request. This will be if the request was successful, an error code otherwise. + [SupportedOSPlatform ("ios17.0")] + [SupportedOSPlatform ("maccatalyst17.0")] + [SupportedOSPlatform ("macos14.0")] + [UnsupportedOSPlatform ("tvos")] + public unsafe Task SendSysexUmpAsync (uint [] data, CancellationToken? cancellationToken = null) + { + if (data is null) + ThrowHelper.ThrowArgumentNullException (nameof (data)); + + var tcs = new TaskCompletionSource (); + var request = new SysexRequest (this, data, tcs); + var rv = (MidiError) MIDISendUMPSysex (request.GetSysexUmpRequestStruct (cancellationToken)); + if (rv != MidiError.Ok) { + request.Dispose (); + tcs.TrySetResult (rv); + } + + return tcs.Task; + } + + [SupportedOSPlatform ("ios17.0")] + [SupportedOSPlatform ("maccatalyst17.0")] + [SupportedOSPlatform ("macos14.0")] + [UnsupportedOSPlatform ("tvos")] + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDISendUMPSysex8 (MidiSysexSendRequestUmp* request); + + /// Asynchronously sends a single 8-bit system-exclusive event. + /// The data to send. + /// An optional cancellation token that can be used to cancel the request. + /// A value for the request. This will be if the request was successful, an error code otherwise. + [SupportedOSPlatform ("ios17.0")] + [SupportedOSPlatform ("maccatalyst17.0")] + [SupportedOSPlatform ("macos14.0")] + [UnsupportedOSPlatform ("tvos")] + public unsafe Task SendSysexUmp8Async (uint [] data, CancellationToken? cancellationToken = null) + { + if (data is null) + ThrowHelper.ThrowArgumentNullException (nameof (data)); + + var tcs = new TaskCompletionSource (); + var request = new SysexRequest (this, data, tcs); + var rv = (MidiError) MIDISendUMPSysex8 (request.GetSysexUmpRequestStruct (cancellationToken)); + if (rv != MidiError.Ok) { + request.Dispose (); + tcs.TrySetResult (rv); + } + + return tcs.Task; + } + + class SysexRequest : IDisposable { + IntPtr structPointer; + MidiEndpoint endpoint; + byte []? byteData; + uint []? uintData; + GCHandle dataHandle; + GCHandle thisHandle; + TaskCompletionSource onCompletion; + CancellationTokenRegistration? cancellationTokenRegistration; + + public SysexRequest (MidiEndpoint endpoint, byte [] data, TaskCompletionSource onCompletion) + { + this.endpoint = endpoint; + this.byteData = data; + this.onCompletion = onCompletion; + + unsafe { + structPointer = Marshal.AllocHGlobal (sizeof (MidiSysexSendRequest)); + } + dataHandle = GCHandle.Alloc (byteData, GCHandleType.Pinned); + thisHandle = GCHandle.Alloc (this); + } + + public SysexRequest (MidiEndpoint endpoint, uint [] data, TaskCompletionSource onCompletion) + { + this.endpoint = endpoint; + this.uintData = data; + this.onCompletion = onCompletion; + + unsafe { + structPointer = Marshal.AllocHGlobal (sizeof (MidiSysexSendRequestUmp)); + } + dataHandle = GCHandle.Alloc (uintData, GCHandleType.Pinned); + thisHandle = GCHandle.Alloc (this); + } + + public unsafe MidiSysexSendRequest* GetSysexRequestStruct (CancellationToken? cancellationToken) + { + if (byteData is null) + throw new InvalidOperationException ($"No byte[] data specified."); + + var rv = (MidiSysexSendRequest*) structPointer; + + rv->Destination = endpoint.GetCheckedHandle (); + rv->Data = dataHandle.AddrOfPinnedObject (); + rv->BytesToSend = (uint) byteData.Length; + rv->CompletionProcedure = &SysexCompletion; + rv->Context = GCHandle.ToIntPtr (thisHandle); + + cancellationTokenRegistration = cancellationToken?.Register (SysexCancellationRequest); + + return rv; + } + + public unsafe MidiSysexSendRequestUmp* GetSysexUmpRequestStruct (CancellationToken? cancellationToken) + { + if (uintData is null) + throw new InvalidOperationException ($"No uint[] data specified."); + + var rv = (MidiSysexSendRequestUmp*) structPointer; + + rv->Destination = endpoint.GetCheckedHandle (); + rv->Words = dataHandle.AddrOfPinnedObject (); + rv->WordsToSend = (uint) uintData.Length; + rv->CompletionProcedure = &UmpSysexCompletion; + rv->Context = GCHandle.ToIntPtr (thisHandle); + + cancellationTokenRegistration = cancellationToken?.Register (UmpSysexCancellationRequest); + + return rv; + } + + void OnCompleted () + { + onCompletion.TrySetResult (MidiError.Ok); + Dispose (); + } + + [UnmanagedCallersOnly] + unsafe static void SysexCompletion (MidiSysexSendRequest* request) + { + var obj = (SysexRequest?) GCHandle.FromIntPtr (request->Context).Target; + obj?.OnCompleted (); + } + + [UnmanagedCallersOnly] + unsafe static void UmpSysexCompletion (MidiSysexSendRequestUmp* request) + { + var obj = (SysexRequest?) GCHandle.FromIntPtr (request->Context).Target; + obj?.OnCompleted (); + } + + unsafe void SysexCancellationRequest () + { + var rv = (MidiSysexSendRequest*) structPointer; + if (rv is null) + return; + rv->Complete = true; + } + + unsafe void UmpSysexCancellationRequest () + { + var rv = (MidiSysexSendRequestUmp*) structPointer; + if (rv is null) + return; + rv->Complete = true; + } + + public void Dispose () + { + cancellationTokenRegistration?.Dispose (); + cancellationTokenRegistration = null; + if (structPointer != IntPtr.Zero) { + Marshal.FreeHGlobal (structPointer); + structPointer = IntPtr.Zero; + } + if (dataHandle.IsAllocated) + dataHandle.Free (); + if (thisHandle.IsAllocated) + thisHandle.Free (); + GC.SuppressFinalize (this); + } + + ~SysexRequest () + { + Dispose (); + } + } + // MidiEndpoint #endif // !COREBUILD } +#endif // TVOS + // SInt32 - MIDIServices.h + [NativeName ("MIDINotificationMessageID")] enum MidiNotificationMessageId : int { SetupChanged = 1, ObjectAdded, @@ -3001,10 +3544,12 @@ enum MidiNotificationMessageId : int { [SupportedOSPlatform ("ios")] [SupportedOSPlatform ("maccatalyst")] [UnsupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("tvos")] InternalStart = 0x1000, #endif } +#if !TVOS // // The notification EventArgs // @@ -3178,5 +3723,5 @@ protected virtual void Dispose (bool disposing) } #endif // !COREBUILD } +#endif // TVOS } -#endif diff --git a/src/CoreMidi/MidiStructs.cs b/src/CoreMidi/MidiStructs.cs index 390d427287ea..befd002ef1dd 100644 --- a/src/CoreMidi/MidiStructs.cs +++ b/src/CoreMidi/MidiStructs.cs @@ -141,5 +141,139 @@ public unsafe MidiCIProfileIdManufacturerSpecific ManufacturerSpecific { } } } + + /// A struct that represents a request to transmit a single system-exclusive event. + [NativeName ("MIDISysexSendRequest")] + struct MidiSysexSendRequest { + MidiEndpointRef destination; + IntPtr /* const Byte * */ data; + uint bytesToSend; + byte /* Boolean */ complete; +#pragma warning disable CS0169 // The field '...' is never used + byte reserved1; + byte reserved2; + byte reserved3; +#pragma warning restore CS0169 + unsafe delegate* unmanaged /* MIDICompletionProc */ completionProc; + IntPtr /* void * __nullable */ completionRefCon; + + /// The endpoint where the request is sent. + public MidiEndpointRef Destination { + get => destination; + set => destination = value; + } + + /// A pointer to the data to send. + /// The MIDI system will update this value as the request progresses. + public IntPtr Data { + get => data; + set => data = value; + } + + /// The number of bytes to send. + /// The MIDI system will update this value as the request progresses. + public uint BytesToSend { + get => bytesToSend; + set => bytesToSend = value; + } + + /// The client can set true to immediately stop the request. The MIDI system will set it to true when the request is complete. + public bool Complete { + get => complete != 0; + set => complete = value.AsByte (); + } + + /// The callback that is called when all the data has been sent and the request is complete. + /// Also called if the client sets to true before the request is complete. + public unsafe delegate* unmanaged CompletionProcedure { + get => completionProc; + set => completionProc = value; + } + + /// A context value that's passed to the callback. + public IntPtr Context { + get => completionRefCon; + set => completionRefCon = value; + } + } + + + /*! + @struct MIDISysexSendRequestUMP + @abstract A request to transmit a UMP system-exclusive event. + + @discussion + This represents a request to send a single UMP system-exclusive MIDI event to + a MIDI destination asynchronously. + + @field destination + The endpoint to which the event is to be sent. + @field words + Initially, a pointer to the UMP SysEx event to be sent. + MIDISendUMPSysex will advance this pointer as data is + sent. + @field wordsToSend + Initially, the number of words to be sent. MIDISendUMPSysex + will decrement this counter as data is sent. + @field complete + The client may set this to true at any time to abort + transmission. The implementation sets this to true when + all data been transmitted. + @field completionProc + Called when all bytes have been sent, or after the client + has set complete to true. + @field completionRefCon + Passed as a refCon to completionProc. + */ + + /// A struct that represents a request to transmit a single UMP system-exclusive event. + [NativeName ("MIDISysexSendRequestUMP")] + struct MidiSysexSendRequestUmp { + MidiEndpointRef destination; + IntPtr /* UInt32* */ words; + uint /* UInt32 */ wordsToSend; + byte /* Boolean */ complete; + unsafe delegate* unmanaged /* MIDICompletionProcUMP */ completionProc; + IntPtr /* void* __nullable */ completionRefCon; + + /// The endpoint where the request is sent. + public MidiEndpointRef Destination { + get => destination; + set => destination = value; + } + + /// A pointer to the 32-bit word(s) to send. + /// The MIDI system will update this value as the request progresses. + public IntPtr Words { + get => words; + set => words = value; + } + + /// The number of 32-bit words to send. + /// The MIDI system will update this value as the request progresses. + public uint WordsToSend { + get => wordsToSend; + set => wordsToSend = value; + } + + /// The client can set true to immediately stop the request. The MIDI system will set it to true when the request is complete. + public bool Complete { + get => complete != 0; + set => complete = value.AsByte (); + } + + /// The callback that is called when all the data has been sent and the request is complete. + /// Also called if the client sets to true before the request is complete. + public unsafe delegate* unmanaged CompletionProcedure { + get => completionProc; + set => completionProc = value; + } + + /// A context value that's passed to the callback. + public IntPtr Context { + get => completionRefCon; + set => completionRefCon = value; + } + }; } #endif diff --git a/src/CoreMidi/MidiThruConnectionParams.cs b/src/CoreMidi/MidiThruConnectionParams.cs index 5c3312778940..3dbd81c7326d 100644 --- a/src/CoreMidi/MidiThruConnectionParams.cs +++ b/src/CoreMidi/MidiThruConnectionParams.cs @@ -1,5 +1,4 @@ -#if !TVOS -// + // MidiThruConnectionParams.cs: A C# wrapper around MidiThruConnectionParamsStruct // // Authors: Alex Soto (alex.soto@xamarin.com) @@ -24,6 +23,7 @@ namespace CoreMidi { /// MIDI transform types. /// To be added. + [NativeName ("MIDITransformType")] public enum MidiTransformType : ushort { /// To be added. None = 0, @@ -45,6 +45,7 @@ public enum MidiTransformType : ushort { /// MIDI Control Transformation Type. /// To be added. + [NativeName ("MIDITransformControlType")] public enum MidiTransformControlType : byte { /// To be added. SevenBit = 0, @@ -60,6 +61,7 @@ public enum MidiTransformControlType : byte { FourteenBitNRpn = 5, } +#if !TVOS /// Object that defines how a MIDI event is transformed. /// To be added. [SupportedOSPlatform ("ios")] @@ -653,5 +655,5 @@ internal NSData WriteStruct () } } #endif // !COREBUILD +#endif // TVOS } -#endif diff --git a/src/coremidi.cs b/src/coremidi.cs index 36d9105e2469..d9acb41663cc 100644 --- a/src/coremidi.cs +++ b/src/coremidi.cs @@ -1096,6 +1096,13 @@ enum MidiProperty { AssociatedEndpoint, } + [Internal] + [NoiOS, NoMacCatalyst, NoTV] + enum MidiDriverProperty { + [Field ("kMIDIDriverPropertyUsesSerial")] + UsesSerial, + } + [NoTV, Mac (15, 0), iOS (18, 0), MacCatalyst (18, 0)] [BaseType (typeof (NSObject), Name = "MIDICIDevice")] [DisableDefaultCtor] diff --git a/src/frameworks.sources b/src/frameworks.sources index f2595d372e88..df5da47860e7 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -448,6 +448,7 @@ COREFOUNDATION_CORE_SOURCES = \ CoreFoundation/CFMutableString.cs \ CoreFoundation/CFRunLoop.cs \ CoreFoundation/CFString.cs \ + CoreFoundation/CFUuidBytes.cs \ CoreFoundation/Dispatch.cs \ CoreFoundation/DispatchData.cs \ CoreFoundation/NativeObject.cs \ @@ -600,6 +601,7 @@ COREMEDIA_SOURCES = \ COREMIDI_CORE_SOURCES = \ CoreMidi/MidiCIDeviceIdentification.cs \ + CoreMidi/MidiDriverInterface.cs \ CoreMidi/MidiServices.cs \ CoreMidi/MidiStructs.cs \ CoreMidi/MidiThruConnection.cs \ @@ -607,6 +609,8 @@ COREMIDI_CORE_SOURCES = \ COREMIDI_SOURCES = \ CoreMidi/MidiBluetoothDriver.cs \ + CoreMidi/MidiEventList.cs \ + CoreMidi/MidiEventPacket.cs \ # CoreML diff --git a/tests/monotouch-test/CoreMidi/MidiDeviceTest.cs b/tests/monotouch-test/CoreMidi/MidiDeviceTest.cs new file mode 100644 index 000000000000..56aa4d80a9cc --- /dev/null +++ b/tests/monotouch-test/CoreMidi/MidiDeviceTest.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +#if !__TVOS__ +using System; +using System.Diagnostics; + +using CoreMidi; +using Foundation; + +using NUnit.Framework; + +namespace MonoTouchFixtures.CoreMidi { + [TestFixture] + [Preserve (AllMembers = true)] + public class MidiDeviceTest { + [Test] + public void ExternalDevice () + { + using var device = Midi.CreateExternalDevice ("MonoTouchTestMidiTestDevice", "MonoTouchTestMidiTestManufacturer", "MonoTouchTestMidiTestModel", out var status); + Assert.That (device, Is.Not.Null, "Device"); + Assert.That (status, Is.EqualTo (MidiError.Ok), "Status"); + if (device is not null) { + var rv = MidiSetup.AddExternalDevice (device); + Assert.That (rv, Is.EqualTo (MidiError.Ok), "Add Status"); + rv = MidiSetup.RemoveExternalDevice (device); + Assert.That (rv, Is.EqualTo (MidiError.Ok), "Remove Status"); + } + } + } +} +#endif diff --git a/tests/monotouch-test/CoreMidi/MidiEndpointTest.cs b/tests/monotouch-test/CoreMidi/MidiEndpointTest.cs index 179cce057b57..39e4eb1bd464 100644 --- a/tests/monotouch-test/CoreMidi/MidiEndpointTest.cs +++ b/tests/monotouch-test/CoreMidi/MidiEndpointTest.cs @@ -10,8 +10,12 @@ #if !__TVOS__ using System; +using System.Collections.Generic; + +using AudioToolbox; using Foundation; using CoreMidi; + using NUnit.Framework; namespace MonoTouchFixtures.CoreMidi { @@ -33,6 +37,53 @@ public void CorrectDisposeTest () } }); } + + [Test] + public void SendTest () + { + var anyChecks = false; + + for (var i = 0; i < Midi.DeviceCount; i++) { + using var device = Midi.GetDevice (i); + Assert.IsNotNull (device, "Device"); + for (var e = 0; e < device.EntityCount; e++) { + using var entity = device.GetEntity (e); + var endpoints = new List (); + for (var d = 0; d < entity.Destinations; d++) + endpoints.Add (entity.GetDestination (d)); + for (var d = 0; d < entity.Sources; d++) + endpoints.Add (entity.GetSource (d)); + + foreach (var ep in endpoints) { + Assert.NotNull (ep, "EndPoint"); + + // These APIs returns -50 (GeneralParamError) no matter what I do :/ + + Assert.AreEqual (AudioQueueStatus.GeneralParamError, (AudioQueueStatus) ep.GetRefCons (out var ref1, out var ref2), "GetRefCons A"); + Assert.AreEqual (ref1, IntPtr.Zero, "GetRefCons A 1"); + Assert.AreEqual (ref2, IntPtr.Zero, "GetRefCons A 2"); + + ref1 = unchecked((IntPtr) 0xfee1600d); + ref2 = 0x42f00f00; + Assert.AreEqual (AudioQueueStatus.GeneralParamError, (AudioQueueStatus) ep.SetRefCons (ref1, ref2), "SetRefCons B"); + Assert.AreEqual (AudioQueueStatus.GeneralParamError, (AudioQueueStatus) ep.GetRefCons (out ref1, out ref2), "GetRefCons C"); + Assert.AreEqual (ref1, IntPtr.Zero /* 0xfee1600d */, "GetRefCons C 1"); + Assert.AreEqual (ref2, IntPtr.Zero /* 0x42f00f00 */, "GetRefCons C 2"); + + Assert.AreEqual (AudioQueueStatus.GeneralParamError, (AudioQueueStatus) ep.SetRefCons (IntPtr.Zero, IntPtr.Zero), "SetRefCons D"); + + Assert.AreEqual (AudioQueueStatus.GeneralParamError, (AudioQueueStatus) ep.GetRefCons (out ref1, out ref2), "GetRefCons E"); + Assert.AreEqual (ref1, IntPtr.Zero, "GetRefCons E 1"); + Assert.AreEqual (ref2, IntPtr.Zero, "GetRefCons E 2"); + + anyChecks = true; + } + } + } + + if (!anyChecks) + Assert.Inconclusive ("No applicable MidiEntity found."); + } } } #endif diff --git a/tests/monotouch-test/CoreMidi/MidiEventListTest.cs b/tests/monotouch-test/CoreMidi/MidiEventListTest.cs new file mode 100644 index 000000000000..a75ad26ace8b --- /dev/null +++ b/tests/monotouch-test/CoreMidi/MidiEventListTest.cs @@ -0,0 +1,157 @@ +// +// Unit tests for MidiEventPacket +// +// Copyright 2025 Microsoft Corp. All rights reserved. +// + +#if HAS_COREMIDI + +using System; +using System.Collections.Generic; +using System.Linq; + +using CoreMidi; +using Foundation; + +using NUnit.Framework; + +namespace MonoTouchFixtures.CoreMidi { + [TestFixture] + [Preserve (AllMembers = true)] + public class MidiEventListTest { + [Test] + public void CtorTest () + { + Assert.Multiple (() => { + var obj = new MidiEventList (MidiProtocolId.Protocol_1_0); + Assert.That (obj.Protocol, Is.EqualTo (MidiProtocolId.Protocol_1_0), "Protocol"); + Assert.That (obj.PacketCount, Is.EqualTo (0), "PacketCount"); + var packets = obj.ToArray (); + Assert.That (packets.Length, Is.EqualTo (0), "ToArray ().Length"); + }); + } + + [Test] + public void CtorTest_Size () + { + Exception ex; + + Assert.Multiple (() => { + ex = Assert.Throws (() => new MidiEventList (MidiProtocolId.Protocol_1_0, int.MinValue), "AOORE int.MinValue"); + Assert.That (ex.Message, Does.Contain ("size must be at least 276."), "AOORE msg int.MinValue"); + ex = Assert.Throws (() => new MidiEventList (MidiProtocolId.Protocol_1_0, -1), "AOORE -1"); + Assert.That (ex.Message, Does.Contain ("size must be at least 276."), "AOORE msg -1"); + ex = Assert.Throws (() => new MidiEventList (MidiProtocolId.Protocol_1_0, 0), "AOORE 0"); + Assert.That (ex.Message, Does.Contain ("size must be at least 276."), "AOORE msg 0"); + ex = Assert.Throws (() => new MidiEventList (MidiProtocolId.Protocol_1_0, 275), "AOORE 275"); + Assert.That (ex.Message, Does.Contain ("size must be at least 276."), "AOORE msg 275"); + + var obj = new MidiEventList (MidiProtocolId.Protocol_1_0, 276); + Assert.That (obj.Protocol, Is.EqualTo (MidiProtocolId.Protocol_1_0), "Protocol"); + Assert.That (obj.PacketCount, Is.EqualTo (0), "PacketCount"); + var packets = obj.ToArray (); + Assert.That (packets.Length, Is.EqualTo (0), "ToArray ().Length"); + }); + } + + [Test] + public void AddTest () + { + Assert.Multiple (() => { + var obj = new MidiEventList (MidiProtocolId.Protocol_2_0); + Assert.That (obj.Protocol, Is.EqualTo (MidiProtocolId.Protocol_2_0), "Protocol"); + Assert.That (obj.PacketCount, Is.EqualTo (0), "PacketCount"); + + var rv = obj.Add (123, new uint [] { 1, 2, 3 }); + Assert.That (rv, Is.EqualTo (true), "Add B"); + Assert.That (obj.Protocol, Is.EqualTo (MidiProtocolId.Protocol_2_0), "Protocol B"); + Assert.That (obj.PacketCount, Is.EqualTo (1), "PacketCount B"); + + var packets = obj.ToArray (); + Assert.That (packets.Length, Is.EqualTo (1), "ToArray ().Length"); + Assert.That (packets [0].Timestamp, Is.EqualTo (123), "Item[0].Timestamp"); + Assert.That (packets [0].WordCount, Is.EqualTo (3), "Item[0].WordCount"); + Assert.That (packets [0].Words, Is.EqualTo (new uint [] { 1, 2, 3 }), "Item[0].Words"); + }); + } + + [Test] + public void AddTest_ManyWords () + { + Assert.Multiple (() => { + var obj = new MidiEventList (MidiProtocolId.Protocol_2_0); + Assert.That (obj.Protocol, Is.EqualTo (MidiProtocolId.Protocol_2_0), "Protocol"); + Assert.That (obj.PacketCount, Is.EqualTo (0), "PacketCount"); + + var manyWords = Enumerable.Range (1, 65).Select (v => (uint) v).ToArray (); + var rv = obj.Add (123, manyWords); + Assert.That (rv, Is.EqualTo (false), "Add B"); + }); + } + + [Test] + public void AddTest_NotEnoughSpace () + { + Assert.Multiple (() => { + var obj = new MidiEventList (MidiProtocolId.Protocol_2_0); + Assert.That (obj.Protocol, Is.EqualTo (MidiProtocolId.Protocol_2_0), "Protocol"); + Assert.That (obj.PacketCount, Is.EqualTo (0), "PacketCount"); + + var fitsTwice = Enumerable.Range (1, 24).Select (v => (uint) v).ToArray (); + var rv = obj.Add (123, fitsTwice); + Assert.That (rv, Is.EqualTo (true), "Add B"); + rv = obj.Add (456, fitsTwice); + Assert.That (rv, Is.EqualTo (true), "Add C"); + rv = obj.Add (789, fitsTwice); + Assert.That (rv, Is.EqualTo (false), "Add C"); + }); + } + + [Test] + public void EnumeratorTest () + { + Assert.Multiple (() => { + var obj = new MidiEventList (MidiProtocolId.Protocol_2_0); + var rv = obj.Add (789, new uint [] { 4, 5, 6 }); + Assert.That (rv, Is.EqualTo (true), "Add B"); + Assert.That (obj.Protocol, Is.EqualTo (MidiProtocolId.Protocol_2_0), "Protocol B"); + Assert.That (obj.PacketCount, Is.EqualTo (1), "PacketCount B"); + + var packets = obj.ToArray (); + Assert.That (packets.Length, Is.EqualTo (1), "ToArray ().Length"); + Assert.That (packets [0].Timestamp, Is.EqualTo (789), "Item[0].Timestamp"); + Assert.That (packets [0].WordCount, Is.EqualTo (3), "Item[0].WordCount"); + Assert.That (packets [0].Words, Is.EqualTo (new uint [] { 4, 5, 6 }), "Item[0].Words"); + }); + } + + [Test] + public void IteratorTest () + { + Assert.Multiple (() => { + var obj = new MidiEventList (MidiProtocolId.Protocol_2_0); + var rv = obj.Add (456, new uint [] { 1, 2, 3, 4, 5, 6 }); + Assert.That (rv, Is.EqualTo (true), "Add B"); + Assert.That (obj.Protocol, Is.EqualTo (MidiProtocolId.Protocol_2_0), "Protocol B"); + Assert.That (obj.PacketCount, Is.EqualTo (1), "PacketCount B"); + + var packets = obj.ToArray (); + Assert.That (packets.Length, Is.EqualTo (1), "ToArray ().Length"); + Assert.That (packets [0].Timestamp, Is.EqualTo (456), "Item[0].Timestamp"); + Assert.That (packets [0].WordCount, Is.EqualTo (6), "Item[0].WordCount"); + Assert.That (packets [0].Words, Is.EqualTo (new uint [] { 1, 2, 3, 4, 5, 6 }), "Item[0].Words"); + + var packetList = new List (); + obj.Iterate ((ref MidiEventPacket packet) => { + packetList.Add (packet); + }); + Assert.That (packetList.Count, Is.EqualTo (1), "packetList.Length"); + Assert.That (packetList [0].Timestamp, Is.EqualTo (456), "packetList[0].Timestamp"); + Assert.That (packetList [0].WordCount, Is.EqualTo (6), "packetList[0].WordCount"); + Assert.That (packetList [0].Words, Is.EqualTo (new uint [] { 1, 2, 3, 4, 5, 6 }), "packetList[0].Words"); + }); + } + } +} + +#endif diff --git a/tests/monotouch-test/CoreMidi/MidiEventPacketTest.cs b/tests/monotouch-test/CoreMidi/MidiEventPacketTest.cs new file mode 100644 index 000000000000..c4823cb0b95d --- /dev/null +++ b/tests/monotouch-test/CoreMidi/MidiEventPacketTest.cs @@ -0,0 +1,130 @@ +// +// Unit tests for MidiEventPacket +// +// Copyright 2025 Microsoft Corp. All rights reserved. +// + +#if HAS_COREMIDI + +using System; +using System.Linq; + +using CoreMidi; +using Foundation; + +using NUnit.Framework; + +namespace MonoTouchFixtures.CoreMidi { + [TestFixture] + [Preserve (AllMembers = true)] + public class MidiEventPacketTest { + [Test] + public void Default () + { + Assert.Multiple (() => { + Exception ex; + uint v; + + var value = new MidiEventPacket (); + Assert.That (value.Timestamp, Is.EqualTo (0), "Timestamp"); + Assert.That (value.WordCount, Is.EqualTo (0), "WordCount"); + Assert.That (value.Words.Length, Is.EqualTo (0), "WordCount"); + + ex = Assert.Throws (() => v = value [-1], $"Index #-1"); + Assert.That (ex.Message, Does.Contain ("index must be positive."), $"Index #-1 message"); + + for (var i = 0; i < 64; i++) { + ex = Assert.Throws (() => v = value [i], $"Index #{i}"); + Assert.That (ex.Message, Does.Contain ("index must be less than WordCount."), $"Index #{i} message"); + } + + ex = Assert.Throws (() => v = value [64], $"Index #64"); + Assert.That (ex.Message, Does.Contain ("index must be less than 64."), $"Index #64 message"); + }); + } + + [Test] + public void Roundtrips () + { + Assert.Multiple (() => { + Exception ex; + + var value = new MidiEventPacket (); + + // Timestamp + value.Timestamp = 2; + Assert.That (value.Timestamp, Is.EqualTo (2), "Timestamp"); + + value.Timestamp = ulong.MinValue; + Assert.That (value.Timestamp, Is.EqualTo (ulong.MinValue), "Timestamp #2"); + + value.Timestamp = ulong.MaxValue; + Assert.That (value.Timestamp, Is.EqualTo (ulong.MaxValue), "Timestamp #3"); + + // WordCount + + value.WordCount = 3; + Assert.That (value.WordCount, Is.EqualTo (3), "WordCount"); + Assert.That (value.Words.Length, Is.EqualTo (3), "WordCount"); + + value.WordCount = uint.MinValue; + Assert.That (value.WordCount, Is.EqualTo (uint.MinValue), "WordCount #2"); + Assert.That (value.Words.Length, Is.EqualTo (uint.MinValue), "WordCount #2"); + + ex = Assert.Throws (() => value.WordCount = uint.MaxValue, "WordCount #3"); + Assert.That (ex.Message, Does.Contain ("WordCount can't be higher than 64."), $"WordCount #3 message"); + + ex = Assert.Throws (() => value.WordCount = 65, "WordCount #4"); + Assert.That (ex.Message, Does.Contain ("WordCount can't be higher than 64."), $"WordCount #4 message"); + + value.WordCount = 64; + Assert.That (value.WordCount, Is.EqualTo (64), "WordCount #5"); + Assert.That (value.Words.Length, Is.EqualTo (64), "WordCount #5"); + for (var i = 0; i < value.WordCount; i++) + Assert.That (value.Words [i], Is.EqualTo (0), $"WordCount #5 - {i}"); + for (var i = 0; i < value.WordCount; i++) + Assert.That (value [i], Is.EqualTo (0), $"WordCount #5 - idx {i}"); + + // Words + + Assert.Throws (() => value.Words = null, "Words Null"); + + value.Words = new uint [0]; + Assert.That (value.WordCount, Is.EqualTo (0), "Words #1"); + Assert.That (value.Words.Length, Is.EqualTo (0), "Words #1 - Length"); + + value.Words = new uint [] { 2 }; + Assert.That (value.WordCount, Is.EqualTo (1), "Words #2"); + Assert.That (value.Words.Length, Is.EqualTo (1), "Words #2 - Length"); + Assert.That (value.Words [0], Is.EqualTo (2), "Words #2 - element"); + Assert.That (value [0], Is.EqualTo (2), "Words #2 - idx"); + + ex = Assert.Throws (() => value.Words = new uint [65], "Words #3"); + Assert.That (ex.Message, Does.Contain ("WordCount can't be higher than 64."), $"Words #3 message"); + + var array = Enumerable.Range (1, 64).Select (v => (uint) (v * 2)).ToArray (); + value.Words = Enumerable.Range (1, 64).Select (v => (uint) (v * 2)).ToArray (); + Assert.That (value.WordCount, Is.EqualTo (64), "Words #5"); + Assert.That (value.Words.Length, Is.EqualTo (64), "Words #5 - Length"); + for (var i = 0; i < 64; i++) { + Assert.That (value.Words [i], Is.EqualTo ((i + 1) * 2), $"Words #5 - element {i}"); + Assert.That (value [i], Is.EqualTo ((i + 1) * 2), $"Words #5 - indexer {i}"); + Assert.That (array [i], Is.EqualTo ((i + 1) * 2), $"Words #5 - array {i}"); + } + + // indexer + value.Words = new uint [64]; + Assert.That (value.WordCount, Is.EqualTo (64), "indexer #1"); + Assert.That (value.Words.Length, Is.EqualTo (64), "indexer #1 - Length"); + for (var i = 0; i < 64; i++) { + Assert.That (value [i], Is.EqualTo (0), $"indexer #1 - element {i} - 1"); + var v = (uint) ((i + 3) * 3); + value [i] = v; + Assert.That (value [i], Is.EqualTo (v), $"indexer #1 - element {i} - 2"); + } + }); + } + } +} + +#endif // HAS_COREMIDI diff --git a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index ca7683dfa9c4..5dd862cb92f5 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -1,30 +1,2 @@ -# no known use -!missing-pinvoke! MIDIEndpointGetRefCons is not bound -!missing-pinvoke! MIDIEndpointSetRefCons is not bound - -# old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 +# There's no need to bind this P/Invoke, we bind/use 'MIDIClientCreate' which provide the exact same capabilities. !missing-pinvoke! MIDIClientCreateWithBlock is not bound -!missing-pinvoke! MIDIDeviceCreate is not bound -!missing-pinvoke! MIDIDeviceDispose is not bound -!missing-pinvoke! MIDIDeviceRemoveEntity is not bound -!missing-pinvoke! MIDIEntityAddOrRemoveEndpoints is not bound -!missing-pinvoke! MIDIExternalDeviceCreate is not bound -!missing-pinvoke! MIDIGetDriverDeviceList is not bound -!missing-pinvoke! MIDIGetDriverIORunLoop is not bound -!missing-pinvoke! MIDISendSysex is not bound -!missing-pinvoke! MIDISetupAddDevice is not bound -!missing-pinvoke! MIDISetupAddExternalDevice is not bound -!missing-pinvoke! MIDISetupRemoveDevice is not bound -!missing-pinvoke! MIDISetupRemoveExternalDevice is not bound - -# same as the above, we should bind all of them, these have been added on Xcode 12 beta 2 -# https://github.com/dotnet/macios/issues/4452#issuecomment-660220392 -!missing-pinvoke! MIDIDestinationCreateWithProtocol is not bound -!missing-pinvoke! MIDIDeviceNewEntity is not bound -!missing-pinvoke! MIDIInputPortCreateWithProtocol is not bound -!missing-pinvoke! MIDIReceivedEventList is not bound -!missing-pinvoke! MIDISendEventList is not bound -!missing-pinvoke! MIDISourceCreateWithProtocol is not bound -!missing-pinvoke! MIDIEventPacketSysexBytesForGroup is not bound -!missing-pinvoke! MIDISendUMPSysex is not bound -!missing-pinvoke! MIDISendUMPSysex8 is not bound diff --git a/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore index 166e9451a6be..d5d48a5d41b7 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore @@ -1,4 +1,2 @@ # https://github.com/dotnet/macios/issues/4452#issuecomment-660220392 -!missing-pinvoke! MIDIEventListAdd is not bound -!missing-pinvoke! MIDIEventListInit is not bound !missing-pinvoke! MIDIEventListForEachEvent is not bound diff --git a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore index ca7683dfa9c4..5dd862cb92f5 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -1,30 +1,2 @@ -# no known use -!missing-pinvoke! MIDIEndpointGetRefCons is not bound -!missing-pinvoke! MIDIEndpointSetRefCons is not bound - -# old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 +# There's no need to bind this P/Invoke, we bind/use 'MIDIClientCreate' which provide the exact same capabilities. !missing-pinvoke! MIDIClientCreateWithBlock is not bound -!missing-pinvoke! MIDIDeviceCreate is not bound -!missing-pinvoke! MIDIDeviceDispose is not bound -!missing-pinvoke! MIDIDeviceRemoveEntity is not bound -!missing-pinvoke! MIDIEntityAddOrRemoveEndpoints is not bound -!missing-pinvoke! MIDIExternalDeviceCreate is not bound -!missing-pinvoke! MIDIGetDriverDeviceList is not bound -!missing-pinvoke! MIDIGetDriverIORunLoop is not bound -!missing-pinvoke! MIDISendSysex is not bound -!missing-pinvoke! MIDISetupAddDevice is not bound -!missing-pinvoke! MIDISetupAddExternalDevice is not bound -!missing-pinvoke! MIDISetupRemoveDevice is not bound -!missing-pinvoke! MIDISetupRemoveExternalDevice is not bound - -# same as the above, we should bind all of them, these have been added on Xcode 12 beta 2 -# https://github.com/dotnet/macios/issues/4452#issuecomment-660220392 -!missing-pinvoke! MIDIDestinationCreateWithProtocol is not bound -!missing-pinvoke! MIDIDeviceNewEntity is not bound -!missing-pinvoke! MIDIInputPortCreateWithProtocol is not bound -!missing-pinvoke! MIDIReceivedEventList is not bound -!missing-pinvoke! MIDISendEventList is not bound -!missing-pinvoke! MIDISourceCreateWithProtocol is not bound -!missing-pinvoke! MIDIEventPacketSysexBytesForGroup is not bound -!missing-pinvoke! MIDISendUMPSysex is not bound -!missing-pinvoke! MIDISendUMPSysex8 is not bound diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore index c413b9edef6e..5dd862cb92f5 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -1,36 +1,2 @@ -# deprecated pinvokes and not binded. -!missing-pinvoke! MIDIDriverEnableMonitoring is not bound - -!missing-field! kMIDIDriverPropertyUsesSerial not bound - -# no known use -!missing-pinvoke! MIDIEndpointGetRefCons is not bound -!missing-pinvoke! MIDIEndpointSetRefCons is not bound - -# old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 +# There's no need to bind this P/Invoke, we bind/use 'MIDIClientCreate' which provide the exact same capabilities. !missing-pinvoke! MIDIClientCreateWithBlock is not bound -!missing-pinvoke! MIDIDeviceCreate is not bound -!missing-pinvoke! MIDIDeviceDispose is not bound -!missing-pinvoke! MIDIDeviceRemoveEntity is not bound -!missing-pinvoke! MIDIEntityAddOrRemoveEndpoints is not bound -!missing-pinvoke! MIDIExternalDeviceCreate is not bound -!missing-pinvoke! MIDIGetDriverDeviceList is not bound -!missing-pinvoke! MIDIGetDriverIORunLoop is not bound -!missing-pinvoke! MIDISendSysex is not bound -!missing-pinvoke! MIDISetupAddDevice is not bound -!missing-pinvoke! MIDISetupAddExternalDevice is not bound -!missing-pinvoke! MIDISetupRemoveDevice is not bound -!missing-pinvoke! MIDISetupRemoveExternalDevice is not bound - -# same as the above, we should bind all of them, these have been added on Xcode 12 beta 2 -# https://github.com/dotnet/macios/issues/4452#issuecomment-660220392 -!missing-pinvoke! MIDIDestinationCreateWithProtocol is not bound -!missing-pinvoke! MIDIDeviceNewEntity is not bound -!missing-pinvoke! MIDIInputPortCreateWithProtocol is not bound -!missing-pinvoke! MIDIReceivedEventList is not bound -!missing-pinvoke! MIDISendEventList is not bound -!missing-pinvoke! MIDISourceCreateWithProtocol is not bound - -!missing-pinvoke! MIDIEventPacketSysexBytesForGroup is not bound -!missing-pinvoke! MIDISendUMPSysex is not bound -!missing-pinvoke! MIDISendUMPSysex8 is not bound diff --git a/tests/xtro-sharpie/api-annotations-dotnet/tvOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/tvOS-CoreMIDI.ignore deleted file mode 100644 index 9e8bc217096d..000000000000 --- a/tests/xtro-sharpie/api-annotations-dotnet/tvOS-CoreMIDI.ignore +++ /dev/null @@ -1,7 +0,0 @@ -# CoreMIDI not supported for TV in Xcode14 -!missing-enum! MIDINotificationMessageID not bound -!missing-enum! MIDIObjectType not bound -!missing-enum! MIDITransformControlType not bound -!missing-enum! MIDITransformType not bound -!missing-pinvoke! MIDIBluetoothDriverActivateAllConnections is not bound -!missing-pinvoke! MIDIBluetoothDriverDisconnect is not bound