Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CoreMidi] Implement some of the missing CoreMIDI APIs. #22459

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions src/CoreMidi/MidiEventList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
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 {
/// <summary>This class represents the Objective-C struct MIDIEventList, which is a list of <see cref="MidiEventPacket" /> packets.</summary>
[SupportedOSPlatform ("ios14.0")]
[SupportedOSPlatform ("tvos14.0")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst")]
// [NativeName ("MIDIEventList")]
public class MidiEventList : IEnumerable<MidiEventPacket> {
/* 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
}

byte [] midiData;
unsafe MidiEventPacket* currentPacket;

const int MinimumSize = 276; /* 4 + 4 + sizeof (MidiEventPacket) */

/// <summary>The <see cref="MidiProtocolId" /> protocol for the packets in this list of packets.</summary>
/// <returns>The <see cref="MidiProtocolId" /> protocol for the packets in this list of packets.</returns>
public unsafe MidiProtocolId Protocol {
get {
fixed (byte* midiDataPtr = midiData)
return ((MIDIEventList*) midiDataPtr)->protocol;
}
}

/// <summary>The number of packets in this list.</summary>
/// <returns>The number of packets in this list.</returns>
public unsafe uint PacketCount {
get {
fixed (byte* midiDataPtr = midiData)
return ((MIDIEventList*) midiDataPtr)->numPackets;
}
}

internal byte [] MidiData { get => midiData; }

/// <summary>Create a new <see cref="MidiEventList" /> list with the minimum size.</summary>
/// <param name="protocol">The protocol for the packets in the created list.</param>
/// <returns>A newly created <see cref="MidiEventList" />, or an exception in case of failure.</returns>
public MidiEventList (MidiProtocolId protocol)
: this (protocol, MinimumSize)
{
}

/// <summary>Create a new <see cref="MidiEventList" /> for the specified protocol and size.</summary>
/// <param name="protocol">The protocol for the event list.</param>
/// <param name="size">The size, in number of bytes, of the event list. Minimum size is 276 bytes.</param>
/// <returns>A newly created <see cref="MidiEventList" />, or an exception in case of failure.</returns>
public MidiEventList (MidiProtocolId protocol, int size)
{
if (size < MinimumSize)
throw new ArgumentOutOfRangeException ($"{nameof (size)} must be at least {MinimumSize}.");

midiData = new byte [size];
unsafe {
fixed (byte* midiDataPtr = midiData)
currentPacket = MIDIEventListInit (midiDataPtr, protocol);
if (currentPacket is null)
throw new Exception ($"Failed to create midi event list.");
}
}

#if !__TVOS__
/// <summary>Send the packets in this list to the specified <paramref name="destination" />.</summary>
/// <param name="port">The port through which the packets are sent.</param>
/// <param name="destination">The destination where the packets are sent.</param>
/// <returns>A non-zero error code in case of failure, otherwise zero (which indicates success).</returns>
[SupportedOSPlatform ("ios14.0")]
[UnsupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst")]
public unsafe int /* OSStatus */ Send (MidiPort port, MidiEndpoint destination)
{
fixed (byte* midiDataPtr = midiData)
return MIDISendEventList (port.Handle, destination.Handle, midiDataPtr);
}

/// <summary>Distribute the packets from the specified <paramref name="source" />.</summary>
/// <param name="source">The endpoint where the packates come from.</param>
/// <returns>A non-zero error code in case of failure, otherwise zero (which indicates success).</returns>
[SupportedOSPlatform ("ios14.0")]
[UnsupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst")]
public unsafe int /* OSStatus */ Receive (MidiEndpoint source)
{
fixed (byte* midiDataPtr = midiData)
return MIDIReceivedEventList (source.Handle, midiDataPtr);
}
#endif

/// <summary>Add a new <see cref="MidiEventPacket" /> to this lis.</summary>
/// <param name="time">The timestamp for the new packet.</param>
/// <param name="words">The data for the midi event to add.</param>
/// <returns>True if successful, otherwise false (which typically means there's not enough space for the new packet).</returns>
public unsafe bool Add (ulong time, uint [] words)
{
fixed (byte* midiDataPtr = midiData) {
fixed (uint* wordsPtr = words) {
var rv = MIDIEventListAdd (midiDataPtr, (ulong) midiData.Length, 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 (byte* /* MIDIEventList * */ evtlist, MidiProtocolId /* MIDIProtocolID */ protocol);

[DllImport (Constants.CoreMidiLibrary)]
unsafe static extern MidiEventPacket* MIDIEventListAdd (
byte* /* 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, byte* /* const MIDIEventList */ evtList);

[SupportedOSPlatform ("ios14.0")]
[UnsupportedOSPlatform ("tvos")]
[SupportedOSPlatform ("macos")]
[SupportedOSPlatform ("maccatalyst")]
[DllImport (Constants.CoreMidiLibrary)]
unsafe static extern int /* OSStatus */ MIDIReceivedEventList (MidiEndpointRef src, byte* /* const MIDIEventList * */ evtlist);
#endif // !__TVOS__

IEnumerator<MidiEventPacket> IEnumerable<MidiEventPacket>.GetEnumerator ()
{
MidiEventPacket packetToYield;
IntPtr packetPtr;

if (PacketCount == 0)
yield break;

unsafe {
fixed (byte* midiDataPtr = midiData) {
MIDIEventList* list = (MIDIEventList*) midiDataPtr;
MidiEventPacket* packet = &list->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<MidiEventPacket>) this).GetEnumerator ();
}

/// <summary>Iterate over each <see cref="MidiEventPacket" /> in this list without allocating or copying memory.</summary>
/// <param name="callback">The function to call for each packet.</param>
public unsafe void Iterate (MidiEventListIterator callback)
{
if (PacketCount == 0)
return;

fixed (byte* midiDataPtr = midiData) {
MIDIEventList* list = (MIDIEventList*) midiDataPtr;
MidiEventPacket* packet = &list->packet;
callback (ref Unsafe.AsRef<MidiEventPacket> (packet));
for (var i = 1; i < PacketCount; i++) {
uint* wordPointer = &packet->word_00;
packet = (MidiEventPacket*) wordPointer [packet->WordCount];
callback (ref Unsafe.AsRef<MidiEventPacket> (packet));
}
}
}
}

/// <summary>The delegate type used by <see cref="MidiEventList.Iterate" />.</summary>
/// <param name="packet">The current packet found when iterating.</param>
public delegate void MidiEventListIterator (ref MidiEventPacket packet);
}
170 changes: 170 additions & 0 deletions src/CoreMidi/MidiEventPacket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
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 {
/// <summary>This class represents the Objective-C struct MIDIEventPacket, which is a variable-sized struct.</summary>
[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;

/// <summary>The timestamp for this packet.</summary>
/// <returns>The timestamp for this packet.</returns>
public ulong Timestamp {
get => timeStamp;
set => timeStamp = value;
}

/// <summary>The number of 32-bi Midi words in this packet.</summary>
/// <returns>The number of 32-bi Midi words in this packet.</returns>
public uint WordCount {
get => wordCount;
set {
if (value > 64)
throw new ArgumentOutOfRangeException ($"WordCount can't be higher than 64.");
wordCount = value;
}
}

/// <summary>All the 32-bit Midi words in this packet.</summary>
/// <returns>All the 32-bit Midi words in this packet.</returns>
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));
}
}
}
}
}

/// <summary>An indexer for the 32-bit Midi words in this packet.</summary>
/// <param name="index">The index of the 32-bit Midi word to set or get.</param>
/// <returns>The 32-bit Midi words for specified index.</returns>
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;
}
}
}
}
}
Loading