From 08f9050bc585fa695ef7838f4fcc87d60f46c5a2 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:27:39 +0100 Subject: [PATCH 1/5] Add message buffer and program event, oldTypeName to binding gen, handle CC fine messages, and adjust some stuff --- .../BindingGenerator.cs | 4 + .../Components/Devices/MIDI_InputDevice.cs | 261 ++++++++++++++---- ProjectObsidian/Elements/MIDI.cs | 32 ++- ...vent.cs => MIDI_ChannelAftertouchEvent.cs} | 35 +-- ...t.cs => MIDI_PolyphonicAftertouchEvent.cs} | 33 +-- .../ProtoFlux/Devices/MIDI_ProgramEvent.cs | 83 ++++++ ProjectObsidian/Settings/MIDI_Settings.cs | 9 +- 7 files changed, 365 insertions(+), 92 deletions(-) rename ProjectObsidian/ProtoFlux/Devices/{MIDI_ChannelPressureEvent.cs => MIDI_ChannelAftertouchEvent.cs} (59%) rename ProjectObsidian/ProtoFlux/Devices/{MIDI_AftertouchEvent.cs => MIDI_PolyphonicAftertouchEvent.cs} (61%) create mode 100644 ProjectObsidian/ProtoFlux/Devices/MIDI_ProgramEvent.cs diff --git a/ProjectObsidian.SourceGenerators/BindingGenerator.cs b/ProjectObsidian.SourceGenerators/BindingGenerator.cs index 57822ea..e4b3d25 100644 --- a/ProjectObsidian.SourceGenerators/BindingGenerator.cs +++ b/ProjectObsidian.SourceGenerators/BindingGenerator.cs @@ -148,6 +148,7 @@ public string Result namespace {BindingPrefix}{_currentNameSpace}; {_genericTypesAttribute} +{_oldTypeNameAttribute} [Category(new string[] {{""ProtoFlux/Runtimes/Execution/Nodes/{_category}""}})] public partial class {_fullName} : global::FrooxEngine.ProtoFlux.Runtimes.Execution.{_baseType} {_constraints} {{ @@ -189,6 +190,7 @@ public override N Instantiate() private bool _isValidGenericTypeMethod; private string _constraints = ""; private string _genericTypesAttribute; + private string _oldTypeNameAttribute; private bool TypedFieldDetection(string type, string name, string targetTypeName, string declarationFormat, OrderedCount counter) { @@ -317,6 +319,8 @@ public override void VisitClassDeclaration(ClassDeclarationSyntax node) _genericTypesAttribute = node.AttributeLists.FirstOrDefault(attrList => attrList.Attributes.Any(attr => attr.Name.ToString() == "GenericTypes"))?.ToString(); + _oldTypeNameAttribute = node.AttributeLists.FirstOrDefault(attrList => attrList.Attributes.Any(attr => attr.Name.ToString() == "OldTypeName"))?.ToString(); + if (find?.ArgumentList is null) { base.VisitClassDeclaration(node); diff --git a/ProjectObsidian/Components/Devices/MIDI_InputDevice.cs b/ProjectObsidian/Components/Devices/MIDI_InputDevice.cs index 5bd246d..6bbcb9f 100644 --- a/ProjectObsidian/Components/Devices/MIDI_InputDevice.cs +++ b/ProjectObsidian/Components/Devices/MIDI_InputDevice.cs @@ -8,10 +8,12 @@ using CoreMidi; using Commons.Music.Midi; using Obsidian.Elements; +using Obsidian; +using System.Threading; -namespace Obsidian; +namespace Components.Devices.MIDI; -[Category(new string[] { "Obsidian/Devices" })] +[Category(new string[] { "Obsidian/Devices/MIDI" })] public class MIDI_InputDevice : Component { [NoContinuousParsing] @@ -23,6 +25,12 @@ public class MIDI_InputDevice : Component public readonly Sync _lastEvent; + public readonly Sync _lastEventType; + + public readonly Sync _lastSystemRealtimeEvent; + + public readonly Sync _lastSystemRealtimeEventType; + private bool _lastIsConnected; private IMidiInput _inputDevice; @@ -34,23 +42,29 @@ public class MIDI_InputDevice : Component public event MIDI_NoteEventHandler NoteOff; // Pressure for whole keyboard - public event MIDI_ChannelPressureEventHandler ChannelPressure; + public event MIDI_ChannelAftertouchEventHandler ChannelAftertouch; // Pressure for individual notes (polyphonic) - public event MIDI_AftertouchEventHandler Aftertouch; + public event MIDI_PolyphonicAftertouchEventHandler PolyphonicAftertouch; public event MIDI_CC_EventHandler Control; public event MIDI_PitchWheelEventHandler PitchWheel; - private const bool DEBUG = false; + public event MIDI_ProgramEventHandler Program; + + private const bool DEBUG = true; protected override void OnStart() { base.OnStart(); Settings.GetActiveSetting(); Settings.RegisterValueChanges(OnInputDeviceSettingsChanged); - RunInUpdates(7, Update); + //if (!Engine.Current.IsInitialized) + //{ + // Engine.Current.RunPostInit(() => RunInUpdates(30, Update)); + //} + RunInUpdates(30, Update); } private void OnInputDeviceSettingsChanged(MIDI_Settings setting) @@ -68,6 +82,21 @@ protected override void OnChanges() _lastEvent.WasChanged = false; return; } + if (_lastEventType.WasChanged) + { + _lastEventType.WasChanged = false; + return; + } + if (_lastSystemRealtimeEvent.WasChanged) + { + _lastSystemRealtimeEvent.WasChanged = false; + return; + } + if (_lastSystemRealtimeEventType.WasChanged) + { + _lastSystemRealtimeEventType.WasChanged = false; + return; + } if (IsConnected.WasChanged) { IsConnected.Value = _lastIsConnected; @@ -80,7 +109,7 @@ protected override void OnChanges() private async void ReleaseDeviceAsync() { UniLog.Log("Releasing device..."); - await _inputDevice.CloseAsync(); + await Task.WhenAny(_inputDevice.CloseAsync(), Task.Delay(10000)); UniLog.Log("Device released."); _inputDevice = null; } @@ -145,9 +174,9 @@ private void Update() return; } - if (_inputDevice != null - && (_inputDevice.Connection == MidiPortConnectionState.Open || _inputDevice.Connection == MidiPortConnectionState.Pending) - && _inputDevice.Details.Name == DeviceName.Value) + if (_inputDevice != null + && (_inputDevice.Connection == MidiPortConnectionState.Open || _inputDevice.Connection == MidiPortConnectionState.Pending) + && _inputDevice.Details.Name == DeviceName.Value) { UniLog.Log("Already connected."); return; @@ -189,91 +218,223 @@ private ushort CombineBytes(byte First, byte Second) return _14bit; } - private void OnMessageReceived(object sender, MidiReceivedEventArgs args) + private struct TimestampedMidiEvent { - if (DEBUG) UniLog.Log($"Received {args.Length} bytes"); - if (DEBUG) UniLog.Log($"Timestamp: {args.Timestamp}"); - if (DEBUG) UniLog.Log($"Start: {args.Start}"); - var events = MidiEvent.Convert(args.Data, args.Start, args.Length); - foreach (var e in events) + public MidiEvent midiEvent; + public long timestamp; + public TimestampedMidiEvent(MidiEvent _midiEvent, long _timestamp) + { + midiEvent = _midiEvent; + timestamp = _timestamp; + } + } + + // I am using this like a Queue so it could possibly be turned into a Queue instead... + private List _eventBuffer = new(); + + private const long BATCH_TIME_SIZE_MILLISECONDS = 3; + private const long CC_FINE_MESSAGE_GAP_MILLISECONDS = 1; + + private bool IsCCFineMessage() + { + long timestamp = _eventBuffer[0].timestamp; + if (_eventBuffer.Count >= 2 && _eventBuffer[0].midiEvent.EventType == MidiEvent.CC && _eventBuffer[1].midiEvent.EventType == MidiEvent.CC && _eventBuffer[1].timestamp - timestamp <= CC_FINE_MESSAGE_GAP_MILLISECONDS) { - if (DEBUG) UniLog.Log(e.ToString()); - RunSynchronously(() => + return true; + } + return false; + } + + private void ProcessMessageBatch() + { + var batchStartTime = _eventBuffer[0].timestamp; + if (DEBUG) UniLog.Log("Processing message batch: " + batchStartTime.ToString()); + + while (_eventBuffer.Count() > 0 && _eventBuffer[0].timestamp - batchStartTime <= BATCH_TIME_SIZE_MILLISECONDS) + { + + while (IsCCFineMessage()) { - _lastEvent.Value = e.ToString(); - }); + var e1 = _eventBuffer[0].midiEvent; + var e2 = _eventBuffer[1].midiEvent; + var finalValue = CombineBytes(e2.Lsb, e1.Lsb); + if (DEBUG) UniLog.Log($"CC fine. Value: " + finalValue.ToString()); + Control?.Invoke(this, new MIDI_CC_EventData(e1.Channel, e1.Msb, finalValue)); + _eventBuffer.RemoveRange(0, 2); + } + + if (_eventBuffer.Count() == 0) break; + + var e = _eventBuffer[0].midiEvent; switch (e.EventType) { case MidiEvent.NoteOn: + if (DEBUG) UniLog.Log("NoteOn"); NoteOn?.Invoke(this, new MIDI_NoteEventData(e.Channel, e.Msb, e.Lsb)); + _lastEventType.Value = "NoteOn"; break; case MidiEvent.NoteOff: + if (DEBUG) UniLog.Log("NoteOff"); NoteOff?.Invoke(this, new MIDI_NoteEventData(e.Channel, e.Msb, e.Lsb)); + _lastEventType.Value = "NoteOff"; break; case MidiEvent.CAf: - ChannelPressure?.Invoke(this, new MIDI_ChannelPressureEventData(e.Channel, e.Msb)); + if (DEBUG) UniLog.Log("CAf"); + ChannelAftertouch?.Invoke(this, new MIDI_ChannelAftertouchEventData(e.Channel, e.Msb)); + _lastEventType.Value = "CAf"; break; case MidiEvent.CC: + if (DEBUG) UniLog.Log("CC"); Control?.Invoke(this, new MIDI_CC_EventData(e.Channel, e.Msb, e.Lsb)); + _lastEventType.Value = "CC"; break; case MidiEvent.Pitch: + if (DEBUG) UniLog.Log("Pitch"); PitchWheel?.Invoke(this, new MIDI_PitchWheelEventData(e.Channel, CombineBytes(e.Msb, e.Lsb))); + _lastEventType.Value = "Pitch"; break; case MidiEvent.PAf: - Aftertouch?.Invoke(this, new MIDI_AftertouchEventData(e.Channel, e.Msb, e.Lsb)); + if (DEBUG) UniLog.Log("PAf"); + PolyphonicAftertouch?.Invoke(this, new MIDI_PolyphonicAftertouchEventData(e.Channel, e.Msb, e.Lsb)); + _lastEventType.Value = "PAf"; + break; + case MidiEvent.Program: + if (DEBUG) UniLog.Log("Program"); + Program?.Invoke(this, new MIDI_ProgramEventData(e.Channel, e.Msb)); + _lastEventType.Value = "Program"; break; // Unhandled events: //SysEx events are probably not worth handling case MidiEvent.SysEx1: - //if (DEBUG) UniLog.Log("UnhandledEvent: SysEx1"); + if (DEBUG) UniLog.Log("UnhandledEvent: SysEx1"); + _lastEventType.Value = "SysEx1"; break; case MidiEvent.SysEx2: // Same as EndSysEx - //if (DEBUG) UniLog.Log("UnhandledEvent: SysEx2"); - break; - - case MidiEvent.Program: - if (DEBUG) UniLog.Log("UnhandledEvent: Program"); + if (DEBUG) UniLog.Log("UnhandledEvent: SysEx2"); + _lastEventType.Value = "SysEx2"; break; case MidiEvent.MtcQuarterFrame: if (DEBUG) UniLog.Log("UnhandledEvent: MtcQuarterFrame"); + _lastEventType.Value = "MtcQuarterFrame"; break; case MidiEvent.SongPositionPointer: if (DEBUG) UniLog.Log("UnhandledEvent: SongPositionPointer"); + _lastEventType.Value = "SongPositionPointer"; break; case MidiEvent.SongSelect: if (DEBUG) UniLog.Log("UnhandledEvent: SongSelect"); + _lastEventType.Value = "SongSelect"; break; case MidiEvent.TuneRequest: if (DEBUG) UniLog.Log("UnhandledEvent: TuneRequest"); - break; - case MidiEvent.MidiClock: - if (DEBUG) UniLog.Log("UnhandledEvent: Clock"); - break; - case MidiEvent.MidiTick: - if (DEBUG) UniLog.Log("UnhandledEvent: MidiTick"); - break; - case MidiEvent.MidiStart: - if (DEBUG) UniLog.Log("UnhandledEvent: MidiStart"); - break; - case MidiEvent.MidiStop: - if (DEBUG) UniLog.Log("UnhandledEvent: MidiStart"); - break; - case MidiEvent.MidiContinue: - if (DEBUG) UniLog.Log("UnhandledEvent: MidiContinue"); - break; - case MidiEvent.ActiveSense: - if (DEBUG) UniLog.Log("UnhandledEvent: ActiveSense"); - break; - case MidiEvent.Reset: - // Same as Meta - if (DEBUG) UniLog.Log("UnhandledEvent: Reset"); + _lastEventType.Value = "TuneRequest"; break; default: break; } + _eventBuffer.RemoveAt(0); + } + UniLog.Log("End event batch: " + batchStartTime.ToString()); + UniLog.Log("Remaining events in event buffer: " + _eventBuffer.Count.ToString()); + } + + private long _lastBatchStartTime = 0; + + private void OnMessageReceived(object sender, MidiReceivedEventArgs args) + { + if (DEBUG) UniLog.Log($"* Received {args.Length} bytes"); + if (DEBUG) UniLog.Log($"* Timestamp: {args.Timestamp}"); + + var events = MidiEvent.Convert(args.Data, args.Start, args.Length); + + if (args.Length == 1) + { + // system realtime message, do not buffer these, execute immediately + if (DEBUG) UniLog.Log($"* System realtime message"); + foreach (var e in events) + { + var str = e.ToString(); + if (DEBUG) UniLog.Log("* " + str); + RunSynchronously(() => + { + _lastSystemRealtimeEvent.Value = str; + }); + switch (e.StatusByte) + { + case MidiEvent.MidiClock: + if (DEBUG) UniLog.Log("UnhandledEvent: MidiClock"); + RunSynchronously(() => + { + _lastSystemRealtimeEventType.Value = "MidiClock"; + }); + break; + case MidiEvent.MidiTick: + if (DEBUG) UniLog.Log("UnhandledEvent: MidiTick"); + RunSynchronously(() => + { + _lastSystemRealtimeEventType.Value = "MidiTick"; + }); + break; + case MidiEvent.MidiStart: + if (DEBUG) UniLog.Log("UnhandledEvent: MidiStart"); + RunSynchronously(() => + { + _lastSystemRealtimeEventType.Value = "MidiStart"; + }); + break; + case MidiEvent.MidiStop: + if (DEBUG) UniLog.Log("UnhandledEvent: MidiStop"); + RunSynchronously(() => + { + _lastSystemRealtimeEventType.Value = "MidiStop"; + }); + break; + case MidiEvent.MidiContinue: + if (DEBUG) UniLog.Log("UnhandledEvent: MidiContinue"); + RunSynchronously(() => + { + _lastSystemRealtimeEventType.Value = "MidiContinue"; + }); + break; + case MidiEvent.ActiveSense: + if (DEBUG) UniLog.Log("UnhandledEvent: ActiveSense"); + RunSynchronously(() => + { + _lastSystemRealtimeEventType.Value = "ActiveSense"; + }); + break; + case MidiEvent.Reset: + // Same as Meta + if (DEBUG) UniLog.Log("UnhandledEvent: Reset"); + RunSynchronously(() => + { + _lastSystemRealtimeEventType.Value = "Reset"; + }); + break; + } + } + return; + } + + // other types of messages: channel message (voice or channel mode), system common message, system exclusive message + foreach(var e in events) + { + var str = e.ToString(); + if (DEBUG) UniLog.Log("* " + str); + RunSynchronously(() => + { + _lastEvent.Value = str; + }); + _eventBuffer.Add(new TimestampedMidiEvent(e, args.Timestamp)); + } + if (events.Count() > 0 && args.Timestamp - _lastBatchStartTime > BATCH_TIME_SIZE_MILLISECONDS) + { + _lastBatchStartTime = args.Timestamp; + if (DEBUG) UniLog.Log("* New message batch created: " + args.Timestamp.ToString()); + RunInUpdates(2, ProcessMessageBatch); } } } \ No newline at end of file diff --git a/ProjectObsidian/Elements/MIDI.cs b/ProjectObsidian/Elements/MIDI.cs index f73f45e..9a0b7ca 100644 --- a/ProjectObsidian/Elements/MIDI.cs +++ b/ProjectObsidian/Elements/MIDI.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Components.Devices.MIDI; using Elements.Core; namespace Obsidian.Elements; @@ -90,6 +91,20 @@ public enum MIDI_CC_Definition PolyModeOn = 127 } +[DataModelType] +public readonly struct MIDI_ProgramEventData +{ + public readonly int channel; + + public readonly int program; + + public MIDI_ProgramEventData(in int _channel, in int _program) + { + channel = _channel; + program = _program; + } +} + [DataModelType] public readonly struct MIDI_PitchWheelEventData { @@ -122,13 +137,13 @@ public MIDI_NoteEventData(in int _channel, in int _note, in int _velocity) } [DataModelType] -public readonly struct MIDI_ChannelPressureEventData +public readonly struct MIDI_ChannelAftertouchEventData { public readonly int channel; public readonly int pressure; - public MIDI_ChannelPressureEventData(in int _channel, in int _pressure) + public MIDI_ChannelAftertouchEventData(in int _channel, in int _pressure) { channel = _channel; pressure = _pressure; @@ -136,7 +151,7 @@ public MIDI_ChannelPressureEventData(in int _channel, in int _pressure) } [DataModelType] -public readonly struct MIDI_AftertouchEventData +public readonly struct MIDI_PolyphonicAftertouchEventData { public readonly int channel; @@ -144,7 +159,7 @@ public readonly struct MIDI_AftertouchEventData public readonly int pressure; - public MIDI_AftertouchEventData(in int _channel, in int _note, in int _pressure) + public MIDI_PolyphonicAftertouchEventData(in int _channel, in int _note, in int _pressure) { channel = _channel; note = _note; @@ -173,13 +188,16 @@ public MIDI_CC_EventData(in int _channel, in int _controller, in int _value) public delegate void MIDI_NoteEventHandler(MIDI_InputDevice device, MIDI_NoteEventData eventData); [DataModelType] -public delegate void MIDI_ChannelPressureEventHandler(MIDI_InputDevice device, MIDI_ChannelPressureEventData eventData); +public delegate void MIDI_ChannelAftertouchEventHandler(MIDI_InputDevice device, MIDI_ChannelAftertouchEventData eventData); [DataModelType] -public delegate void MIDI_AftertouchEventHandler(MIDI_InputDevice device, MIDI_AftertouchEventData eventData); +public delegate void MIDI_PolyphonicAftertouchEventHandler(MIDI_InputDevice device, MIDI_PolyphonicAftertouchEventData eventData); [DataModelType] public delegate void MIDI_CC_EventHandler(MIDI_InputDevice device, MIDI_CC_EventData eventData); [DataModelType] -public delegate void MIDI_PitchWheelEventHandler(MIDI_InputDevice device, MIDI_PitchWheelEventData eventData); \ No newline at end of file +public delegate void MIDI_PitchWheelEventHandler(MIDI_InputDevice device, MIDI_PitchWheelEventData eventData); + +[DataModelType] +public delegate void MIDI_ProgramEventHandler(MIDI_InputDevice device, MIDI_ProgramEventData eventData); \ No newline at end of file diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI_ChannelPressureEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI_ChannelAftertouchEvent.cs similarity index 59% rename from ProjectObsidian/ProtoFlux/Devices/MIDI_ChannelPressureEvent.cs rename to ProjectObsidian/ProtoFlux/Devices/MIDI_ChannelAftertouchEvent.cs index f98bcc3..29317c6 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI_ChannelPressureEvent.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI_ChannelAftertouchEvent.cs @@ -7,17 +7,18 @@ using FrooxEngine; using FrooxEngine.ProtoFlux; using Obsidian.Elements; -using Obsidian; +using Components.Devices.MIDI; namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Devices; -[NodeName("MIDI Channel Pressure Event")] -[NodeCategory("Obsidian/Devices")] -public class MIDI_ChannelPressureEvent : VoidNode +[NodeName("MIDI Channel Aftertouch Event")] +[NodeCategory("Obsidian/Devices/MIDI")] +[OldTypeName("FrooxEngine.ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Devices.MIDI_ChannelPressureEvent")] +public class MIDI_ChannelAftertouchEvent : VoidNode { public readonly GlobalRef Device; - public Call ChannelPressure; + public Call ChannelAftertouch; public readonly ValueOutput Channel; @@ -27,7 +28,7 @@ public class MIDI_ChannelPressureEvent : VoidNode private ObjectStore _currentDevice; - private ObjectStore _channelPressure; + private ObjectStore _channelAftertouch; public override bool CanBeEvaluated => false; @@ -40,44 +41,44 @@ private void OnDeviceChanged(MIDI_InputDevice device, FrooxEngineContext context } if (device2 != null) { - device2.ChannelPressure -= _channelPressure.Read(context); + device2.ChannelAftertouch -= _channelAftertouch.Read(context); } if (device != null) { NodeContextPath path = context.CaptureContextPath(); context.GetEventDispatcher(out var dispatcher); - MIDI_ChannelPressureEventHandler value3 = delegate (MIDI_InputDevice dev, MIDI_ChannelPressureEventData e) + MIDI_ChannelAftertouchEventHandler value3 = delegate (MIDI_InputDevice dev, MIDI_ChannelAftertouchEventData e) { dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) { - OnChannelPressure(dev, in e, c); + OnChannelAftertouch(dev, in e, c); }); }; _currentDevice.Write(device, context); - _channelPressure.Write(value3, context); - device.ChannelPressure += value3; + _channelAftertouch.Write(value3, context); + device.ChannelAftertouch += value3; } else { _currentDevice.Clear(context); - _channelPressure.Clear(context); + _channelAftertouch.Clear(context); } } - private void WriteChannelPressureEventData(in MIDI_ChannelPressureEventData eventData, FrooxEngineContext context) + private void WriteChannelAftertouchEventData(in MIDI_ChannelAftertouchEventData eventData, FrooxEngineContext context) { Channel.Write(eventData.channel, context); Pressure.Write(eventData.pressure, context); NormalizedPressure.Write(eventData.pressure / 127f, context); } - private void OnChannelPressure(MIDI_InputDevice device, in MIDI_ChannelPressureEventData eventData, FrooxEngineContext context) + private void OnChannelAftertouch(MIDI_InputDevice device, in MIDI_ChannelAftertouchEventData eventData, FrooxEngineContext context) { - WriteChannelPressureEventData(in eventData, context); - ChannelPressure.Execute(context); + WriteChannelAftertouchEventData(in eventData, context); + ChannelAftertouch.Execute(context); } - public MIDI_ChannelPressureEvent() + public MIDI_ChannelAftertouchEvent() { Device = new GlobalRef(this, 0); Channel = new ValueOutput(this); diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI_AftertouchEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI_PolyphonicAftertouchEvent.cs similarity index 61% rename from ProjectObsidian/ProtoFlux/Devices/MIDI_AftertouchEvent.cs rename to ProjectObsidian/ProtoFlux/Devices/MIDI_PolyphonicAftertouchEvent.cs index 93f7955..9eb19be 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI_AftertouchEvent.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI_PolyphonicAftertouchEvent.cs @@ -7,17 +7,18 @@ using FrooxEngine; using FrooxEngine.ProtoFlux; using Obsidian.Elements; -using Obsidian; +using Components.Devices.MIDI; namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Devices; [NodeName("MIDI Polyphonic Aftertouch Event")] -[NodeCategory("Obsidian/Devices")] -public class MIDI_AftertouchEvent : VoidNode +[NodeCategory("Obsidian/Devices/MIDI")] +[OldTypeName("FrooxEngine.ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Devices.MIDI_AftertouchEvent")] +public class MIDI_PolyphonicAftertouchEvent : VoidNode { public readonly GlobalRef Device; - public Call Aftertouch; + public Call PolyphonicAftertouch; public readonly ValueOutput Channel; @@ -29,7 +30,7 @@ public class MIDI_AftertouchEvent : VoidNode private ObjectStore _currentDevice; - private ObjectStore _aftertouch; + private ObjectStore _polyphonicAftertouch; public override bool CanBeEvaluated => false; @@ -42,31 +43,31 @@ private void OnDeviceChanged(MIDI_InputDevice device, FrooxEngineContext context } if (device2 != null) { - device2.Aftertouch -= _aftertouch.Read(context); + device2.PolyphonicAftertouch -= _polyphonicAftertouch.Read(context); } if (device != null) { NodeContextPath path = context.CaptureContextPath(); context.GetEventDispatcher(out var dispatcher); - MIDI_AftertouchEventHandler value3 = delegate (MIDI_InputDevice dev, MIDI_AftertouchEventData e) + MIDI_PolyphonicAftertouchEventHandler value3 = delegate (MIDI_InputDevice dev, MIDI_PolyphonicAftertouchEventData e) { dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) { - OnAftertouch(dev, in e, c); + OnPolyphonicAftertouch(dev, in e, c); }); }; _currentDevice.Write(device, context); - _aftertouch.Write(value3, context); - device.Aftertouch += value3; + _polyphonicAftertouch.Write(value3, context); + device.PolyphonicAftertouch += value3; } else { _currentDevice.Clear(context); - _aftertouch.Clear(context); + _polyphonicAftertouch.Clear(context); } } - private void WriteAftertouchEventData(in MIDI_AftertouchEventData eventData, FrooxEngineContext context) + private void WritePolyphonicAftertouchEventData(in MIDI_PolyphonicAftertouchEventData eventData, FrooxEngineContext context) { Channel.Write(eventData.channel, context); Note.Write(eventData.note, context); @@ -74,13 +75,13 @@ private void WriteAftertouchEventData(in MIDI_AftertouchEventData eventData, Fro NormalizedPressure.Write(eventData.pressure / 127f, context); } - private void OnAftertouch(MIDI_InputDevice device, in MIDI_AftertouchEventData eventData, FrooxEngineContext context) + private void OnPolyphonicAftertouch(MIDI_InputDevice device, in MIDI_PolyphonicAftertouchEventData eventData, FrooxEngineContext context) { - WriteAftertouchEventData(in eventData, context); - Aftertouch.Execute(context); + WritePolyphonicAftertouchEventData(in eventData, context); + PolyphonicAftertouch.Execute(context); } - public MIDI_AftertouchEvent() + public MIDI_PolyphonicAftertouchEvent() { Device = new GlobalRef(this, 0); Channel = new ValueOutput(this); diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI_ProgramEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI_ProgramEvent.cs new file mode 100644 index 0000000..227ff24 --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI_ProgramEvent.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq; +using Newtonsoft.Json.Linq; +using ProtoFlux.Core; +using ProtoFlux.Runtimes.Execution; +using Elements.Core; +using FrooxEngine; +using FrooxEngine.ProtoFlux; +using Obsidian.Elements; +using Obsidian; + +namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Devices; + +[NodeName("MIDI Program Event")] +[NodeCategory("Obsidian/Devices/MIDI")] +public class MIDI_ProgramEvent : VoidNode +{ + public readonly GlobalRef Device; + + public Call Program; + + public readonly ValueOutput Channel; + + public readonly ValueOutput ProgramValue; + + private ObjectStore _currentDevice; + + private ObjectStore _program; + + public override bool CanBeEvaluated => false; + + private void OnDeviceChanged(MIDI_InputDevice device, FrooxEngineContext context) + { + MIDI_InputDevice device2 = _currentDevice.Read(context); + if (device == device2) + { + return; + } + if (device2 != null) + { + device2.Program -= _program.Read(context); + } + if (device != null) + { + NodeContextPath path = context.CaptureContextPath(); + context.GetEventDispatcher(out var dispatcher); + MIDI_ProgramEventHandler value = delegate (MIDI_InputDevice dev, MIDI_ProgramEventData e) + { + dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) + { + OnNoteOn(dev, in e, c); + }); + }; + _currentDevice.Write(device, context); + _program.Write(value, context); + device.Program += value; + } + else + { + _currentDevice.Clear(context); + _program.Clear(context); + } + } + + private void WriteNoteOnOffEventData(in MIDI_ProgramEventData eventData, FrooxEngineContext context) + { + Channel.Write(eventData.channel, context); + ProgramValue.Write(eventData.program, context); + } + + private void OnNoteOn(MIDI_InputDevice device, in MIDI_ProgramEventData eventData, FrooxEngineContext context) + { + WriteNoteOnOffEventData(in eventData, context); + Program.Execute(context); + } + + public MIDI_ProgramEvent() + { + Device = new GlobalRef(this, 0); + Channel = new ValueOutput(this); + ProgramValue = new ValueOutput(this); + } +} \ No newline at end of file diff --git a/ProjectObsidian/Settings/MIDI_Settings.cs b/ProjectObsidian/Settings/MIDI_Settings.cs index c8c8b5c..a6d3b71 100644 --- a/ProjectObsidian/Settings/MIDI_Settings.cs +++ b/ProjectObsidian/Settings/MIDI_Settings.cs @@ -104,13 +104,18 @@ protected override void OnStart() _localeData.Messages.Add("Settings.MIDI_Settings.Remove", "Remove"); // Sometimes the locale is null in here, so wait a bit I guess - RunInUpdates(7, () => + + RunInUpdates(30, () => { UpdateLocale(); Settings.RegisterValueChanges(UpdateLocale); + RefreshDeviceLists(); }); - RefreshDeviceLists(); + //MidiAccessManager.Default.StateChanged += (object sender, MidiConnectionEventArgs args) => + //{ + // UniLog.Log("midi access state changed"); + //}; } protected override void OnDispose() From 1f784eda148659d80eec946259602e526a8c0725 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:38:36 +0100 Subject: [PATCH 2/5] Fix method names --- .../ProtoFlux/Devices/MIDI/MIDI_ProgramEvent.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ProgramEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ProgramEvent.cs index 9a8150c..ef80a5a 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ProgramEvent.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ProgramEvent.cs @@ -48,7 +48,7 @@ private void OnDeviceChanged(MIDI_InputDevice device, FrooxEngineContext context { dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) { - OnNoteOn(dev, in e, c); + OnProgram(dev, in e, c); }); }; _currentDevice.Write(device, context); @@ -62,15 +62,15 @@ private void OnDeviceChanged(MIDI_InputDevice device, FrooxEngineContext context } } - private void WriteNoteOnOffEventData(in MIDI_ProgramEventData eventData, FrooxEngineContext context) + private void WriteProgramEventData(in MIDI_ProgramEventData eventData, FrooxEngineContext context) { Channel.Write(eventData.channel, context); ProgramValue.Write(eventData.program, context); } - private void OnNoteOn(MIDI_InputDevice device, in MIDI_ProgramEventData eventData, FrooxEngineContext context) + private void OnProgram(MIDI_InputDevice device, in MIDI_ProgramEventData eventData, FrooxEngineContext context) { - WriteNoteOnOffEventData(in eventData, context); + WriteProgramEventData(in eventData, context); Program.Execute(context); } From b8325cb200105a2167d2cc584c257e3ba709b713 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 23 Jul 2024 01:00:46 +0100 Subject: [PATCH 3/5] Implement realtime events handling, add message buffer, handle 14bit CC messages, and more... --- .../Devices/MIDI/MIDI_InputDevice.cs | 249 +++++++++--------- .../Devices/MIDI/MIDI_PitchWheel_Value.cs | 8 +- ProjectObsidian/Elements/MIDI.cs | 30 ++- .../ProtoFlux/Devices/MIDI/MIDI_CC_Event.cs | 2 +- .../MIDI/MIDI_ChannelAftertouchEvent.cs | 2 +- .../ProtoFlux/Devices/MIDI/MIDI_NoteEvents.cs | 3 +- .../Devices/MIDI/MIDI_PitchWheelEvent.cs | 5 +- .../MIDI/MIDI_PolyphonicAftertouchEvent.cs | 2 +- .../Devices/MIDI/MIDI_SystemRealtimeEvents.cs | 201 ++++++++++++++ ProjectObsidian/Settings/MIDI_Settings.cs | 9 +- 10 files changed, 362 insertions(+), 149 deletions(-) create mode 100644 ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_SystemRealtimeEvents.cs diff --git a/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs b/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs index e019962..93de92f 100644 --- a/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs +++ b/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs @@ -1,11 +1,8 @@ using Elements.Core; using FrooxEngine; -using System; using System.Linq; using System.Threading.Tasks; using System.Collections.Generic; -using Commons.Music.Midi.RtMidi; -using CoreMidi; using Commons.Music.Midi; using Obsidian.Elements; using Obsidian; @@ -13,6 +10,7 @@ namespace Components.Devices.MIDI; [Category(new string[] { "Obsidian/Devices/MIDI" })] +[OldTypeName("Obsidian.MIDI_InputDevice")] public class MIDI_InputDevice : Component { [NoContinuousParsing] @@ -22,14 +20,6 @@ public class MIDI_InputDevice : Component public readonly UserRef HandlingUser; - public readonly Sync _lastEvent; - - public readonly Sync _lastEventType; - - public readonly Sync _lastSystemRealtimeEvent; - - public readonly Sync _lastSystemRealtimeEventType; - private bool _lastIsConnected; private IMidiInput _inputDevice; @@ -52,6 +42,20 @@ public class MIDI_InputDevice : Component public event MIDI_ProgramEventHandler Program; + public event MIDI_SystemRealtimeEventHandler MidiClock; + + public event MIDI_SystemRealtimeEventHandler MidiTick; + + public event MIDI_SystemRealtimeEventHandler MidiStart; + + public event MIDI_SystemRealtimeEventHandler MidiStop; + + public event MIDI_SystemRealtimeEventHandler MidiContinue; + + public event MIDI_SystemRealtimeEventHandler ActiveSense; + + public event MIDI_SystemRealtimeEventHandler Reset; + private const bool DEBUG = true; protected override void OnStart() @@ -59,10 +63,6 @@ protected override void OnStart() base.OnStart(); Settings.GetActiveSetting(); Settings.RegisterValueChanges(OnInputDeviceSettingsChanged); - //if (!Engine.Current.IsInitialized) - //{ - // Engine.Current.RunPostInit(() => RunInUpdates(30, Update)); - //} RunInUpdates(30, Update); } @@ -74,28 +74,7 @@ private void OnInputDeviceSettingsChanged(MIDI_Settings setting) protected override void OnChanges() { - //UniLog.Log("OnChanges"); base.OnChanges(); - if (_lastEvent.WasChanged) - { - _lastEvent.WasChanged = false; - return; - } - if (_lastEventType.WasChanged) - { - _lastEventType.WasChanged = false; - return; - } - if (_lastSystemRealtimeEvent.WasChanged) - { - _lastSystemRealtimeEvent.WasChanged = false; - return; - } - if (_lastSystemRealtimeEventType.WasChanged) - { - _lastSystemRealtimeEventType.WasChanged = false; - return; - } if (IsConnected.WasChanged) { IsConnected.Value = _lastIsConnected; @@ -105,12 +84,26 @@ protected override void OnChanges() Update(); } - private async void ReleaseDeviceAsync() + private async Task ReleaseDeviceAsync() { UniLog.Log("Releasing device..."); - await Task.WhenAny(_inputDevice.CloseAsync(), Task.Delay(10000)); + await Task.WhenAny(_inputDevice.CloseAsync(), Task.Delay(5000)); UniLog.Log("Device released."); _inputDevice = null; + _eventBuffer.Clear(); + _lastBatchStartTime = 0; + } + + private async void ReleaseDeviceAndConnectAsync(IMidiAccess access, string deviceId) + { + if (_inputDevice != null) + { + await ReleaseDeviceAsync(); + } + _inputDevice = access.OpenInputAsync(deviceId).Result; + _inputDevice.MessageReceived += OnMessageReceived; + SetIsConnected(true); + UniLog.Log("Connected."); } protected override void OnDispose() @@ -177,7 +170,7 @@ private void Update() && (_inputDevice.Connection == MidiPortConnectionState.Open || _inputDevice.Connection == MidiPortConnectionState.Pending) && _inputDevice.Details.Name == DeviceName.Value) { - UniLog.Log("Already connected."); + UniLog.Log("Already connected. Connection state: " + _inputDevice.Connection.ToString()); return; } @@ -186,10 +179,7 @@ private void Update() if (targetDevice != null) { UniLog.Log("Found the target device."); - _inputDevice = access.OpenInputAsync(targetDevice.Id).Result; - _inputDevice.MessageReceived += OnMessageReceived; - SetIsConnected(true); - UniLog.Log("Connected."); + ReleaseDeviceAndConnectAsync(access, targetDevice.Id); } else { @@ -232,75 +222,61 @@ public TimestampedMidiEvent(MidiEvent _midiEvent, long _timestamp) private List _eventBuffer = new(); private const long BATCH_TIME_SIZE_MILLISECONDS = 3; - private const long CC_FINE_MESSAGE_GAP_MILLISECONDS = 1; private bool IsCCFineMessage() { + if (_eventBuffer.Count == 0) return false; long timestamp = _eventBuffer[0].timestamp; - if (_eventBuffer.Count >= 2 && _eventBuffer[0].midiEvent.EventType == MidiEvent.CC && _eventBuffer[1].midiEvent.EventType == MidiEvent.CC && _eventBuffer[1].timestamp - timestamp <= CC_FINE_MESSAGE_GAP_MILLISECONDS) + if (_eventBuffer.Count >= 2 + && _eventBuffer[0].midiEvent.EventType == MidiEvent.CC && _eventBuffer[1].midiEvent.EventType == MidiEvent.CC + && _eventBuffer[0].midiEvent.Msb == _eventBuffer[1].midiEvent.Msb - 32) { return true; } return false; } - private void ProcessMessageBatch() + private void FlushMessageBuffer() { + if (_eventBuffer.Count == 0) + { + UniLog.Log("Message buffer empty."); + return; + } + var batchStartTime = _eventBuffer[0].timestamp; - if (DEBUG) UniLog.Log("Processing message batch: " + batchStartTime.ToString()); + if (DEBUG) UniLog.Log("Flushing message buffer from start time: " + batchStartTime.ToString()); - while (_eventBuffer.Count() > 0 && _eventBuffer[0].timestamp - batchStartTime <= BATCH_TIME_SIZE_MILLISECONDS) + while (_eventBuffer.Count > 0) { while (IsCCFineMessage()) { var e1 = _eventBuffer[0].midiEvent; + if (DEBUG) UniLog.Log(e1.ToString()); var e2 = _eventBuffer[1].midiEvent; + if (DEBUG) UniLog.Log(e2.ToString()); var finalValue = CombineBytes(e2.Lsb, e1.Lsb); if (DEBUG) UniLog.Log($"CC fine. Value: " + finalValue.ToString()); - Control?.Invoke(this, new MIDI_CC_EventData(e1.Channel, e1.Msb, finalValue)); + Control?.Invoke(this, new MIDI_CC_EventData(e1.Channel, e1.Msb, finalValue, _coarse: false)); _eventBuffer.RemoveRange(0, 2); + _bufferedMessagesToHandle -= 2; } - if (_eventBuffer.Count() == 0) break; + if (_eventBuffer.Count == 0) break; var e = _eventBuffer[0].midiEvent; + if (DEBUG) UniLog.Log(e.ToString()); switch (e.EventType) { - case MidiEvent.NoteOn: - if (DEBUG) UniLog.Log("NoteOn"); - NoteOn?.Invoke(this, new MIDI_NoteEventData(e.Channel, e.Msb, e.Lsb)); - _lastEventType.Value = "NoteOn"; - break; - case MidiEvent.NoteOff: - if (DEBUG) UniLog.Log("NoteOff"); - NoteOff?.Invoke(this, new MIDI_NoteEventData(e.Channel, e.Msb, e.Lsb)); - _lastEventType.Value = "NoteOff"; - break; - case MidiEvent.CAf: - if (DEBUG) UniLog.Log("CAf"); - ChannelAftertouch?.Invoke(this, new MIDI_ChannelAftertouchEventData(e.Channel, e.Msb)); - _lastEventType.Value = "CAf"; - break; case MidiEvent.CC: if (DEBUG) UniLog.Log("CC"); - Control?.Invoke(this, new MIDI_CC_EventData(e.Channel, e.Msb, e.Lsb)); - _lastEventType.Value = "CC"; - break; - case MidiEvent.Pitch: - if (DEBUG) UniLog.Log("Pitch"); - PitchWheel?.Invoke(this, new MIDI_PitchWheelEventData(e.Channel, CombineBytes(e.Msb, e.Lsb))); - _lastEventType.Value = "Pitch"; - break; - case MidiEvent.PAf: - if (DEBUG) UniLog.Log("PAf"); - PolyphonicAftertouch?.Invoke(this, new MIDI_PolyphonicAftertouchEventData(e.Channel, e.Msb, e.Lsb)); - _lastEventType.Value = "PAf"; + Control?.Invoke(this, new MIDI_CC_EventData(e.Channel, e.Msb, e.Lsb, _coarse: true)); break; + // Program events are buffered because they can be sent after a CC fine message for Bank Select, one of my devices sends consecutively: CC (Bank Select) -> CC (Bank Select Lsb) -> Program for some buttons case MidiEvent.Program: if (DEBUG) UniLog.Log("Program"); Program?.Invoke(this, new MIDI_ProgramEventData(e.Channel, e.Msb)); - _lastEventType.Value = "Program"; break; // Unhandled events: @@ -308,47 +284,51 @@ private void ProcessMessageBatch() //SysEx events are probably not worth handling case MidiEvent.SysEx1: if (DEBUG) UniLog.Log("UnhandledEvent: SysEx1"); - _lastEventType.Value = "SysEx1"; break; case MidiEvent.SysEx2: // Same as EndSysEx if (DEBUG) UniLog.Log("UnhandledEvent: SysEx2"); - _lastEventType.Value = "SysEx2"; break; case MidiEvent.MtcQuarterFrame: if (DEBUG) UniLog.Log("UnhandledEvent: MtcQuarterFrame"); - _lastEventType.Value = "MtcQuarterFrame"; break; case MidiEvent.SongPositionPointer: if (DEBUG) UniLog.Log("UnhandledEvent: SongPositionPointer"); - _lastEventType.Value = "SongPositionPointer"; break; case MidiEvent.SongSelect: if (DEBUG) UniLog.Log("UnhandledEvent: SongSelect"); - _lastEventType.Value = "SongSelect"; break; case MidiEvent.TuneRequest: if (DEBUG) UniLog.Log("UnhandledEvent: TuneRequest"); - _lastEventType.Value = "TuneRequest"; break; default: break; } _eventBuffer.RemoveAt(0); + _bufferedMessagesToHandle -= 1; + } + if (DEBUG) UniLog.Log("Finished flushing message buffer from start time: " + batchStartTime.ToString()); + if (_bufferedMessagesToHandle != 0) + { + // Just in case some messages got lost somehow + UniLog.Warning("Did not handle all buffered messages! " + _bufferedMessagesToHandle.ToString()); } - UniLog.Log("End event batch: " + batchStartTime.ToString()); - UniLog.Log("Remaining events in event buffer: " + _eventBuffer.Count.ToString()); } private long _lastBatchStartTime = 0; - private void OnMessageReceived(object sender, MidiReceivedEventArgs args) + private int _bufferedMessagesToHandle = 0; + + private async void OnMessageReceived(object sender, MidiReceivedEventArgs args) { + if (DEBUG) UniLog.Log($"*** New midi message"); if (DEBUG) UniLog.Log($"* Received {args.Length} bytes"); if (DEBUG) UniLog.Log($"* Timestamp: {args.Timestamp}"); var events = MidiEvent.Convert(args.Data, args.Start, args.Length); + //if (events.Count() == 0) return; + if (args.Length == 1) { // system realtime message, do not buffer these, execute immediately @@ -357,61 +337,36 @@ private void OnMessageReceived(object sender, MidiReceivedEventArgs args) { var str = e.ToString(); if (DEBUG) UniLog.Log("* " + str); - RunSynchronously(() => - { - _lastSystemRealtimeEvent.Value = str; - }); switch (e.StatusByte) { case MidiEvent.MidiClock: - if (DEBUG) UniLog.Log("UnhandledEvent: MidiClock"); - RunSynchronously(() => - { - _lastSystemRealtimeEventType.Value = "MidiClock"; - }); + if (DEBUG) UniLog.Log("* MidiClock"); + MidiClock?.Invoke(this, new MIDI_SystemRealtimeEventData()); break; case MidiEvent.MidiTick: - if (DEBUG) UniLog.Log("UnhandledEvent: MidiTick"); - RunSynchronously(() => - { - _lastSystemRealtimeEventType.Value = "MidiTick"; - }); + if (DEBUG) UniLog.Log("* MidiTick"); + MidiTick?.Invoke(this, new MIDI_SystemRealtimeEventData()); break; case MidiEvent.MidiStart: - if (DEBUG) UniLog.Log("UnhandledEvent: MidiStart"); - RunSynchronously(() => - { - _lastSystemRealtimeEventType.Value = "MidiStart"; - }); + if (DEBUG) UniLog.Log("* MidiStart"); + MidiStart?.Invoke(this, new MIDI_SystemRealtimeEventData()); break; case MidiEvent.MidiStop: - if (DEBUG) UniLog.Log("UnhandledEvent: MidiStop"); - RunSynchronously(() => - { - _lastSystemRealtimeEventType.Value = "MidiStop"; - }); + if (DEBUG) UniLog.Log("* MidiStop"); + MidiStop?.Invoke(this, new MIDI_SystemRealtimeEventData()); break; case MidiEvent.MidiContinue: - if (DEBUG) UniLog.Log("UnhandledEvent: MidiContinue"); - RunSynchronously(() => - { - _lastSystemRealtimeEventType.Value = "MidiContinue"; - }); + if (DEBUG) UniLog.Log("* MidiContinue"); + MidiContinue?.Invoke(this, new MIDI_SystemRealtimeEventData()); break; case MidiEvent.ActiveSense: - if (DEBUG) UniLog.Log("UnhandledEvent: ActiveSense"); - RunSynchronously(() => - { - _lastSystemRealtimeEventType.Value = "ActiveSense"; - }); + if (DEBUG) UniLog.Log("* ActiveSense"); + ActiveSense?.Invoke(this, new MIDI_SystemRealtimeEventData()); break; case MidiEvent.Reset: // Same as Meta - if (DEBUG) UniLog.Log("UnhandledEvent: Reset"); - RunSynchronously(() => - { - _lastSystemRealtimeEventType.Value = "Reset"; - }); + if (DEBUG) UniLog.Log("* Reset"); + Reset?.Invoke(this, new MIDI_SystemRealtimeEventData()); break; } } @@ -423,17 +378,51 @@ private void OnMessageReceived(object sender, MidiReceivedEventArgs args) { var str = e.ToString(); if (DEBUG) UniLog.Log("* " + str); - RunSynchronously(() => + + switch (e.EventType) { - _lastEvent.Value = str; - }); + case MidiEvent.NoteOn: + if (DEBUG) UniLog.Log("* NoteOn"); + if (e.Lsb == 0) + { + if (DEBUG) UniLog.Log("* Zero velocity, so it's actually a NoteOff"); + NoteOff?.Invoke(this, new MIDI_NoteEventData(e.Channel, e.Msb, e.Lsb)); + return; + } + NoteOn?.Invoke(this, new MIDI_NoteEventData(e.Channel, e.Msb, e.Lsb)); + return; + case MidiEvent.NoteOff: + if (DEBUG) UniLog.Log("* NoteOff"); + NoteOff?.Invoke(this, new MIDI_NoteEventData(e.Channel, e.Msb, e.Lsb)); + return; + case MidiEvent.CAf: + if (DEBUG) UniLog.Log("* CAf"); + ChannelAftertouch?.Invoke(this, new MIDI_ChannelAftertouchEventData(e.Channel, e.Msb)); + return; + case MidiEvent.Pitch: + if (DEBUG) UniLog.Log("* Pitch"); + PitchWheel?.Invoke(this, new MIDI_PitchWheelEventData(e.Channel, CombineBytes(e.Msb, e.Lsb))); + return; + case MidiEvent.PAf: + if (DEBUG) UniLog.Log("* PAf"); + PolyphonicAftertouch?.Invoke(this, new MIDI_PolyphonicAftertouchEventData(e.Channel, e.Msb, e.Lsb)); + return; + default: + break; + } + + // buffer CC messages because consecutive ones may need to be combined + // also buffer Program messages _eventBuffer.Add(new TimestampedMidiEvent(e, args.Timestamp)); + _bufferedMessagesToHandle += 1; } + if (events.Count() > 0 && args.Timestamp - _lastBatchStartTime > BATCH_TIME_SIZE_MILLISECONDS) { _lastBatchStartTime = args.Timestamp; if (DEBUG) UniLog.Log("* New message batch created: " + args.Timestamp.ToString()); - RunInUpdates(2, ProcessMessageBatch); + await Task.Delay((int)BATCH_TIME_SIZE_MILLISECONDS); + FlushMessageBuffer(); } } } \ No newline at end of file diff --git a/ProjectObsidian/Components/Devices/MIDI/MIDI_PitchWheel_Value.cs b/ProjectObsidian/Components/Devices/MIDI/MIDI_PitchWheel_Value.cs index f76146b..857cc32 100644 --- a/ProjectObsidian/Components/Devices/MIDI/MIDI_PitchWheel_Value.cs +++ b/ProjectObsidian/Components/Devices/MIDI/MIDI_PitchWheel_Value.cs @@ -8,7 +8,6 @@ using CoreMidi; using Commons.Music.Midi; using Obsidian.Elements; -using System.Runtime.Remoting.Contexts; namespace Components.Devices.MIDI; @@ -17,6 +16,8 @@ public class MIDI_PitchWheel_Value : Component { public readonly SyncRef InputDevice; + public readonly Sync AutoMap; + public readonly Sync Channel; public readonly Sync Value; @@ -50,6 +51,11 @@ private void OnPitchWheel(MIDI_InputDevice device, MIDI_PitchWheelEventData even { RunSynchronously(() => { + if (AutoMap.Value) + { + AutoMap.Value = false; + Channel.Value = eventData.channel; + } if (eventData.channel == Channel.Value) { Value.Value = eventData.value; diff --git a/ProjectObsidian/Elements/MIDI.cs b/ProjectObsidian/Elements/MIDI.cs index 9a0b7ca..d5f2725 100644 --- a/ProjectObsidian/Elements/MIDI.cs +++ b/ProjectObsidian/Elements/MIDI.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Components.Devices.MIDI; using Elements.Core; +using FrooxEngine; namespace Obsidian.Elements; @@ -91,6 +92,15 @@ public enum MIDI_CC_Definition PolyModeOn = 127 } +[DataModelType] +public readonly struct MIDI_SystemRealtimeEventData +{ + public MIDI_SystemRealtimeEventData() + { + // owo + } +} + [DataModelType] public readonly struct MIDI_ProgramEventData { @@ -112,6 +122,8 @@ public readonly struct MIDI_PitchWheelEventData public readonly int value; + public readonly float normalizedValue => value == 8192 ? 0f : MathX.Remap(value, 0f, 16383f, -1f, 1f); + public MIDI_PitchWheelEventData(in int _channel, in int _value) { channel = _channel; @@ -128,6 +140,8 @@ public readonly struct MIDI_NoteEventData public readonly int velocity; + public readonly float normalizedVelocity => velocity / 127f; + public MIDI_NoteEventData(in int _channel, in int _note, in int _velocity) { channel = _channel; @@ -143,6 +157,8 @@ public readonly struct MIDI_ChannelAftertouchEventData public readonly int pressure; + public readonly float normalizedPressure => pressure / 127f; + public MIDI_ChannelAftertouchEventData(in int _channel, in int _pressure) { channel = _channel; @@ -159,6 +175,8 @@ public readonly struct MIDI_PolyphonicAftertouchEventData public readonly int pressure; + public readonly float normalizedPressure => pressure / 127f; + public MIDI_PolyphonicAftertouchEventData(in int _channel, in int _note, in int _pressure) { channel = _channel; @@ -176,11 +194,16 @@ public readonly struct MIDI_CC_EventData public readonly int value; - public MIDI_CC_EventData(in int _channel, in int _controller, in int _value) + public readonly bool coarse; // is it 7bit (coarse) or 14bit (fine) value? + + public readonly float normalizedValue => coarse ? value / 127f : value / 16383f; + + public MIDI_CC_EventData(in int _channel, in int _controller, in int _value, in bool _coarse) { channel = _channel; controller = _controller; value = _value; + coarse = _coarse; } } @@ -200,4 +223,7 @@ public MIDI_CC_EventData(in int _channel, in int _controller, in int _value) public delegate void MIDI_PitchWheelEventHandler(MIDI_InputDevice device, MIDI_PitchWheelEventData eventData); [DataModelType] -public delegate void MIDI_ProgramEventHandler(MIDI_InputDevice device, MIDI_ProgramEventData eventData); \ No newline at end of file +public delegate void MIDI_ProgramEventHandler(MIDI_InputDevice device, MIDI_ProgramEventData eventData); + +[DataModelType] +public delegate void MIDI_SystemRealtimeEventHandler(MIDI_InputDevice device, MIDI_SystemRealtimeEventData eventData); \ No newline at end of file diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_CC_Event.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_CC_Event.cs index ab9afb6..6da80c3 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_CC_Event.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_CC_Event.cs @@ -81,7 +81,7 @@ private void WriteCCEventData(in MIDI_CC_EventData eventData, FrooxEngineContext ControllerDefinition.Write(MIDI_CC_Definition.UNDEFINED, context); } Value.Write(eventData.value, context); - NormalizedValue.Write(eventData.value / 127f, context); + NormalizedValue.Write(eventData.normalizedValue, context); } private void OnControl(MIDI_InputDevice device, in MIDI_CC_EventData eventData, FrooxEngineContext context) diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ChannelAftertouchEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ChannelAftertouchEvent.cs index 29317c6..59363c9 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ChannelAftertouchEvent.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ChannelAftertouchEvent.cs @@ -69,7 +69,7 @@ private void WriteChannelAftertouchEventData(in MIDI_ChannelAftertouchEventData { Channel.Write(eventData.channel, context); Pressure.Write(eventData.pressure, context); - NormalizedPressure.Write(eventData.pressure / 127f, context); + NormalizedPressure.Write(eventData.normalizedPressure, context); } private void OnChannelAftertouch(MIDI_InputDevice device, in MIDI_ChannelAftertouchEventData eventData, FrooxEngineContext context) diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_NoteEvents.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_NoteEvents.cs index d3356f8..b775be6 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_NoteEvents.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_NoteEvents.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using Newtonsoft.Json.Linq; using ProtoFlux.Core; using ProtoFlux.Runtimes.Execution; using Elements.Core; @@ -86,7 +85,7 @@ private void WriteNoteOnOffEventData(in MIDI_NoteEventData eventData, FrooxEngin Channel.Write(eventData.channel, context); Note.Write(eventData.note, context); Velocity.Write(eventData.velocity, context); - NormalizedVelocity.Write(eventData.velocity / 127f, context); + NormalizedVelocity.Write(eventData.normalizedVelocity, context); } private void OnNoteOn(MIDI_InputDevice device, in MIDI_NoteEventData eventData, FrooxEngineContext context) diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PitchWheelEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PitchWheelEvent.cs index 4a1c54c..c32069b 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PitchWheelEvent.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PitchWheelEvent.cs @@ -1,13 +1,12 @@ using System; using System.Linq; -using Newtonsoft.Json.Linq; using ProtoFlux.Core; -using ProtoFlux.Runtimes.Execution; using Elements.Core; using FrooxEngine; using FrooxEngine.ProtoFlux; using Obsidian.Elements; using Components.Devices.MIDI; +using ProtoFlux.Runtimes.Execution; namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Devices; @@ -70,7 +69,7 @@ private void WritePitchEventData(in MIDI_PitchWheelEventData eventData, FrooxEng Value.Write(eventData.value, context); // should be 1 at 16383, -1 at 0 - NormalizedValue.Write(eventData.value == 8192 ? 0f : MathX.Remap(eventData.value, 0f, 16383f, -1f, 1f), context); + NormalizedValue.Write(eventData.normalizedValue, context); } private void OnPitch(MIDI_InputDevice device, in MIDI_PitchWheelEventData eventData, FrooxEngineContext context) diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PolyphonicAftertouchEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PolyphonicAftertouchEvent.cs index 9eb19be..d65c205 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PolyphonicAftertouchEvent.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PolyphonicAftertouchEvent.cs @@ -72,7 +72,7 @@ private void WritePolyphonicAftertouchEventData(in MIDI_PolyphonicAftertouchEven Channel.Write(eventData.channel, context); Note.Write(eventData.note, context); Pressure.Write(eventData.pressure, context); - NormalizedPressure.Write(eventData.pressure / 127f, context); + NormalizedPressure.Write(eventData.normalizedPressure, context); } private void OnPolyphonicAftertouch(MIDI_InputDevice device, in MIDI_PolyphonicAftertouchEventData eventData, FrooxEngineContext context) diff --git a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_SystemRealtimeEvents.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_SystemRealtimeEvents.cs new file mode 100644 index 0000000..b8ff0ea --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_SystemRealtimeEvents.cs @@ -0,0 +1,201 @@ +using System; +using System.Linq; +using Newtonsoft.Json.Linq; +using ProtoFlux.Core; +using ProtoFlux.Runtimes.Execution; +using Elements.Core; +using FrooxEngine; +using FrooxEngine.ProtoFlux; +using Obsidian.Elements; +using Components.Devices.MIDI; + +namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Devices; + +[NodeName("MIDI System Realtime Events")] +[NodeCategory("Obsidian/Devices/MIDI")] +public class MIDI_SystemRealtimeEvents : VoidNode +{ + public readonly GlobalRef Device; + + public Call Clock; + + public Call Tick; + + public Call Start; + + public Call Stop; + + public Call Continue; + + public Call ActiveSense; + + public Call Reset; + + private ObjectStore _currentDevice; + + private ObjectStore _clock; + + private ObjectStore _tick; + + private ObjectStore _start; + + private ObjectStore _stop; + + private ObjectStore _continue; + + private ObjectStore _activeSense; + + private ObjectStore _reset; + + public override bool CanBeEvaluated => false; + + private void OnDeviceChanged(MIDI_InputDevice device, FrooxEngineContext context) + { + MIDI_InputDevice device2 = _currentDevice.Read(context); + if (device == device2) + { + return; + } + if (device2 != null) + { + device2.MidiClock -= _clock.Read(context); + device2.MidiTick -= _tick.Read(context); + device2.MidiStart -= _start.Read(context); + device2.MidiStop -= _stop.Read(context); + device2.MidiContinue -= _continue.Read(context); + device2.ActiveSense -= _activeSense.Read(context); + device2.Reset -= _reset.Read(context); + } + if (device != null) + { + NodeContextPath path = context.CaptureContextPath(); + context.GetEventDispatcher(out var dispatcher); + MIDI_SystemRealtimeEventHandler value = delegate (MIDI_InputDevice dev, MIDI_SystemRealtimeEventData e) + { + dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) + { + OnClock(dev, in e, c); + }); + }; + MIDI_SystemRealtimeEventHandler value2 = delegate (MIDI_InputDevice dev, MIDI_SystemRealtimeEventData e) + { + dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) + { + OnTick(dev, in e, c); + }); + }; + MIDI_SystemRealtimeEventHandler value3 = delegate (MIDI_InputDevice dev, MIDI_SystemRealtimeEventData e) + { + dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) + { + OnStart(dev, in e, c); + }); + }; + MIDI_SystemRealtimeEventHandler value4 = delegate (MIDI_InputDevice dev, MIDI_SystemRealtimeEventData e) + { + dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) + { + OnStop(dev, in e, c); + }); + }; + MIDI_SystemRealtimeEventHandler value5 = delegate (MIDI_InputDevice dev, MIDI_SystemRealtimeEventData e) + { + dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) + { + OnContinue(dev, in e, c); + }); + }; + MIDI_SystemRealtimeEventHandler value6 = delegate (MIDI_InputDevice dev, MIDI_SystemRealtimeEventData e) + { + dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) + { + OnActiveSense(dev, in e, c); + }); + }; + MIDI_SystemRealtimeEventHandler value7 = delegate (MIDI_InputDevice dev, MIDI_SystemRealtimeEventData e) + { + dispatcher.ScheduleEvent(path, delegate (FrooxEngineContext c) + { + OnReset(dev, in e, c); + }); + }; + _currentDevice.Write(device, context); + _clock.Write(value, context); + _tick.Write(value2, context); + _start.Write(value3, context); + _stop.Write(value4, context); + _continue.Write(value5, context); + _activeSense.Write(value6, context); + _reset.Write(value7, context); + device.MidiClock += value; + device.MidiTick += value2; + device.MidiStart += value3; + device.MidiStop += value4; + device.MidiContinue += value5; + device.ActiveSense += value6; + device.Reset += value7; + } + else + { + _currentDevice.Clear(context); + _clock.Clear(context); + _tick.Clear(context); + _start.Clear(context); + _stop.Clear(context); + _continue.Clear(context); + _activeSense.Clear(context); + _reset.Clear(context); + } + } + + private void WriteSystemRealtimeEventData(in MIDI_SystemRealtimeEventData eventData, FrooxEngineContext context) + { + } + + private void OnClock(MIDI_InputDevice device, in MIDI_SystemRealtimeEventData eventData, FrooxEngineContext context) + { + WriteSystemRealtimeEventData(eventData, context); + Clock.Execute(context); + } + + private void OnTick(MIDI_InputDevice device, in MIDI_SystemRealtimeEventData eventData, FrooxEngineContext context) + { + WriteSystemRealtimeEventData(eventData, context); + Tick.Execute(context); + } + + private void OnStart(MIDI_InputDevice device, in MIDI_SystemRealtimeEventData eventData, FrooxEngineContext context) + { + WriteSystemRealtimeEventData(eventData, context); + Start.Execute(context); + } + + private void OnStop(MIDI_InputDevice device, in MIDI_SystemRealtimeEventData eventData, FrooxEngineContext context) + { + WriteSystemRealtimeEventData(eventData, context); + Stop.Execute(context); + } + + private void OnContinue(MIDI_InputDevice device, in MIDI_SystemRealtimeEventData eventData, FrooxEngineContext context) + { + WriteSystemRealtimeEventData(eventData, context); + Continue.Execute(context); + } + + private void OnActiveSense(MIDI_InputDevice device, in MIDI_SystemRealtimeEventData eventData, FrooxEngineContext context) + { + WriteSystemRealtimeEventData(eventData, context); + ActiveSense.Execute(context); + } + + private void OnReset(MIDI_InputDevice device, in MIDI_SystemRealtimeEventData eventData, FrooxEngineContext context) + { + WriteSystemRealtimeEventData(eventData, context); + Reset.Execute(context); + } + + public MIDI_SystemRealtimeEvents() + { + Device = new GlobalRef(this, 0); + } +} \ No newline at end of file diff --git a/ProjectObsidian/Settings/MIDI_Settings.cs b/ProjectObsidian/Settings/MIDI_Settings.cs index a6d3b71..0614e33 100644 --- a/ProjectObsidian/Settings/MIDI_Settings.cs +++ b/ProjectObsidian/Settings/MIDI_Settings.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using FrooxEngine; using Elements.Core; using Elements.Assets; @@ -105,17 +103,12 @@ protected override void OnStart() // Sometimes the locale is null in here, so wait a bit I guess - RunInUpdates(30, () => + RunInUpdates(15, () => { UpdateLocale(); Settings.RegisterValueChanges(UpdateLocale); RefreshDeviceLists(); }); - - //MidiAccessManager.Default.StateChanged += (object sender, MidiConnectionEventArgs args) => - //{ - // UniLog.Log("midi access state changed"); - //}; } protected override void OnDispose() From 453eec6ebd2bdf56c96deeb924bf7fbb8adf99d8 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 23 Jul 2024 01:39:39 +0100 Subject: [PATCH 4/5] Cleanup and set debug buffered messages count to zero after buffer flush --- .../Devices/MIDI/MIDI_InputDevice.cs | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs b/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs index 93de92f..7bd1c4a 100644 --- a/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs +++ b/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs @@ -58,6 +58,26 @@ public class MIDI_InputDevice : Component private const bool DEBUG = true; + private struct TimestampedMidiEvent + { + public MidiEvent midiEvent; + public long timestamp; + public TimestampedMidiEvent(MidiEvent _midiEvent, long _timestamp) + { + midiEvent = _midiEvent; + timestamp = _timestamp; + } + } + + // I am using this like a Queue so it could possibly be turned into a Queue instead... + private List _eventBuffer = new(); + + private const long MESSAGE_BUFFER_TIME_MILLISECONDS = 3; + + private long _lastMessageBufferStartTime = 0; + + private int _bufferedMessagesToHandle = 0; + protected override void OnStart() { base.OnStart(); @@ -91,7 +111,7 @@ private async Task ReleaseDeviceAsync() UniLog.Log("Device released."); _inputDevice = null; _eventBuffer.Clear(); - _lastBatchStartTime = 0; + _lastMessageBufferStartTime = 0; } private async void ReleaseDeviceAndConnectAsync(IMidiAccess access, string deviceId) @@ -207,22 +227,6 @@ private ushort CombineBytes(byte First, byte Second) return _14bit; } - private struct TimestampedMidiEvent - { - public MidiEvent midiEvent; - public long timestamp; - public TimestampedMidiEvent(MidiEvent _midiEvent, long _timestamp) - { - midiEvent = _midiEvent; - timestamp = _timestamp; - } - } - - // I am using this like a Queue so it could possibly be turned into a Queue instead... - private List _eventBuffer = new(); - - private const long BATCH_TIME_SIZE_MILLISECONDS = 3; - private bool IsCCFineMessage() { if (_eventBuffer.Count == 0) return false; @@ -313,12 +317,9 @@ private void FlushMessageBuffer() // Just in case some messages got lost somehow UniLog.Warning("Did not handle all buffered messages! " + _bufferedMessagesToHandle.ToString()); } + _bufferedMessagesToHandle = 0; } - private long _lastBatchStartTime = 0; - - private int _bufferedMessagesToHandle = 0; - private async void OnMessageReceived(object sender, MidiReceivedEventArgs args) { if (DEBUG) UniLog.Log($"*** New midi message"); @@ -327,8 +328,6 @@ private async void OnMessageReceived(object sender, MidiReceivedEventArgs args) var events = MidiEvent.Convert(args.Data, args.Start, args.Length); - //if (events.Count() == 0) return; - if (args.Length == 1) { // system realtime message, do not buffer these, execute immediately @@ -417,11 +416,11 @@ private async void OnMessageReceived(object sender, MidiReceivedEventArgs args) _bufferedMessagesToHandle += 1; } - if (events.Count() > 0 && args.Timestamp - _lastBatchStartTime > BATCH_TIME_SIZE_MILLISECONDS) + if (events.Count() > 0 && args.Timestamp - _lastMessageBufferStartTime > MESSAGE_BUFFER_TIME_MILLISECONDS) { - _lastBatchStartTime = args.Timestamp; + _lastMessageBufferStartTime = args.Timestamp; if (DEBUG) UniLog.Log("* New message batch created: " + args.Timestamp.ToString()); - await Task.Delay((int)BATCH_TIME_SIZE_MILLISECONDS); + await Task.Delay((int)MESSAGE_BUFFER_TIME_MILLISECONDS); FlushMessageBuffer(); } } From 8502f5557eabd01d015545703ff9621d15cae6f3 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 23 Jul 2024 02:15:08 +0100 Subject: [PATCH 5/5] Turn off debug (I always forget this) --- ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs b/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs index 7bd1c4a..c8c7cec 100644 --- a/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs +++ b/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs @@ -56,7 +56,7 @@ public class MIDI_InputDevice : Component public event MIDI_SystemRealtimeEventHandler Reset; - private const bool DEBUG = true; + private const bool DEBUG = false; private struct TimestampedMidiEvent {