Skip to content

Commit

Permalink
feat(MIDI): Support MidiInPort on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund committed May 24, 2020
1 parent e2556f8 commit b26e069
Show file tree
Hide file tree
Showing 21 changed files with 169 additions and 31 deletions.
53 changes: 44 additions & 9 deletions src/Uno.UWP/Devices/Midi/Internal/MidiMessageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,58 @@ public IMidiMessage Parse(byte[] bytes, TimeSpan timestamp)
throw new ArgumentException(nameof(bytes), "MIDI message cannot be empty");
}

// Parsing logic based on
// https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message

// Try to parse channel voice messages first
// These start with a unique combination of four bits and the remaining
// four represent the channel.

var upperBits = (byte)(bytes[0] & 0b_1111_0000);
var lowerBits = (byte)(bytes[0] & 0b_0000_1111);
if (upperBits == (byte)MidiMessageType.NoteOff)
switch (upperBits)
{
return new MidiNoteOffMessage(lowerBits, bytes[1], bytes[2]);
case (byte)MidiMessageType.NoteOff:
return new MidiNoteOffMessage(bytes, timestamp);
case (byte)MidiMessageType.NoteOn:
return new MidiNoteOnMessage(bytes, timestamp);
case (byte)MidiMessageType.PolyphonicKeyPressure:
return new MidiPolyphonicKeyPressureMessage(bytes, timestamp);
case (byte)MidiMessageType.ControlChange:
return new MidiControlChangeMessage(bytes, timestamp);
case (byte)MidiMessageType.ProgramChange:
return new MidiProgramChangeMessage(bytes, timestamp);
case (byte)MidiMessageType.ChannelPressure:
return new MidiChannelPressureMessage(bytes, timestamp);
case (byte)MidiMessageType.PitchBendChange:
return new MidiPitchBendChangeMessage(bytes, timestamp);
}

}

private static bool MatchesMessageType(byte firstByte, MidiMessageType messageType)
{
var encodedMessageType = (byte)messageType;
return (firstByte & encodedMessageType) == encodedMessageType;
// System common messages
switch (bytes[0])
{
case (byte)MidiMessageType.MidiTimeCode:
return new MidiTimeCodeMessage(bytes, timestamp);
case (byte)MidiMessageType.SongPositionPointer:
return new MidiSongPositionPointerMessage(bytes, timestamp);
case (byte)MidiMessageType.SongSelect:
return new MidiSongSelectMessage(bytes, timestamp);
case (byte)MidiMessageType.TuneRequest:
return new MidiTuneRequestMessage(bytes, timestamp);
case (byte)MidiMessageType.TimingClock:
return new MidiTimingClockMessage(bytes, timestamp);
case (byte)MidiMessageType.Start:
return new MidiStartMessage(bytes, timestamp);
case (byte)MidiMessageType.Continue:
return new MidiContinueMessage(bytes, timestamp);
case (byte)MidiMessageType.Stop:
return new MidiStopMessage(bytes, timestamp);
case (byte)MidiMessageType.ActiveSensing:
return new MidiActiveSensingMessage(bytes, timestamp);
case (byte)MidiMessageType.SystemReset:
return new MidiSystemResetMessage(bytes, timestamp);
default:
return new MidiSystemExclusiveMessage(bytes, timestamp);
}
}
}
}
10 changes: 10 additions & 0 deletions src/Uno.UWP/Devices/Midi/MidiActiveSensingMessage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Uno.Devices.Midi.Internal;
using Windows.Storage.Streams;

namespace Windows.Devices.Midi
Expand All @@ -16,6 +17,15 @@ public MidiActiveSensingMessage()
RawData = new InMemoryBuffer(new byte[] { (byte)Type });
}

internal MidiActiveSensingMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 1, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);

RawData = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
/// Gets the type of this MIDI message.
/// </summary>
Expand Down
5 changes: 3 additions & 2 deletions src/Uno.UWP/Devices/Midi/MidiChannelPressureMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ public MidiChannelPressureMessage(byte channel, byte pressure)
});
}

internal MidiChannelPressureMessage(byte[] rawData)
internal MidiChannelPressureMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 2, MidiMessageType.ChannelPressure);
MidiMessageValidators.VerifyMessageType(rawData[0], MidiMessageType.ChannelPressure);
MidiMessageValidators.VerifyRange(MidiHelpers.GetChannel(rawData[0]), MidiMessageParameter.Channel);
MidiMessageValidators.VerifyRange(rawData[1], MidiMessageParameter.Pressure);

