[CoreMidi] Add missing CoreMIDI bindings and MidiEventList support.#24738
[CoreMidi] Add missing CoreMIDI bindings and MidiEventList support.#24738rolfbjarne wants to merge 2 commits intomainfrom
Conversation
Bind the following missing CoreMIDI APIs: - MIDIDeviceCreate/MIDIDeviceDispose - MIDIExternalDeviceCreate - MIDISetupAddDevice/MIDISetupRemoveDevice - MIDISetupAddExternalDevice/MIDISetupRemoveExternalDevice - MIDIEntityAddOrRemoveEndpoints - MIDIDeviceRemoveEntity - MIDIEndPointGetRefCons/MIDIEndPointSetRefCons - MIDIDriverEnableMonitoring - MIDIGetDriverDeviceList/MIDIGetDriverIORunLoop - MIDISendSysex/MIDISendUMPSysex - MIDIDestinationCreateWithProtocol - MIDISourceCreateWithProtocol - MIDIInputPortCreateWithProtocol - MIDIClientCreateWithBlock - MIDIEventPacketSysexBytesForGroup - MIDI 2.0 structs (MIDI2DeviceManufacturer, MIDI2DeviceRevisionLevel, MIDICIProfileID, MIDISysexSendRequest, MIDISysexSendRequestUMP) - MidiDriver abstract class for implementing custom MIDI drivers Add MidiEventList and MidiEventPacket classes for MIDI 2.0 Universal MIDI Packet (UMP) support (MIDIEventList/MIDIEventPacket structs). Add comprehensive tests including a Happy Birthday melody test. There's a sample project in progress here: dotnet/macios-samples#10. Fixes #4452 Fixes #12489
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive CoreMIDI bindings for missing APIs and implements MIDI 2.0 Universal MIDI Packet (UMP) support, addressing issues #4452 and #12489. The changes enable developers to work with MIDI devices at a lower level, including custom driver implementation, and provide full support for the modern MIDI 2.0 protocol.
Changes:
- Added MidiEventList and MidiEventPacket classes for MIDI 2.0 UMP support, enabling Universal MIDI Packet handling
- Implemented missing CoreMIDI device/entity management APIs (Create, Dispose, AddOrRemoveEndpoints, etc.) and sysex sending functionality
- Introduced experimental MidiDriver abstract class for implementing custom MIDI drivers with full lifecycle management
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/xtro-sharpie/api-annotations-dotnet/*.ignore | Removed API ignore entries for newly bound CoreMIDI APIs across all platforms |
| tests/cecil-tests/Documentation.cs | Added FunctionPointerType handling to return empty doc IDs for unmanaged function pointers |
| tests/cecil-tests/Documentation.KnownFailures.txt | Removed known failures for newly documented CoreMIDI types and fixed function pointer doc IDs |
| tests/monotouch-test/CoreMidi/MidiEventPacketTest.cs | Comprehensive unit tests for MidiEventPacket struct including bounds checking and data manipulation |
| tests/monotouch-test/CoreMidi/MidiEventListTest.cs | Unit tests for MidiEventList covering construction, adding packets, iteration, and edge cases |
| tests/monotouch-test/CoreMidi/MidiEndpointTest.cs | Added tests for GetRefCons/SetRefCons endpoint reference management APIs |
| tests/monotouch-test/CoreMidi/MidiDeviceTest.cs | Tests for external device creation and MidiSetup add/remove operations |
| tests/monotouch-test/CoreMidi/MidiComprehensiveTest.cs | Extensive integration tests including a complete "Happy Birthday" melody demonstration |
| src/frameworks.sources | Registered new CoreMidi and CoreFoundation source files in the build system |
| src/coremidi.cs | Added MidiDriverProperty enum for driver-specific properties |
| src/CoreMidi/MidiThruConnectionParams.cs | Moved enum definitions outside TVOS conditional to fix tvOS build issues |
| src/CoreMidi/MidiStructs.cs | Implemented MIDI 2.0 structs (Midi2DeviceManufacturer, Midi2DeviceRevisionLevel, MidiCIProfileId variants, MidiSysexSendRequest) |
| src/CoreMidi/MidiServices.cs | Added device/entity creation, external device support, sysex async sending, and protocol-aware port/endpoint creation methods |
| src/CoreMidi/MidiEventPacket.cs | Implemented MidiEventPacket struct with 64-word capacity, indexer, and GetSysexBytes method |
| src/CoreMidi/MidiEventList.cs | Implemented MidiEventList as IDisposable wrapper with Add, Send, Receive, and iteration support |
| src/CoreMidi/MidiDriverInterface.cs | Created experimental MidiDriver abstract class with COM-style interface for custom driver implementation |
| src/CoreMidi/MidiBluetoothDriver.cs | Removed TVOS conditional and added XML documentation for Bluetooth driver methods |
| src/CoreFoundation/CFUuidBytes.cs | Added CFUuidBytes struct for CoreMIDI driver interface QueryInterface method |
| docs/preview-apis.md | Documented MidiDriver as experimental (APL0004) until .NET 12 |
| public void CtorTest_Size () | ||
| { | ||
| Exception ex; | ||
|
|
||
| Assert.Multiple (() => { | ||
| ex = Assert.Throws<ArgumentOutOfRangeException> (() => 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<ArgumentOutOfRangeException> (() => 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<ArgumentOutOfRangeException> (() => 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<ArgumentOutOfRangeException> (() => 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"); | ||
| }); |
There was a problem hiding this comment.
The MidiEventList instances created in these tests are not being disposed. Since MidiEventList allocates unmanaged memory when owns=true, these should be wrapped in using statements to ensure proper cleanup. For example: using var obj = new MidiEventList (MidiProtocolId.Protocol_1_0, 276);
| 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"); | ||
| }); | ||
| } |
There was a problem hiding this comment.
The MidiEventList instance created in this test is not being disposed. Since MidiEventList allocates unmanaged memory when owns=true, this should be wrapped in a using statement to ensure proper cleanup. For example: using var obj = new MidiEventList (MidiProtocolId.Protocol_2_0);
| [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"); | ||
| }); | ||
| } |
There was a problem hiding this comment.
The MidiEventList instance created in this test is not being disposed. Since MidiEventList allocates unmanaged memory when owns=true, this should be wrapped in a using statement to ensure proper cleanup.
| [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"); | ||
| }); | ||
| } |
There was a problem hiding this comment.
The MidiEventList instance created in this test is not being disposed. Since MidiEventList allocates unmanaged memory when owns=true, this should be wrapped in a using statement to ensure proper cleanup.
| [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"); | ||
| }); | ||
| } |
There was a problem hiding this comment.
The MidiEventList instance created in this test is not being disposed. Since MidiEventList allocates unmanaged memory when owns=true, this should be wrapped in a using statement to ensure proper cleanup.
| [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<MidiEventPacket> (); | ||
| 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"); | ||
| }); | ||
| } |
There was a problem hiding this comment.
The MidiEventList instance created in this test is not being disposed. Since MidiEventList allocates unmanaged memory when owns=true, this should be wrapped in a using statement to ensure proper cleanup.
| } | ||
| #endif | ||
|
|
||
| /// <summary>Add a new <see cref="MidiEventPacket" /> to this lis.</summary> |
There was a problem hiding this comment.
There's a typo in the XML documentation: "lis" should be "list".
| /// <summary>Add a new <see cref="MidiEventPacket" /> to this lis.</summary> | |
| /// <summary>Add a new <see cref="MidiEventPacket" /> to this list.</summary> |
| } | ||
|
|
||
| /// <summary>Distribute the packets from the specified <paramref name="source" />.</summary> | ||
| /// <param name="source">The endpoint where the packates come from.</param> |
There was a problem hiding this comment.
There's a typo in the XML documentation: "packates" should be "packets".
| /// <param name="source">The endpoint where the packates come from.</param> | |
| /// <param name="source">The endpoint where the packets come from.</param> |
| return 0; | ||
| } | ||
|
|
||
| static List<MidiDriver> strongReferences = new List<MidiDriver> (); |
There was a problem hiding this comment.
The static List<MidiDriver> field should be initialized as readonly to prevent accidental reassignment. Consider: static readonly List<MidiDriver> strongReferences = new List<MidiDriver> ();
| static List<MidiDriver> strongReferences = new List<MidiDriver> (); | |
| static readonly List<MidiDriver> strongReferences = new List<MidiDriver> (); |
| 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"); | ||
| }); |
There was a problem hiding this comment.
The MidiEventList instances created in these tests are not being disposed. Since MidiEventList allocates unmanaged memory when owns=true, these should be wrapped in using statements to ensure proper cleanup. For example: using var obj = new MidiEventList (MidiProtocolId.Protocol_1_0);
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
✅ [CI Build #9db33b7] Build passed (Build packages) ✅Pipeline on Agent |
✅ [PR Build #9db33b7] Build passed (Detect API changes) ✅Pipeline on Agent |
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ [CI Build #9db33b7] Build passed (Build macOS tests) ✅Pipeline on Agent |
🔥 [CI Build #9db33b7] Test results 🔥Test results❌ Tests failed on VSTS: test results 0 tests crashed, 4 tests failed, 152 tests passed. Failures❌ linker tests [attempt 2]1 tests failed, 43 tests passed.Failed tests
Html Report (VSDrops) Download ❌ monotouch tests (iOS) [attempt 2]1 tests failed, 10 tests passed.Failed tests
Html Report (VSDrops) Download ❌ monotouch tests (MacCatalyst) [attempt 2]2 tests failed, 13 tests passed.Failed tests
Html Report (VSDrops) Download Successes✅ cecil: All 1 tests passed. Html Report (VSDrops) Download macOS tests✅ Tests on macOS Monterey (12): All 5 tests passed. [attempt 2] Html Report (VSDrops) Download Pipeline on Agent |
Bind the following missing CoreMIDI APIs:
MIDICIProfileID, MIDISysexSendRequest, MIDISysexSendRequestUMP)
Add MidiEventList and MidiEventPacket classes for MIDI 2.0 Universal MIDI
Packet (UMP) support (MIDIEventList/MIDIEventPacket structs).
Add comprehensive tests including a Happy Birthday melody test.
There's a sample project in progress here: dotnet/macios-samples#10.
Fixes #4452
Fixes #12489