From aee0f263e3f9f9602afa88dac779aff11db281c6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 26 Mar 2025 16:32:08 +0100 Subject: [PATCH 01/19] [CoreMidi] Add a MidiObject.GetCheckedHandle method, and use it. --- src/CoreMidi/MidiServices.cs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index 29d2476ee654..43ded24ef76b 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -239,6 +239,14 @@ internal MidiObjectRef MidiHandle { get { return handle; } } + internal MidiObjectRef GetCheckedHandle () + { + if (handle == MidiObject.InvalidRef) + throw new ObjectDisposedException ("handle"); + + return handle; + } + internal MidiObject () { owns = true; @@ -1820,9 +1828,7 @@ public class MidiDevice : MidiObject { 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); @@ -1836,10 +1842,8 @@ public class MidiDevice : MidiObject { [ObsoletedOSPlatform ("macos11.0")] 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); } } @@ -2512,16 +2516,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 +2533,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 () From db03d8ac2f2558122d0f6b4a428c0ee308d4a2a7 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 24 Mar 2025 10:27:51 +0100 Subject: [PATCH 02/19] [CoreMidi] Create MidiEventList and MidiEventListPacket, and bind Midi[Send|Received]EventList. Partial fix for https://github.com/dotnet/macios/issues/12489. --- src/CoreMidi/MidiEventList.cs | 257 ++++++++++++++++++ src/CoreMidi/MidiEventPacket.cs | 175 ++++++++++++ src/frameworks.sources | 2 + .../CoreMidi/MidiEventListTest.cs | 157 +++++++++++ .../CoreMidi/MidiEventPacketTest.cs | 130 +++++++++ .../MacCatalyst-CoreMIDI.ignore | 2 - .../common-CoreMIDI.ignore | 2 - .../iOS-CoreMIDI.ignore | 2 - .../macOS-CoreMIDI.ignore | 2 - 9 files changed, 721 insertions(+), 8 deletions(-) create mode 100644 src/CoreMidi/MidiEventList.cs create mode 100644 src/CoreMidi/MidiEventPacket.cs create mode 100644 tests/monotouch-test/CoreMidi/MidiEventListTest.cs create mode 100644 tests/monotouch-test/CoreMidi/MidiEventPacketTest.cs diff --git a/src/CoreMidi/MidiEventList.cs b/src/CoreMidi/MidiEventList.cs new file mode 100644 index 000000000000..fc6c0ea608e3 --- /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..dbc2181f6845 --- /dev/null +++ b/src/CoreMidi/MidiEventPacket.cs @@ -0,0 +1,175 @@ +// 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; + } + } + } + } +} + +#endif // !__TVOS__ diff --git a/src/frameworks.sources b/src/frameworks.sources index f2595d372e88..67e149644da1 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -607,6 +607,8 @@ COREMIDI_CORE_SOURCES = \ COREMIDI_SOURCES = \ CoreMidi/MidiBluetoothDriver.cs \ + CoreMidi/MidiEventList.cs \ + CoreMidi/MidiEventPacket.cs \ # CoreML 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..745abf9276a8 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -22,8 +22,6 @@ !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 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..745abf9276a8 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -22,8 +22,6 @@ !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 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore index c413b9edef6e..1f3c8d9899b1 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -27,8 +27,6 @@ !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 From c9638f7a5d0a2de1ad01cb4106c5047dc8862114 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 26 Mar 2025 16:32:22 +0100 Subject: [PATCH 03/19] [CoreMidi] Bind MIDIEndPoint[Get|Set]RefCons. --- src/CoreMidi/MidiServices.cs | 31 +++++++++++ .../CoreMidi/MidiEndpointTest.cs | 51 +++++++++++++++++++ .../MacCatalyst-CoreMIDI.ignore | 4 -- .../iOS-CoreMIDI.ignore | 4 -- .../macOS-CoreMIDI.ignore | 4 -- 5 files changed, 82 insertions(+), 12 deletions(-) diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index 43ded24ef76b..e872c7f41c6f 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -42,6 +42,7 @@ using System; using System.ComponentModel; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using ObjCRuntime; using CoreFoundation; @@ -2982,6 +2983,36 @@ 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); + } + } // MidiEndpoint #endif // !COREBUILD } 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/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index 745abf9276a8..52ad06cc8475 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -1,7 +1,3 @@ -# 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 !missing-pinvoke! MIDIClientCreateWithBlock is not bound !missing-pinvoke! MIDIDeviceCreate 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 745abf9276a8..52ad06cc8475 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -1,7 +1,3 @@ -# 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 !missing-pinvoke! MIDIClientCreateWithBlock is not bound !missing-pinvoke! MIDIDeviceCreate 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 1f3c8d9899b1..cab57ab5b777 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -3,10 +3,6 @@ !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 !missing-pinvoke! MIDIClientCreateWithBlock is not bound !missing-pinvoke! MIDIDeviceCreate is not bound From c1404c054069adce0b2cc9b96d465224d024e975 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 31 Mar 2025 16:04:46 +0200 Subject: [PATCH 04/19] [CoreMidi] Bind MIDIExternalDeviceCreate/MIDISetupAddExternalDevice/MIDISetupRemoveExternalDevice. --- src/CoreMidi/MidiServices.cs | 50 +++++++++++++++++++ .../monotouch-test/CoreMidi/MidiDeviceTest.cs | 34 +++++++++++++ .../MacCatalyst-CoreMIDI.ignore | 4 -- .../common-CoreMIDI.ignore | 3 ++ .../iOS-CoreMIDI.ignore | 4 -- .../macOS-CoreMIDI.ignore | 4 -- 6 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 tests/monotouch-test/CoreMidi/MidiDeviceTest.cs diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index e872c7f41c6f..be8360efff75 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -206,6 +206,56 @@ 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 + } + + public static class MidiSetup { +#if !COREBUILD + [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 } 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/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index 52ad06cc8475..73e462aedd25 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -1,17 +1,13 @@ # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!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 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore index d5d48a5d41b7..e4221b158f88 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore @@ -1,2 +1,5 @@ # https://github.com/dotnet/macios/issues/4452#issuecomment-660220392 !missing-pinvoke! MIDIEventListForEachEvent is not bound + +# 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 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore index 52ad06cc8475..73e462aedd25 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -1,17 +1,13 @@ # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!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 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore index cab57ab5b777..4c7b97a9cfe4 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -4,19 +4,15 @@ !missing-field! kMIDIDriverPropertyUsesSerial not bound # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!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 From c36ed6b1b0e47dd660c704c4f4f68872a0048636 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 31 Mar 2025 19:30:16 +0200 Subject: [PATCH 05/19] MIDISetup - add/remove device. Missing tests. --- src/CoreMidi/MidiServices.cs | 24 +++++++++++++++++++ .../MacCatalyst-CoreMIDI.ignore | 2 -- .../iOS-CoreMIDI.ignore | 2 -- .../macOS-CoreMIDI.ignore | 2 -- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index be8360efff75..58995e553d09 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -233,8 +233,32 @@ public static nint DeviceCount { #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); diff --git a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index 73e462aedd25..94217fce260f 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -6,8 +6,6 @@ !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! MIDISetupRemoveDevice 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 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore index 73e462aedd25..94217fce260f 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -6,8 +6,6 @@ !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! MIDISetupRemoveDevice 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 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore index 4c7b97a9cfe4..a872ad87dab5 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -11,8 +11,6 @@ !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! MIDISetupRemoveDevice 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 From 600c2127afb2184274e89c222fd02dfc7a1795de Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 31 Mar 2025 20:08:52 +0200 Subject: [PATCH 06/19] AddRemoveEndpoints - missing tests. --- src/CoreMidi/MidiServices.cs | 13 +++++++++++++ .../MacCatalyst-CoreMIDI.ignore | 1 - .../api-annotations-dotnet/iOS-CoreMIDI.ignore | 1 - .../api-annotations-dotnet/macOS-CoreMIDI.ignore | 1 - 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index 58995e553d09..b2a2227b8cdb 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -1866,6 +1866,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 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index 94217fce260f..b491016390e6 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -2,7 +2,6 @@ !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! MIDIGetDriverDeviceList is not bound !missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex 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 94217fce260f..b491016390e6 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -2,7 +2,6 @@ !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! MIDIGetDriverDeviceList is not bound !missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex 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 a872ad87dab5..3286a5951edf 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -7,7 +7,6 @@ !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! MIDIGetDriverDeviceList is not bound !missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex is not bound From 1f7c94aeeeda4681d9077c69cd55a3d57750f8bb Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 1 Apr 2025 09:14:23 +0200 Subject: [PATCH 07/19] MIDIClientCreateWithBlock --- .../api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore | 3 +++ .../xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore | 3 --- tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore | 3 +++ .../xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore | 3 +++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index b491016390e6..9c2aca4811b8 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -1,3 +1,6 @@ +# 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 + # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 !missing-pinvoke! MIDIDeviceCreate is not bound !missing-pinvoke! MIDIDeviceDispose 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 e4221b158f88..d5d48a5d41b7 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/common-CoreMIDI.ignore @@ -1,5 +1,2 @@ # https://github.com/dotnet/macios/issues/4452#issuecomment-660220392 !missing-pinvoke! MIDIEventListForEachEvent is not bound - -# 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 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore index b491016390e6..9c2aca4811b8 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -1,3 +1,6 @@ +# 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 + # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 !missing-pinvoke! MIDIDeviceCreate is not bound !missing-pinvoke! MIDIDeviceDispose 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 3286a5951edf..20e6d796cb46 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -1,3 +1,6 @@ +# 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 + # deprecated pinvokes and not binded. !missing-pinvoke! MIDIDriverEnableMonitoring is not bound From 7886e64d995d13e68b547d2dc286aed203592411 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 1 Apr 2025 12:45:43 +0200 Subject: [PATCH 08/19] New/RemoveEntity --- src/CoreMidi/MidiServices.cs | 52 ++++++++++++++++--- .../MacCatalyst-CoreMIDI.ignore | 2 - .../iOS-CoreMIDI.ignore | 2 - .../macOS-CoreMIDI.ignore | 2 - 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index b2a2227b8cdb..7ac30edde559 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -1905,12 +1905,38 @@ 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); @@ -1925,9 +1951,9 @@ 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) { using (NSString nsName = new NSString (name)) { @@ -1935,6 +1961,20 @@ public int Add (string name, bool embedded, nuint numSourceEndpoints, nuint numD } } + [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 ()); + } + /// Returns the number of MIDI entities in this device. /// /// diff --git a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index 9c2aca4811b8..b9aa341fa459 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -4,7 +4,6 @@ # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 !missing-pinvoke! MIDIDeviceCreate is not bound !missing-pinvoke! MIDIDeviceDispose is not bound -!missing-pinvoke! MIDIDeviceRemoveEntity is not bound !missing-pinvoke! MIDIGetDriverDeviceList is not bound !missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex is not bound @@ -12,7 +11,6 @@ # 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! MIDISourceCreateWithProtocol is not bound !missing-pinvoke! MIDIEventPacketSysexBytesForGroup 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 9c2aca4811b8..b9aa341fa459 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -4,7 +4,6 @@ # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 !missing-pinvoke! MIDIDeviceCreate is not bound !missing-pinvoke! MIDIDeviceDispose is not bound -!missing-pinvoke! MIDIDeviceRemoveEntity is not bound !missing-pinvoke! MIDIGetDriverDeviceList is not bound !missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex is not bound @@ -12,7 +11,6 @@ # 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! MIDISourceCreateWithProtocol is not bound !missing-pinvoke! MIDIEventPacketSysexBytesForGroup 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 20e6d796cb46..0c43a1fd36a3 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -9,7 +9,6 @@ # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 !missing-pinvoke! MIDIDeviceCreate is not bound !missing-pinvoke! MIDIDeviceDispose is not bound -!missing-pinvoke! MIDIDeviceRemoveEntity is not bound !missing-pinvoke! MIDIGetDriverDeviceList is not bound !missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex is not bound @@ -17,7 +16,6 @@ # 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! MIDISourceCreateWithProtocol is not bound From 2ac388c8bf18f18340c370aed6944394bf9717d3 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Tue, 1 Apr 2025 16:52:36 +0200 Subject: [PATCH 09/19] Create/DisposeDevice: missing tests --- src/CoreFoundation/CFUuidBytes.cs | 23 +++ src/CoreMidi/MidiDriverInterface.cs | 194 ++++++++++++++++++ src/CoreMidi/MidiServices.cs | 44 ++++ src/frameworks.sources | 2 + .../MacCatalyst-CoreMIDI.ignore | 2 - .../iOS-CoreMIDI.ignore | 2 - .../macOS-CoreMIDI.ignore | 2 - 7 files changed, 263 insertions(+), 6 deletions(-) create mode 100644 src/CoreFoundation/CFUuidBytes.cs create mode 100644 src/CoreMidi/MidiDriverInterface.cs 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/MidiDriverInterface.cs b/src/CoreMidi/MidiDriverInterface.cs new file mode 100644 index 000000000000..8af79ff6729b --- /dev/null +++ b/src/CoreMidi/MidiDriverInterface.cs @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#if !__TVOS__ + +using System.Runtime.InteropServices; + +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; + +namespace CoreMidi { + public class MidiDriver { +#if !COREBUILD + MidiDriverInterface driverInterface = default; + + internal MidiDriverInterface DriverInterface { get => driverInterface; } + + internal MidiDriver (MidiDriverInterface iface) + { + driverInterface = iface; + } +#endif // COREBUILD + } + + struct MidiDriverInterface { +#pragma warning disable CS0649 // Field '...' is never assigned to, and will always have its default value 0 +#pragma warning disable CS0169 // The field '...' is never used + /* IUNKNOWN_C_GUTS; + + void *_reserved; \ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(void *thisPointer, REFIID iid, LPVOID *ppv); \ + ULONG (STDMETHODCALLTYPE *AddRef)(void *thisPointer); \ + ULONG (STDMETHODCALLTYPE *Release)(void *thisPointer) \ + + */ + + IntPtr _reserved; + unsafe delegate* unmanaged QueryInterface; + unsafe delegate* unmanaged AddRef; + unsafe delegate* unmanaged Release; + + /*! + @fn FindDevices + @discussion + This is only called for version 1 drivers. The server is requesting that the driver + detect the devices which are present. For each device present, the driver should + create a MIDIDeviceRef with entities, using MIDIDeviceCreate and + MIDIDeviceAddEntity, and add the device to the supplied MIDIDeviceListRef, using + MIDIDeviceListAddDevice. + + The driver should not retain any references to the created devices and entities. + */ + // OSStatus (*FindDevices)(MIDIDriverRef __nonnull self, MIDIDeviceListRef devList); + unsafe delegate* unmanaged FindDevices; + + /*! + @fn Start + @discussion + The server is telling the driver to begin MIDI I/O. + + The provided device list contains the devices which were previously located by + FindDevices (in the case of a version 1 driver), or the devices which are owned by + this driver and are currently in the current MIDISetup (for version 2 drivers). + + The provided devices may or may not still be present. A version 1 driver should + attempt to use as many of the devices as are actually present. + + A version 2 driver may make calls such as MIDISetupAddDevice, MIDIDeviceAddEntity, + MIDIDeviceRemoveEntity to dynamically modify the system's current state. For devices + in the provided device list which are not present, the driver should set their + kMIDIPropertyOffline property to 1. A version 2 driver may also set up + notifications when the IORegistry changes, to detect connection and disconnection of + devices it wishes to control. At these times also, the driver may change the + devices' kMIDIPropertyOffline, and dynamically modify the system's current state to + reflect the devices which are present. When passing a CFRunLoopRef to IOKit for + notification purposes, the driver must use the server's main runloop, which is + obtained with CFRunLoopGetCurrent(). + + The driver will probably want to iterate through the destination endpoints and + assign their driver refCons, so as to identify multiple destinations when Send() is + called. + + The provided device list remains owned by the system and can be assumed to contain + only devices owned by this driver. The driver may retain references to the devices + in this list and any it creates while running. + */ + // OSStatus (*Start)(MIDIDriverRef __nonnull self, MIDIDeviceListRef devList); + unsafe delegate* unmanaged Start; + + /*! + @fn Stop + @discussion + The server is telling the driver to terminate MIDI I/O. All I/O operations that + were begun in Start, or as a result of a subsequent IOKit notification, should be + terminated. + */ + // OSStatus (*Stop)(MIDIDriverRef __nonnull self); + unsafe delegate* unmanaged Stop; + + /*! + @fn Configure + @discussion + not currently used + */ + // OSStatus (*Configure)(MIDIDriverRef __nonnull self, MIDIDeviceRef device); + unsafe delegate* unmanaged Configure; + + /*! + @fn Send + @discussion + Send a MIDIPacketList to the destination endpoint whose refCons are being passed as + arguments. + */ + // OSStatus (*Send)(MIDIDriverRef __nonnull self, const MIDIPacketList *pktlist, void *destRefCon1, void *destRefCon2); + unsafe delegate* unmanaged Send; + + /*! + @fn EnableSource + @discussion + A client has opened or closed a connection, and now the server is telling the driver + that input from a particular source either does or does not have any listeners in + the system. The driver may use this information to decide whether to pass messages + from the source to the server, and it may even be able to tell the source hardware + not to generate incoming MIDI I/O for that source. + */ + // OSStatus (*EnableSource)(MIDIDriverRef __nonnull self, MIDIEndpointRef src, Boolean enabled); + unsafe delegate* unmanaged EnableSource; + + /*! + @fn Flush + @discussion + Only for version 2 drivers (new for CoreMIDI 1.1). + + Drivers which support schedule-ahead, when receiving this message, should unschedule + all pending output to the specified destination. If the destination is null/0, the + driver should unschedule all pending output to all destinations. + */ + // OSStatus (*Flush)(MIDIDriverRef __nonnull self, MIDIEndpointRef dest, void * __nullable destRefCon1, void * __nullable destRefCon2); + unsafe delegate* unmanaged Flush; + + /*! + @fn Monitor + @discussion + Only for version 2 drivers (new for CoreMIDI 1.1). + + Some specialized drivers (e.g. a MIDI monitor display) may wish to intercept and + look at all outgoing MIDI messages. After a driver calls + MIDIDriverEnableMonitoring(true) on itself, this function is called with the + outgoing MIDI packets for all destinations in the system. The Monitor function + cannot rely on the MIDI events arriving in order, due to MIDIServer's schedule-ahead + facilities. + */ + // OSStatus (*Monitor)(MIDIDriverRef __nonnull self, MIDIEndpointRef dest, const MIDIPacketList *pktlist); + unsafe delegate* unmanaged Monitor; + + /*! + @fn SendPackets + @discussion + Only for version 3 drivers (new for macOS 12.0). + + Send a MIDIEventList to the destination endpoint whose refCons are being passed as + arguments. + */ + // OSStatus (*SendPackets)(MIDIDriverRef __nonnull self, const MIDIEventList *pktlist, void *destRefCon1, void *destRefCon2); + unsafe delegate* unmanaged SendPackets; + + /*! + @fn MonitorEvents + @discussion + Only for version 3 drivers (new for macOS 12.0). + + Same as Monitor but uses MIDEventList, whose protocol may vary from MIDI 1.0. + */ + // OSStatus (*MonitorEvents)(MIDIDriverRef __nonnull self, MIDIEndpointRef dest, const MIDIEventList *pktlist); + unsafe delegate* unmanaged MonitorEvents; +#pragma warning restore CS0169 +#pragma warning restore CS0649 + } +} + +#endif // !__TVOS__ + diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index 7ac30edde559..23bb79a4945b 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -1975,6 +1975,50 @@ public MidiError Remove (MidiEntity entity) return (MidiError) MIDIDeviceRemoveEntity (GetCheckedHandle (), entity.GetCheckedHandle ()); } + [DllImport (Constants.CoreMidiLibrary)] + unsafe extern static OSStatus MIDIDeviceCreate (IntPtr /* 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. + 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 { + var driverInterface = default (MidiDriverInterface); + if (owner is not null) + driverInterface = owner.DriverInterface; + MidiDriverInterface* driverInterfacePtr1 = &driverInterface; + var driverInterfacePtr2 = default (IntPtr); + if (owner is not null) + driverInterfacePtr2 = (IntPtr) (&driverInterfacePtr1); + status = (MidiError) MIDIDeviceCreate (driverInterfacePtr2, namePtr, manufacturerPtr, modelPtr, &handle); + } + + 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. /// /// diff --git a/src/frameworks.sources b/src/frameworks.sources index 67e149644da1..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 \ diff --git a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index b9aa341fa459..7441b698bd48 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -2,8 +2,6 @@ !missing-pinvoke! MIDIClientCreateWithBlock is not bound # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!missing-pinvoke! MIDIDeviceCreate is not bound -!missing-pinvoke! MIDIDeviceDispose is not bound !missing-pinvoke! MIDIGetDriverDeviceList is not bound !missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex 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 b9aa341fa459..7441b698bd48 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -2,8 +2,6 @@ !missing-pinvoke! MIDIClientCreateWithBlock is not bound # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!missing-pinvoke! MIDIDeviceCreate is not bound -!missing-pinvoke! MIDIDeviceDispose is not bound !missing-pinvoke! MIDIGetDriverDeviceList is not bound !missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex 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 0c43a1fd36a3..1ab18dd95f9f 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -7,8 +7,6 @@ !missing-field! kMIDIDriverPropertyUsesSerial not bound # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!missing-pinvoke! MIDIDeviceCreate is not bound -!missing-pinvoke! MIDIDeviceDispose is not bound !missing-pinvoke! MIDIGetDriverDeviceList is not bound !missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex is not bound From e42213add2c611291f9cc1b178f29af1ce8647a5 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 2 Apr 2025 17:28:56 +0200 Subject: [PATCH 10/19] GetDriverDeviceList/GetDeviceIORunLoop. missing tests. --- src/CoreMidi/MidiDriverInterface.cs | 28 +++++++++++++++++++ .../MacCatalyst-CoreMIDI.ignore | 2 -- .../iOS-CoreMIDI.ignore | 2 -- .../macOS-CoreMIDI.ignore | 2 -- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/CoreMidi/MidiDriverInterface.cs b/src/CoreMidi/MidiDriverInterface.cs index 8af79ff6729b..709654c915cc 100644 --- a/src/CoreMidi/MidiDriverInterface.cs +++ b/src/CoreMidi/MidiDriverInterface.cs @@ -31,6 +31,34 @@ internal MidiDriver (MidiDriverInterface iface) { driverInterface = iface; } + + [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 *driver = &driverInterface) { + var rv = MIDIGetDriverDeviceList (&driver); + 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); + } #endif // COREBUILD } diff --git a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index 7441b698bd48..54c66724c5e6 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -2,8 +2,6 @@ !missing-pinvoke! MIDIClientCreateWithBlock is not bound # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!missing-pinvoke! MIDIGetDriverDeviceList is not bound -!missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex is not bound # same as the above, we should bind all of them, these have been added on Xcode 12 beta 2 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore index 7441b698bd48..54c66724c5e6 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -2,8 +2,6 @@ !missing-pinvoke! MIDIClientCreateWithBlock is not bound # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!missing-pinvoke! MIDIGetDriverDeviceList is not bound -!missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex is not bound # same as the above, we should bind all of them, these have been added on Xcode 12 beta 2 diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore index 1ab18dd95f9f..572c86f04c7f 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -7,8 +7,6 @@ !missing-field! kMIDIDriverPropertyUsesSerial not bound # old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!missing-pinvoke! MIDIGetDriverDeviceList is not bound -!missing-pinvoke! MIDIGetDriverIORunLoop is not bound !missing-pinvoke! MIDISendSysex is not bound # same as the above, we should bind all of them, these have been added on Xcode 12 beta 2 From 53b26f683fb425a99339f28d74e9ee413518e59a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 2 Apr 2025 18:17:06 +0200 Subject: [PATCH 11/19] MIDISendSysex: missing tests. --- src/CoreMidi/MidiServices.cs | 218 ++++++++++++++++++ src/CoreMidi/MidiStructs.cs | 136 +++++++++++ .../MacCatalyst-CoreMIDI.ignore | 5 - .../iOS-CoreMIDI.ignore | 5 - .../macOS-CoreMIDI.ignore | 5 - 5 files changed, 354 insertions(+), 15 deletions(-) diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index 23bb79a4945b..d7f7f41825be 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -44,6 +44,9 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + using ObjCRuntime; using CoreFoundation; using Foundation; @@ -3184,6 +3187,221 @@ public MidiError SetRefCons (IntPtr ref1, IntPtr ref2) 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 == null) + return; + rv->Complete = true; + } + + unsafe void UmpSysexCancellationRequest () + { + var rv = (MidiSysexSendRequestUmp *) structPointer; + if (rv == 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 } diff --git a/src/CoreMidi/MidiStructs.cs b/src/CoreMidi/MidiStructs.cs index 390d427287ea..809a311126d0 100644 --- a/src/CoreMidi/MidiStructs.cs +++ b/src/CoreMidi/MidiStructs.cs @@ -141,5 +141,141 @@ 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/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index 54c66724c5e6..8b5aaeecc5d5 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -1,14 +1,9 @@ # 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 -# old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!missing-pinvoke! MIDISendSysex 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! MIDIInputPortCreateWithProtocol 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/iOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore index 54c66724c5e6..8b5aaeecc5d5 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -1,14 +1,9 @@ # 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 -# old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!missing-pinvoke! MIDISendSysex 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! MIDIInputPortCreateWithProtocol 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 572c86f04c7f..54f85e2e6df8 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -6,9 +6,6 @@ !missing-field! kMIDIDriverPropertyUsesSerial not bound -# old apis that have been ignored, please refer to issue https://github.com/dotnet/macios/issues/4452 -!missing-pinvoke! MIDISendSysex 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 @@ -16,5 +13,3 @@ !missing-pinvoke! MIDISourceCreateWithProtocol is not bound !missing-pinvoke! MIDIEventPacketSysexBytesForGroup is not bound -!missing-pinvoke! MIDISendUMPSysex is not bound -!missing-pinvoke! MIDISendUMPSysex8 is not bound From 803d1f311e14fc057ea4952469fd93afa2014635 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 2 Apr 2025 18:22:16 +0200 Subject: [PATCH 12/19] MIDIDriverEnableMonitoring: missing tests. --- src/CoreMidi/MidiDriverInterface.cs | 23 +++++++++++++++++++ .../macOS-CoreMIDI.ignore | 3 --- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/CoreMidi/MidiDriverInterface.cs b/src/CoreMidi/MidiDriverInterface.cs index 709654c915cc..91b7f0d2fba4 100644 --- a/src/CoreMidi/MidiDriverInterface.cs +++ b/src/CoreMidi/MidiDriverInterface.cs @@ -59,6 +59,29 @@ internal MidiDriver (MidiDriverInterface iface) 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 } diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore index 54f85e2e6df8..2f56a7bd5460 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -1,9 +1,6 @@ # 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 -# deprecated pinvokes and not binded. -!missing-pinvoke! MIDIDriverEnableMonitoring is not bound - !missing-field! kMIDIDriverPropertyUsesSerial not bound # same as the above, we should bind all of them, these have been added on Xcode 12 beta 2 From 1457990d754000c17358bd14286e2afb09454319 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 2 Apr 2025 18:27:25 +0200 Subject: [PATCH 13/19] kMIDIDriverPropertyUsesSerial --- src/CoreMidi/MidiServices.cs | 22 +++++++++++++++---- src/coremidi.cs | 7 ++++++ .../macOS-CoreMIDI.ignore | 2 -- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index d7f7f41825be..f8fa25ce8c7c 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -2085,19 +2085,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. 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/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore index 2f56a7bd5460..357c1c7ba488 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -1,8 +1,6 @@ # 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-field! kMIDIDriverPropertyUsesSerial 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 From a5f3c9dec1058599c2605915a0cc64d85d6d3fb0 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 2 Apr 2025 19:15:11 +0200 Subject: [PATCH 14/19] MIDIEventPacketSysexBytesForGroup --- src/CoreMidi/MidiEventPacket.cs | 32 +++++++++++++++++-- .../MacCatalyst-CoreMIDI.ignore | 1 - .../iOS-CoreMIDI.ignore | 1 - .../macOS-CoreMIDI.ignore | 2 -- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/CoreMidi/MidiEventPacket.cs b/src/CoreMidi/MidiEventPacket.cs index dbc2181f6845..bf8977fd2d44 100644 --- a/src/CoreMidi/MidiEventPacket.cs +++ b/src/CoreMidi/MidiEventPacket.cs @@ -169,7 +169,35 @@ public uint this [int index] { } } } - } -} +#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/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index 8b5aaeecc5d5..c92a95b9c55e 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -6,4 +6,3 @@ !missing-pinvoke! MIDIDestinationCreateWithProtocol is not bound !missing-pinvoke! MIDIInputPortCreateWithProtocol is not bound !missing-pinvoke! MIDISourceCreateWithProtocol is not bound -!missing-pinvoke! MIDIEventPacketSysexBytesForGroup 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 8b5aaeecc5d5..c92a95b9c55e 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -6,4 +6,3 @@ !missing-pinvoke! MIDIDestinationCreateWithProtocol is not bound !missing-pinvoke! MIDIInputPortCreateWithProtocol is not bound !missing-pinvoke! MIDISourceCreateWithProtocol is not bound -!missing-pinvoke! MIDIEventPacketSysexBytesForGroup 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 357c1c7ba488..c92a95b9c55e 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -6,5 +6,3 @@ !missing-pinvoke! MIDIDestinationCreateWithProtocol is not bound !missing-pinvoke! MIDIInputPortCreateWithProtocol is not bound !missing-pinvoke! MIDISourceCreateWithProtocol is not bound - -!missing-pinvoke! MIDIEventPacketSysexBytesForGroup is not bound From f2208d750ee9755e45d437836b6b15db5e4a1a07 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 3 Apr 2025 13:00:49 +0200 Subject: [PATCH 15/19] MIDIDestinationCreateWithProtocol/MIDISourceCreateWithProtocol/MIDIInputPortCreateWithProtocol --- src/CoreMidi/MidiServices.cs | 175 ++++++++++++++---- .../MacCatalyst-CoreMIDI.ignore | 6 - .../iOS-CoreMIDI.ignore | 6 - .../macOS-CoreMIDI.ignore | 6 - 4 files changed, 134 insertions(+), 59 deletions(-) diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index f8fa25ce8c7c..7f2489ef5ab6 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -625,11 +625,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; @@ -683,45 +690,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); @@ -732,6 +753,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. /// @@ -752,6 +799,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; @@ -1134,6 +1216,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)) { @@ -2824,17 +2913,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; } /// diff --git a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore index c92a95b9c55e..5dd862cb92f5 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/MacCatalyst-CoreMIDI.ignore @@ -1,8 +1,2 @@ # 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 - -# 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! MIDIInputPortCreateWithProtocol is not bound -!missing-pinvoke! MIDISourceCreateWithProtocol 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 c92a95b9c55e..5dd862cb92f5 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/iOS-CoreMIDI.ignore @@ -1,8 +1,2 @@ # 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 - -# 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! MIDIInputPortCreateWithProtocol is not bound -!missing-pinvoke! MIDISourceCreateWithProtocol 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 c92a95b9c55e..5dd862cb92f5 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-CoreMIDI.ignore @@ -1,8 +1,2 @@ # 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 - -# 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! MIDIInputPortCreateWithProtocol is not bound -!missing-pinvoke! MIDISourceCreateWithProtocol is not bound From 4cdc6d268ea76a11d379d623e24bc56efca8eb4e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 May 2025 10:01:08 +0200 Subject: [PATCH 16/19] [CoreMidi] Add [NativeName] to a few types. --- src/CoreMidi/MidiServices.cs | 2 ++ src/CoreMidi/MidiThruConnectionParams.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index 7f2489ef5ab6..48c80b80c2f8 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -102,6 +102,7 @@ public enum MidiError : int { [Flags] // SInt32 - MIDIServices.h + [NativeName ("MIDIObjectType")] enum MidiObjectType : int { Other = -1, Device, @@ -3514,6 +3515,7 @@ public void Dispose () } // SInt32 - MIDIServices.h + [NativeName ("MIDINotificationMessageID")] enum MidiNotificationMessageId : int { SetupChanged = 1, ObjectAdded, diff --git a/src/CoreMidi/MidiThruConnectionParams.cs b/src/CoreMidi/MidiThruConnectionParams.cs index 5c3312778940..4b145851ce51 100644 --- a/src/CoreMidi/MidiThruConnectionParams.cs +++ b/src/CoreMidi/MidiThruConnectionParams.cs @@ -24,6 +24,7 @@ namespace CoreMidi { /// MIDI transform types. /// To be added. + [NativeName ("MIDITransformType")] public enum MidiTransformType : ushort { /// To be added. None = 0, @@ -45,6 +46,7 @@ public enum MidiTransformType : ushort { /// MIDI Control Transformation Type. /// To be added. + [NativeName ("MIDITransformControlType")] public enum MidiTransformControlType : byte { /// To be added. SevenBit = 0, From 7199c691957dc03a49e6784c7575fd1f510e57e6 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 May 2025 09:58:43 +0200 Subject: [PATCH 17/19] [CoreMidi] Add some API to tvOS. --- src/CoreMidi/MidiBluetoothDriver.cs | 4 +--- src/CoreMidi/MidiServices.cs | 10 ++++++++-- src/CoreMidi/MidiThruConnectionParams.cs | 6 +++--- .../api-annotations-dotnet/tvOS-CoreMIDI.ignore | 7 ------- 4 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 tests/xtro-sharpie/api-annotations-dotnet/tvOS-CoreMIDI.ignore 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/MidiServices.cs b/src/CoreMidi/MidiServices.cs index 48c80b80c2f8..404942734593 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 // @@ -61,6 +60,7 @@ namespace CoreMidi { +#if !TVOS // anonymous enum - MIDIServices.h /// Errors raised by the CoreMIDI stack. /// @@ -99,6 +99,7 @@ public enum MidiError : int { /// To be added. NotPermitted = -10844, } +#endif // TVOS [Flags] // SInt32 - MIDIServices.h @@ -116,6 +117,7 @@ enum MidiObjectType : int { ExternalDestination = ExternalMask | Destination, } +#if !TVOS public static partial class Midi { #if !COREBUILD [DllImport (Constants.CoreMidiLibrary)] @@ -3513,6 +3515,8 @@ public void Dispose () // MidiEndpoint #endif // !COREBUILD } +#endif // TVOS + // SInt32 - MIDIServices.h [NativeName ("MIDINotificationMessageID")] @@ -3528,10 +3532,12 @@ enum MidiNotificationMessageId : int { [SupportedOSPlatform ("ios")] [SupportedOSPlatform ("maccatalyst")] [UnsupportedOSPlatform ("macos")] + [UnsupportedOSPlatform ("tvos")] InternalStart = 0x1000, #endif } +#if !TVOS // // The notification EventArgs // @@ -3705,5 +3711,5 @@ protected virtual void Dispose (bool disposing) } #endif // !COREBUILD } +#endif // TVOS } -#endif diff --git a/src/CoreMidi/MidiThruConnectionParams.cs b/src/CoreMidi/MidiThruConnectionParams.cs index 4b145851ce51..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) @@ -62,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")] @@ -655,5 +655,5 @@ internal NSData WriteStruct () } } #endif // !COREBUILD +#endif // TVOS } -#endif 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 From 385ee3031560bf86fdd7c02f6f8c6633f17850ef Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 21 May 2025 19:43:40 +0200 Subject: [PATCH 18/19] Driver --- docs/preview-apis.md | 12 +- src/CoreMidi/MidiDriverInterface.cs | 448 ++++++++++++++++++---------- src/CoreMidi/MidiServices.cs | 28 +- 3 files changed, 318 insertions(+), 170 deletions(-) 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/CoreMidi/MidiDriverInterface.cs b/src/CoreMidi/MidiDriverInterface.cs index 91b7f0d2fba4..be4f49148e3b 100644 --- a/src/CoreMidi/MidiDriverInterface.cs +++ b/src/CoreMidi/MidiDriverInterface.cs @@ -1,9 +1,19 @@ // 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; @@ -20,16 +30,254 @@ using MidiEventListPointer = System.IntPtr; using MidiPacketListPointer = System.IntPtr; +using HRESULT = System.Int32; + namespace CoreMidi { - public class MidiDriver { +#if !STABLE_MIDIDRIVER + [Experimental ("APL0004")] +#endif + [SupportedOSPlatform ("ios")] + [SupportedOSPlatform ("maccatalyst")] + [SupportedOSPlatform ("macos")] + public abstract class MidiDriver { #if !COREBUILD - MidiDriverInterface driverInterface = default; + 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; + } - internal MidiDriverInterface DriverInterface { get => driverInterface; } + [UnmanagedCallersOnly] + unsafe static OSStatus Stop (MidiDriverInterface* self) + { + var driver = self->GetObject (); + Console.WriteLine ($"MidiDriver.Stop ({(IntPtr) self}) => {driver}"); + return driver?.Stop () ?? 0; + } - internal MidiDriver (MidiDriverInterface iface) + /// Stop MIDI I/O. + protected virtual OSStatus Stop () { - driverInterface = iface; + 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)] @@ -39,8 +287,8 @@ internal MidiDriver (MidiDriverInterface iface) /// If successful, a list of the device this driver owns or created. Otherwise null. public unsafe MidiDeviceList? GetDeviceList () { - fixed (MidiDriverInterface *driver = &driverInterface) { - var rv = MIDIGetDriverDeviceList (&driver); + fixed (MidiDriverInterface** driverInterfacePtr = &driverInterface) { + var rv = MIDIGetDriverDeviceList (driverInterfacePtr); if (rv == MidiObject.InvalidRef) return null; return new MidiDeviceList (rv); @@ -77,168 +325,48 @@ internal MidiDriver (MidiDriverInterface iface) [UnsupportedOSPlatform ("maccatalyst")] public unsafe MidiError EnableMonitoring (bool enabled) { - fixed (MidiDriverInterface *driver = &driverInterface) { - return (MidiError) MIDIDriverEnableMonitoring (&driver, enabled.AsByte ()); + 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 CS0649 // Field '...' is never assigned to, and will always have its default value 0 -#pragma warning disable CS0169 // The field '...' is never used - /* IUNKNOWN_C_GUTS; - - void *_reserved; \ - HRESULT (STDMETHODCALLTYPE *QueryInterface)(void *thisPointer, REFIID iid, LPVOID *ppv); \ - ULONG (STDMETHODCALLTYPE *AddRef)(void *thisPointer); \ - ULONG (STDMETHODCALLTYPE *Release)(void *thisPointer) \ - - */ - - IntPtr _reserved; - unsafe delegate* unmanaged QueryInterface; - unsafe delegate* unmanaged AddRef; - unsafe delegate* unmanaged Release; - - /*! - @fn FindDevices - @discussion - This is only called for version 1 drivers. The server is requesting that the driver - detect the devices which are present. For each device present, the driver should - create a MIDIDeviceRef with entities, using MIDIDeviceCreate and - MIDIDeviceAddEntity, and add the device to the supplied MIDIDeviceListRef, using - MIDIDeviceListAddDevice. - - The driver should not retain any references to the created devices and entities. - */ - // OSStatus (*FindDevices)(MIDIDriverRef __nonnull self, MIDIDeviceListRef devList); - unsafe delegate* unmanaged FindDevices; - - /*! - @fn Start - @discussion - The server is telling the driver to begin MIDI I/O. - - The provided device list contains the devices which were previously located by - FindDevices (in the case of a version 1 driver), or the devices which are owned by - this driver and are currently in the current MIDISetup (for version 2 drivers). - - The provided devices may or may not still be present. A version 1 driver should - attempt to use as many of the devices as are actually present. - - A version 2 driver may make calls such as MIDISetupAddDevice, MIDIDeviceAddEntity, - MIDIDeviceRemoveEntity to dynamically modify the system's current state. For devices - in the provided device list which are not present, the driver should set their - kMIDIPropertyOffline property to 1. A version 2 driver may also set up - notifications when the IORegistry changes, to detect connection and disconnection of - devices it wishes to control. At these times also, the driver may change the - devices' kMIDIPropertyOffline, and dynamically modify the system's current state to - reflect the devices which are present. When passing a CFRunLoopRef to IOKit for - notification purposes, the driver must use the server's main runloop, which is - obtained with CFRunLoopGetCurrent(). - - The driver will probably want to iterate through the destination endpoints and - assign their driver refCons, so as to identify multiple destinations when Send() is - called. - - The provided device list remains owned by the system and can be assumed to contain - only devices owned by this driver. The driver may retain references to the devices - in this list and any it creates while running. - */ - // OSStatus (*Start)(MIDIDriverRef __nonnull self, MIDIDeviceListRef devList); - unsafe delegate* unmanaged Start; - - /*! - @fn Stop - @discussion - The server is telling the driver to terminate MIDI I/O. All I/O operations that - were begun in Start, or as a result of a subsequent IOKit notification, should be - terminated. - */ - // OSStatus (*Stop)(MIDIDriverRef __nonnull self); - unsafe delegate* unmanaged Stop; - - /*! - @fn Configure - @discussion - not currently used - */ - // OSStatus (*Configure)(MIDIDriverRef __nonnull self, MIDIDeviceRef device); - unsafe delegate* unmanaged Configure; - - /*! - @fn Send - @discussion - Send a MIDIPacketList to the destination endpoint whose refCons are being passed as - arguments. - */ - // OSStatus (*Send)(MIDIDriverRef __nonnull self, const MIDIPacketList *pktlist, void *destRefCon1, void *destRefCon2); - unsafe delegate* unmanaged Send; - - /*! - @fn EnableSource - @discussion - A client has opened or closed a connection, and now the server is telling the driver - that input from a particular source either does or does not have any listeners in - the system. The driver may use this information to decide whether to pass messages - from the source to the server, and it may even be able to tell the source hardware - not to generate incoming MIDI I/O for that source. - */ - // OSStatus (*EnableSource)(MIDIDriverRef __nonnull self, MIDIEndpointRef src, Boolean enabled); - unsafe delegate* unmanaged EnableSource; - - /*! - @fn Flush - @discussion - Only for version 2 drivers (new for CoreMIDI 1.1). - - Drivers which support schedule-ahead, when receiving this message, should unschedule - all pending output to the specified destination. If the destination is null/0, the - driver should unschedule all pending output to all destinations. - */ - // OSStatus (*Flush)(MIDIDriverRef __nonnull self, MIDIEndpointRef dest, void * __nullable destRefCon1, void * __nullable destRefCon2); - unsafe delegate* unmanaged Flush; - - /*! - @fn Monitor - @discussion - Only for version 2 drivers (new for CoreMIDI 1.1). - - Some specialized drivers (e.g. a MIDI monitor display) may wish to intercept and - look at all outgoing MIDI messages. After a driver calls - MIDIDriverEnableMonitoring(true) on itself, this function is called with the - outgoing MIDI packets for all destinations in the system. The Monitor function - cannot rely on the MIDI events arriving in order, due to MIDIServer's schedule-ahead - facilities. - */ - // OSStatus (*Monitor)(MIDIDriverRef __nonnull self, MIDIEndpointRef dest, const MIDIPacketList *pktlist); - unsafe delegate* unmanaged Monitor; - - /*! - @fn SendPackets - @discussion - Only for version 3 drivers (new for macOS 12.0). - - Send a MIDIEventList to the destination endpoint whose refCons are being passed as - arguments. - */ - // OSStatus (*SendPackets)(MIDIDriverRef __nonnull self, const MIDIEventList *pktlist, void *destRefCon1, void *destRefCon2); - unsafe delegate* unmanaged SendPackets; - - /*! - @fn MonitorEvents - @discussion - Only for version 3 drivers (new for macOS 12.0). - - Same as Monitor but uses MIDEventList, whose protocol may vary from MIDI 1.0. - */ - // OSStatus (*MonitorEvents)(MIDIDriverRef __nonnull self, MIDIEndpointRef dest, const MIDIEventList *pktlist); - unsafe delegate* unmanaged MonitorEvents; +#pragma warning disable CS0169 // The field 'MidiDriverInterface._reserved' is never used + IntPtr _reserved; #pragma warning restore CS0169 -#pragma warning restore CS0649 + 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/MidiServices.cs b/src/CoreMidi/MidiServices.cs index 404942734593..d941887b7487 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -38,9 +38,16 @@ #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; @@ -2070,8 +2077,11 @@ 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 (IntPtr /* MidiDriverRef _nullable */ owner, IntPtr /* CFStringRef */ name, IntPtr /* CFStringRef */ manufacturer, IntPtr /* CFStringRef */ model, MidiDeviceRef* outDevice); + 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. @@ -2080,6 +2090,9 @@ public MidiError Remove (MidiEntity entity) /// 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); @@ -2088,16 +2101,15 @@ public MidiError Remove (MidiEntity entity) using var modelPtr = new TransientCFString (model); unsafe { - var driverInterface = default (MidiDriverInterface); - if (owner is not null) - driverInterface = owner.DriverInterface; - MidiDriverInterface* driverInterfacePtr1 = &driverInterface; - var driverInterfacePtr2 = default (IntPtr); + MidiDriverInterface* driverInterfacePtr = owner is null ? null : owner.DriverInterface; + MidiDriverInterface** driverPtr = null; if (owner is not null) - driverInterfacePtr2 = (IntPtr) (&driverInterfacePtr1); - status = (MidiError) MIDIDeviceCreate (driverInterfacePtr2, namePtr, manufacturerPtr, modelPtr, &handle); + driverPtr = &driverInterfacePtr; + status = (MidiError) MIDIDeviceCreate (driverPtr, namePtr, manufacturerPtr, modelPtr, &handle); } + GC.KeepAlive (owner); + if (handle == MidiObject.InvalidRef) return null; return new MidiDevice (handle); From b38aef8a57bd5e984d0afea19fb8c4025509564b Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Wed, 4 Jun 2025 17:02:23 +0000 Subject: [PATCH 19/19] Auto-format source code --- src/CoreMidi/MidiDriverInterface.cs | 12 ++--- src/CoreMidi/MidiEventList.cs | 4 +- src/CoreMidi/MidiServices.cs | 36 +++++++-------- src/CoreMidi/MidiStructs.cs | 70 ++++++++++++++--------------- 4 files changed, 60 insertions(+), 62 deletions(-) diff --git a/src/CoreMidi/MidiDriverInterface.cs b/src/CoreMidi/MidiDriverInterface.cs index be4f49148e3b..2fc4784151e7 100644 --- a/src/CoreMidi/MidiDriverInterface.cs +++ b/src/CoreMidi/MidiDriverInterface.cs @@ -52,7 +52,7 @@ unsafe protected MidiDriver () unsafe MidiDriverInterface* CreateDriver () { - var iface = (MidiDriverInterface *) Marshal.AllocHGlobal (sizeof (MidiDriverInterface)); + var iface = (MidiDriverInterface*) Marshal.AllocHGlobal (sizeof (MidiDriverInterface)); iface->QueryInterface = &QueryInterface; iface->AddRef = &AddRef; iface->Release = &Release; @@ -77,7 +77,7 @@ unsafe protected MidiDriver () } [UnmanagedCallersOnly] - unsafe static HRESULT QueryInterface (MidiDriverInterface *self, CFUuidBytes iid, void * ppv) + unsafe static HRESULT QueryInterface (MidiDriverInterface* self, CFUuidBytes iid, void* ppv) { var driver = self->GetObject (); Console.WriteLine ($"MidiDriver.QueryInterface ({(IntPtr) self}, {iid}, {(IntPtr) ppv}) => {driver}"); @@ -93,7 +93,7 @@ internal virtual HRESULT QueryInterface (CFUuidBytes iid, IntPtr ppv) static List strongReferences = new List (); [UnmanagedCallersOnly] - unsafe static uint AddRef (MidiDriverInterface *self) + unsafe static uint AddRef (MidiDriverInterface* self) { var driver = self->GetObject (); Console.WriteLine ($"MidiDriver.AddRef ({(IntPtr) self}) => {driver}"); @@ -114,7 +114,7 @@ unsafe internal virtual uint AddRef () } [UnmanagedCallersOnly] - unsafe static uint Release (MidiDriverInterface *self) + unsafe static uint Release (MidiDriverInterface* self) { var driver = self->GetObject (); Console.WriteLine ($"MidiDriver.Release ({(IntPtr) self}) => {driver}"); @@ -141,7 +141,7 @@ unsafe internal virtual uint Release () } [UnmanagedCallersOnly] - unsafe static OSStatus FindDevices (MidiDriverInterface *self, MidiDeviceListRef devList) + unsafe static OSStatus FindDevices (MidiDriverInterface* self, MidiDeviceListRef devList) { var driver = self->GetObject (); Console.WriteLine ($"MidiDriver.FindDevices ({(IntPtr) self}, {devList}) => {driver}"); @@ -341,7 +341,7 @@ 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 QueryInterface; internal unsafe delegate* unmanaged AddRef; internal unsafe delegate* unmanaged Release; internal unsafe delegate* unmanaged FindDevices; diff --git a/src/CoreMidi/MidiEventList.cs b/src/CoreMidi/MidiEventList.cs index fc6c0ea608e3..59c889c230a1 100644 --- a/src/CoreMidi/MidiEventList.cs +++ b/src/CoreMidi/MidiEventList.cs @@ -88,7 +88,7 @@ public MidiEventList (MidiProtocolId protocol, int size) midiDataSize = size; owns = true; unsafe { - midiDataPointer = (MIDIEventList *) Marshal.AllocHGlobal (midiDataSize); + midiDataPointer = (MIDIEventList*) Marshal.AllocHGlobal (midiDataSize); currentPacket = MIDIEventListInit (midiDataPointer, protocol); if (currentPacket is null) throw new Exception ($"Failed to create midi event list."); @@ -101,7 +101,7 @@ public MidiEventList (MidiProtocolId protocol, int size) public MidiEventList (IntPtr eventListPointer) { unsafe { - midiDataPointer = (MIDIEventList *) eventListPointer; + midiDataPointer = (MIDIEventList*) eventListPointer; owns = false; midiDataSize = -1; } diff --git a/src/CoreMidi/MidiServices.cs b/src/CoreMidi/MidiServices.cs index d941887b7487..7ae00370c235 100644 --- a/src/CoreMidi/MidiServices.cs +++ b/src/CoreMidi/MidiServices.cs @@ -770,7 +770,7 @@ public override string ToString () /// 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) + public unsafe MidiEndpoint? CreateVirtualDestination (string name, MidiProtocolId protocol, delegate* unmanaged readBlock, out MidiError status) { using var namePtr = new TransientCFString (name); var handle = default (MidiEndpointRef); @@ -787,7 +787,7 @@ public override string ToString () [SupportedOSPlatform ("macos")] [UnsupportedOSPlatform ("tvos")] [DllImport (Constants.CoreMidiLibrary)] - unsafe extern static OSStatus MIDIDestinationCreateWithProtocol (MidiClientRef client, IntPtr /* CFStringRef */ name, MidiProtocolId protocol, MidiEndpointRef* outSrc, delegate* unmanaged readBlock); + 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. @@ -819,7 +819,7 @@ unsafe extern static OSStatus MIDIInputPortCreateWithProtocol ( IntPtr /* CFStringRef */ name, MidiProtocolId protocol, MidiPortRef* outPort, - delegate* unmanaged receiveBlock); + delegate* unmanaged receiveBlock); /// Create a input port for this client. /// The name for the port. @@ -832,7 +832,7 @@ unsafe extern static OSStatus MIDIInputPortCreateWithProtocol ( [SupportedOSPlatform ("maccatalyst")] [SupportedOSPlatform ("macos")] [UnsupportedOSPlatform ("tvos")] - public unsafe MidiPort? CreateInputPort (string name, MidiProtocolId protocol, delegate* unmanaged readBlock, out MidiError status) + public unsafe MidiPort? CreateInputPort (string name, MidiProtocolId protocol, delegate* unmanaged readBlock, out MidiError status) { using var namePtr = new TransientCFString (name); var handle = default (MidiEndpointRef); @@ -3317,7 +3317,7 @@ public MidiError SetRefCons (IntPtr ref1, IntPtr ref2) /// 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) + public unsafe Task SendSysexAsync (byte [] data, CancellationToken? cancellationToken = null) { if (data is null) ThrowHelper.ThrowArgumentNullException (nameof (data)); @@ -3348,7 +3348,7 @@ public unsafe Task SendSysexAsync (byte[] data, CancellationToken? ca [SupportedOSPlatform ("maccatalyst17.0")] [SupportedOSPlatform ("macos14.0")] [UnsupportedOSPlatform ("tvos")] - public unsafe Task SendSysexUmpAsync (uint[] data, CancellationToken? cancellationToken = null) + public unsafe Task SendSysexUmpAsync (uint [] data, CancellationToken? cancellationToken = null) { if (data is null) ThrowHelper.ThrowArgumentNullException (nameof (data)); @@ -3379,7 +3379,7 @@ public unsafe Task SendSysexUmpAsync (uint[] data, CancellationToken? [SupportedOSPlatform ("maccatalyst17.0")] [SupportedOSPlatform ("macos14.0")] [UnsupportedOSPlatform ("tvos")] - public unsafe Task SendSysexUmp8Async (uint[] data, CancellationToken? cancellationToken = null) + public unsafe Task SendSysexUmp8Async (uint [] data, CancellationToken? cancellationToken = null) { if (data is null) ThrowHelper.ThrowArgumentNullException (nameof (data)); @@ -3398,14 +3398,14 @@ public unsafe Task SendSysexUmp8Async (uint[] data, CancellationToken class SysexRequest : IDisposable { IntPtr structPointer; MidiEndpoint endpoint; - byte[]? byteData; - uint[]? uintData; + byte []? byteData; + uint []? uintData; GCHandle dataHandle; GCHandle thisHandle; TaskCompletionSource onCompletion; CancellationTokenRegistration? cancellationTokenRegistration; - public SysexRequest (MidiEndpoint endpoint, byte[] data, TaskCompletionSource onCompletion) + public SysexRequest (MidiEndpoint endpoint, byte [] data, TaskCompletionSource onCompletion) { this.endpoint = endpoint; this.byteData = data; @@ -3418,7 +3418,7 @@ public SysexRequest (MidiEndpoint endpoint, byte[] data, TaskCompletionSource onCompletion) + public SysexRequest (MidiEndpoint endpoint, uint [] data, TaskCompletionSource onCompletion) { this.endpoint = endpoint; this.uintData = data; @@ -3436,7 +3436,7 @@ public SysexRequest (MidiEndpoint endpoint, uint[] data, TaskCompletionSourceDestination = endpoint.GetCheckedHandle (); rv->Data = dataHandle.AddrOfPinnedObject (); @@ -3454,7 +3454,7 @@ public SysexRequest (MidiEndpoint endpoint, uint[] data, TaskCompletionSourceDestination = endpoint.GetCheckedHandle (); rv->Words = dataHandle.AddrOfPinnedObject (); @@ -3481,7 +3481,7 @@ unsafe static void SysexCompletion (MidiSysexSendRequest* request) } [UnmanagedCallersOnly] - unsafe static void UmpSysexCompletion (MidiSysexSendRequestUmp * request) + unsafe static void UmpSysexCompletion (MidiSysexSendRequestUmp* request) { var obj = (SysexRequest?) GCHandle.FromIntPtr (request->Context).Target; obj?.OnCompleted (); @@ -3489,16 +3489,16 @@ unsafe static void UmpSysexCompletion (MidiSysexSendRequestUmp * request) unsafe void SysexCancellationRequest () { - var rv = (MidiSysexSendRequest *) structPointer; - if (rv == null) + var rv = (MidiSysexSendRequest*) structPointer; + if (rv is null) return; rv->Complete = true; } unsafe void UmpSysexCancellationRequest () { - var rv = (MidiSysexSendRequestUmp *) structPointer; - if (rv == null) + var rv = (MidiSysexSendRequestUmp*) structPointer; + if (rv is null) return; rv->Complete = true; } diff --git a/src/CoreMidi/MidiStructs.cs b/src/CoreMidi/MidiStructs.cs index 809a311126d0..befd002ef1dd 100644 --- a/src/CoreMidi/MidiStructs.cs +++ b/src/CoreMidi/MidiStructs.cs @@ -144,16 +144,15 @@ public unsafe MidiCIProfileIdManufacturerSpecific ManufacturerSpecific { /// A struct that represents a request to transmit a single system-exclusive event. [NativeName ("MIDISysexSendRequest")] - struct MidiSysexSendRequest - { - MidiEndpointRef destination; + struct MidiSysexSendRequest { + MidiEndpointRef destination; IntPtr /* const Byte * */ data; - uint bytesToSend; + uint bytesToSend; byte /* Boolean */ complete; #pragma warning disable CS0169 // The field '...' is never used - byte reserved1; - byte reserved2; - byte reserved3; + byte reserved1; + byte reserved2; + byte reserved3; #pragma warning restore CS0169 unsafe delegate* unmanaged /* MIDICompletionProc */ completionProc; IntPtr /* void * __nullable */ completionRefCon; @@ -199,38 +198,37 @@ public IntPtr Context { } -/*! - @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. -*/ + /*! + @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 - { + struct MidiSysexSendRequestUmp { MidiEndpointRef destination; IntPtr /* UInt32* */ words; uint /* UInt32 */ wordsToSend;