_buffer = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
Expand All @@ -62,6 +63,6 @@ internal MidiChannelPressureMessage(byte[] rawData)
/// Gets the duration from when the MidiInPort was created to the time the message was received.
/// For messages being sent to a MidiOutPort, this value has no meaning.
/// </summary>
public TimeSpan Timestamp { get; internal set; } = TimeSpan.Zero;
public TimeSpan Timestamp { get; } = TimeSpan.Zero;
}
}
10 changes: 10 additions & 0 deletions src/Uno.UWP/Devices/Midi/MidiContinueMessage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Uno.Devices.Midi.Internal;
using Windows.Storage.Streams;

namespace Windows.Devices.Midi
Expand All @@ -16,6 +17,15 @@ public MidiContinueMessage()
RawData = new InMemoryBuffer(new byte[] { (byte)Type });
}

internal MidiContinueMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 1, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);

RawData = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
/// Gets the type of this MIDI message.
/// </summary>
Expand Down
5 changes: 3 additions & 2 deletions src/Uno.UWP/Devices/Midi/MidiControlChangeMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public MidiControlChangeMessage(byte channel, byte controller, byte controlValue
});
}

internal MidiControlChangeMessage(byte[] rawData)
internal MidiControlChangeMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 3, MidiMessageType.ControlChange);
MidiMessageValidators.VerifyMessageType(rawData[0], MidiMessageType.ControlChange);
Expand All @@ -40,6 +40,7 @@ internal MidiControlChangeMessage(byte[] rawData)
MidiMessageValidators.VerifyRange(rawData[2], MidiMessageParameter.ControlValue);

_buffer = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
Expand Down Expand Up @@ -71,6 +72,6 @@ internal MidiControlChangeMessage(byte[] rawData)
/// Gets the duration from when the MidiInPort was created to the time the message was received.
/// For messages being sent to a MidiOutPort, this value has no meaning.
/// </summary>
public TimeSpan Timestamp { get; internal set; } = TimeSpan.Zero;
public TimeSpan Timestamp { get; } = TimeSpan.Zero;
}
}
9 changes: 7 additions & 2 deletions src/Uno.UWP/Devices/Midi/MidiInPort.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Uno.Devices.Enumeration.Internal;
using Uno.Devices.Midi.Internal;
using Windows.Foundation;

namespace Windows.Devices.Midi
Expand All @@ -15,6 +16,8 @@ public sealed partial class MidiInPort : IDisposable

private readonly object _syncLock = new object();

private MidiMessageParser _parser = new MidiMessageParser();

/// <summary>
/// Gets the id of the device that was used to initialize the MidiInPort.
/// </summary>
Expand Down Expand Up @@ -70,8 +73,10 @@ private void OnMessageReceived(byte[] message, TimeSpan timestamp)
return;
}

//read message type

// parse message
var parsedMessage = _parser.Parse(message, timestamp);
var eventArgs = new MidiMessageReceivedEventArgs(parsedMessage);
MessageReceived?.Invoke(this, eventArgs);
}
}
}
8 changes: 7 additions & 1 deletion src/Uno.UWP/Devices/Midi/MidiInPort.iOSmacOS.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
Expand Down Expand Up @@ -32,7 +33,12 @@ internal void Open()

private void NativePortMessageReceived(object sender, MidiPacketsEventArgs e)
{
throw new NotImplementedException();
foreach (var packet in e.Packets)
{
var bytes = new byte[packet.Length];
Marshal.Copy(packet.Bytes, bytes, 0, packet.Length);
OnMessageReceived(bytes, TimeSpan.FromMilliseconds(packet.TimeStamp));
}
}

public void Dispose()
Expand Down
5 changes: 3 additions & 2 deletions src/Uno.UWP/Devices/Midi/MidiNoteOffMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public MidiNoteOffMessage(byte channel, byte note, byte velocity)
});
}

internal MidiNoteOffMessage(byte[] rawData)
internal MidiNoteOffMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 3, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);
Expand All @@ -39,6 +39,7 @@ internal MidiNoteOffMessage(byte[] rawData)
MidiMessageValidators.VerifyRange(rawData[2], MidiMessageParameter.Velocity);

_buffer = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
Expand Down Expand Up @@ -70,6 +71,6 @@ internal MidiNoteOffMessage(byte[] rawData)
/// Gets the duration from when the MidiInPort was created to the time the message was received.
/// For messages being sent to a MidiOutPort, this value has no meaning.
/// </summary>
public TimeSpan Timestamp { get; internal set; } = TimeSpan.Zero;
public TimeSpan Timestamp { get; } = TimeSpan.Zero;
}
}
3 changes: 2 additions & 1 deletion src/Uno.UWP/Devices/Midi/MidiNoteOnMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public MidiNoteOnMessage(byte channel, byte note, byte velocity)
});
}

internal MidiNoteOnMessage(byte[] rawData)
internal MidiNoteOnMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 3, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);
Expand All @@ -39,6 +39,7 @@ internal MidiNoteOnMessage(byte[] rawData)
MidiMessageValidators.VerifyRange(rawData[2], MidiMessageParameter.Velocity);

_buffer = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
Expand Down
5 changes: 3 additions & 2 deletions src/Uno.UWP/Devices/Midi/MidiPitchBendChangeMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ public MidiPitchBendChangeMessage(byte channel, ushort bend)
});
}

internal MidiPitchBendChangeMessage(byte[] rawData)
internal MidiPitchBendChangeMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 3, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);
MidiMessageValidators.VerifyRange(MidiHelpers.GetChannel(rawData[0]), MidiMessageParameter.Channel);
MidiMessageValidators.VerifyRange(MidiHelpers.GetBend(rawData[1], rawData[2]), MidiMessageParameter.Bend);

_buffer = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
Expand All @@ -63,6 +64,6 @@ internal MidiPitchBendChangeMessage(byte[] rawData)
/// Gets the duration from when the MidiInPort was created to the time the message was received.
/// For messages being sent to a MidiOutPort, this value has no meaning.
/// </summary>
public TimeSpan Timestamp { get; internal set; } = TimeSpan.Zero;
public TimeSpan Timestamp { get; } = TimeSpan.Zero;
}
}
5 changes: 3 additions & 2 deletions src/Uno.UWP/Devices/Midi/MidiPolyphonicKeyPressureMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public MidiPolyphonicKeyPressureMessage(byte channel, byte note, byte pressure)
});
}

internal MidiPolyphonicKeyPressureMessage(byte[] rawData)
internal MidiPolyphonicKeyPressureMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 3, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);
Expand All @@ -40,6 +40,7 @@ internal MidiPolyphonicKeyPressureMessage(byte[] rawData)
MidiMessageValidators.VerifyRange(rawData[2], MidiMessageParameter.Pressure);

_buffer = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
Expand Down Expand Up @@ -71,6 +72,6 @@ internal MidiPolyphonicKeyPressureMessage(byte[] rawData)
/// Gets the duration from when the MidiInPort was created to the time the message was received.
/// For messages being sent to a MidiOutPort, this value has no meaning.
/// </summary>
public TimeSpan Timestamp { get; internal set; } = TimeSpan.Zero;
public TimeSpan Timestamp { get; } = TimeSpan.Zero;
}
}
5 changes: 3 additions & 2 deletions src/Uno.UWP/Devices/Midi/MidiProgramChangeMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ public MidiProgramChangeMessage(byte channel, byte program)
});
}

internal MidiProgramChangeMessage(byte[] rawData)
internal MidiProgramChangeMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 2, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);
MidiMessageValidators.VerifyRange(MidiHelpers.GetChannel(rawData[0]), MidiMessageParameter.Channel);
MidiMessageValidators.VerifyRange(rawData[1], MidiMessageParameter.Program);

_buffer = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
Expand All @@ -62,6 +63,6 @@ internal MidiProgramChangeMessage(byte[] rawData)
/// Gets the duration from when the MidiInPort was created to the time the message was received.
/// For messages being sent to a MidiOutPort, this value has no meaning.
/// </summary>
public TimeSpan Timestamp { get; internal set; } = TimeSpan.Zero;
public TimeSpan Timestamp { get; } = TimeSpan.Zero;
}
}
3 changes: 2 additions & 1 deletion src/Uno.UWP/Devices/Midi/MidiSongPositionPointerMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ public MidiSongPositionPointerMessage(ushort beats)
});
}

internal MidiSongPositionPointerMessage(byte[] rawData)
internal MidiSongPositionPointerMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 3, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);
MidiMessageValidators.VerifyRange(MidiHelpers.GetBeats(rawData[1], rawData[2]), MidiMessageParameter.Beats);

_buffer = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Uno.UWP/Devices/Midi/MidiSongSelectMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ public MidiSongSelectMessage(byte song)
});
}

internal MidiSongSelectMessage(byte[] rawData)
internal MidiSongSelectMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 2, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);
MidiMessageValidators.VerifyRange(rawData[1], MidiMessageParameter.Song);

_buffer = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Uno.UWP/Devices/Midi/MidiStartMessage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Uno.Devices.Midi.Internal;
using Windows.Storage.Streams;

namespace Windows.Devices.Midi
Expand All @@ -19,6 +20,15 @@ public MidiStartMessage()
});
}

internal MidiStartMessage(byte[] rawData, TimeSpan timestamp)
{
MidiMessageValidators.VerifyMessageLength(rawData, 1, Type);
MidiMessageValidators.VerifyMessageType(rawData[0], Type);

RawData = new InMemoryBuffer(rawData);
Timestamp = timestamp;
}

/// <summary>
/// Gets the type of this MIDI message.
/// </summary>
Expand Down
Loading

0 comments on commit b26e069

Please sign in to comment.