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/MIDI_InputDevice.cs b/ProjectObsidian/Components/Devices/MIDI/MIDI_InputDevice.cs index 7716827..c8c7cec 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,8 +20,6 @@ public class MIDI_InputDevice : Component public readonly UserRef HandlingUser; - public readonly Sync _lastEvent; - private bool _lastIsConnected; private IMidiInput _inputDevice; @@ -35,23 +31,59 @@ 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; + 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 = false; + 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(); Settings.GetActiveSetting(); Settings.RegisterValueChanges(OnInputDeviceSettingsChanged); - RunInUpdates(7, Update); + RunInUpdates(30, Update); } private void OnInputDeviceSettingsChanged(MIDI_Settings setting) @@ -62,13 +94,7 @@ private void OnInputDeviceSettingsChanged(MIDI_Settings setting) protected override void OnChanges() { - //UniLog.Log("OnChanges"); base.OnChanges(); - if (_lastEvent.WasChanged) - { - _lastEvent.WasChanged = false; - return; - } if (IsConnected.WasChanged) { IsConnected.Value = _lastIsConnected; @@ -78,12 +104,26 @@ protected override void OnChanges() Update(); } - private async void ReleaseDeviceAsync() + private async Task ReleaseDeviceAsync() { UniLog.Log("Releasing device..."); - await _inputDevice.CloseAsync(); + await Task.WhenAny(_inputDevice.CloseAsync(), Task.Delay(5000)); UniLog.Log("Device released."); _inputDevice = null; + _eventBuffer.Clear(); + _lastMessageBufferStartTime = 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() @@ -150,7 +190,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; } @@ -159,10 +199,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 { @@ -190,53 +227,71 @@ private ushort CombineBytes(byte First, byte Second) return _14bit; } - private void OnMessageReceived(object sender, MidiReceivedEventArgs args) + private bool IsCCFineMessage() { - 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) + 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[0].midiEvent.Msb == _eventBuffer[1].midiEvent.Msb - 32) { - if (DEBUG) UniLog.Log(e.ToString()); - RunSynchronously(() => + return true; + } + return false; + } + + private void FlushMessageBuffer() + { + if (_eventBuffer.Count == 0) + { + UniLog.Log("Message buffer empty."); + return; + } + + var batchStartTime = _eventBuffer[0].timestamp; + if (DEBUG) UniLog.Log("Flushing message buffer from start time: " + batchStartTime.ToString()); + + while (_eventBuffer.Count > 0) + { + + while (IsCCFineMessage()) { - _lastEvent.Value = e.ToString(); - }); + 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, _coarse: false)); + _eventBuffer.RemoveRange(0, 2); + _bufferedMessagesToHandle -= 2; + } + + if (_eventBuffer.Count == 0) break; + + var e = _eventBuffer[0].midiEvent; + if (DEBUG) UniLog.Log(e.ToString()); switch (e.EventType) { - case MidiEvent.NoteOn: - NoteOn?.Invoke(this, new MIDI_NoteEventData(e.Channel, e.Msb, e.Lsb)); - break; - case MidiEvent.NoteOff: - NoteOff?.Invoke(this, new MIDI_NoteEventData(e.Channel, e.Msb, e.Lsb)); - break; - case MidiEvent.CAf: - ChannelPressure?.Invoke(this, new MIDI_ChannelPressureEventData(e.Channel, e.Msb)); - break; case MidiEvent.CC: - Control?.Invoke(this, new MIDI_CC_EventData(e.Channel, e.Msb, e.Lsb)); + if (DEBUG) UniLog.Log("CC"); + Control?.Invoke(this, new MIDI_CC_EventData(e.Channel, e.Msb, e.Lsb, _coarse: true)); break; - case MidiEvent.Pitch: - PitchWheel?.Invoke(this, new MIDI_PitchWheelEventData(e.Channel, CombineBytes(e.Msb, e.Lsb))); - break; - case MidiEvent.PAf: - Aftertouch?.Invoke(this, new MIDI_AftertouchEventData(e.Channel, e.Msb, e.Lsb)); + // 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)); 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"); 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"); break; case MidiEvent.MtcQuarterFrame: if (DEBUG) UniLog.Log("UnhandledEvent: MtcQuarterFrame"); @@ -250,31 +305,123 @@ private void OnMessageReceived(object sender, MidiReceivedEventArgs args) 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"); + 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()); + } + _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 (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); + switch (e.StatusByte) + { + case MidiEvent.MidiClock: + if (DEBUG) UniLog.Log("* MidiClock"); + MidiClock?.Invoke(this, new MIDI_SystemRealtimeEventData()); + break; + case MidiEvent.MidiTick: + if (DEBUG) UniLog.Log("* MidiTick"); + MidiTick?.Invoke(this, new MIDI_SystemRealtimeEventData()); + break; + case MidiEvent.MidiStart: + if (DEBUG) UniLog.Log("* MidiStart"); + MidiStart?.Invoke(this, new MIDI_SystemRealtimeEventData()); + break; + case MidiEvent.MidiStop: + if (DEBUG) UniLog.Log("* MidiStop"); + MidiStop?.Invoke(this, new MIDI_SystemRealtimeEventData()); + break; + case MidiEvent.MidiContinue: + if (DEBUG) UniLog.Log("* MidiContinue"); + MidiContinue?.Invoke(this, new MIDI_SystemRealtimeEventData()); + break; + case MidiEvent.ActiveSense: + if (DEBUG) UniLog.Log("* ActiveSense"); + ActiveSense?.Invoke(this, new MIDI_SystemRealtimeEventData()); + break; + case MidiEvent.Reset: + // Same as Meta + if (DEBUG) UniLog.Log("* Reset"); + Reset?.Invoke(this, new MIDI_SystemRealtimeEventData()); + 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); + + switch (e.EventType) + { + 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 - _lastMessageBufferStartTime > MESSAGE_BUFFER_TIME_MILLISECONDS) + { + _lastMessageBufferStartTime = args.Timestamp; + if (DEBUG) UniLog.Log("* New message batch created: " + args.Timestamp.ToString()); + await Task.Delay((int)MESSAGE_BUFFER_TIME_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 4bd161d..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,29 @@ public enum MIDI_CC_Definition PolyModeOn = 127 } +[DataModelType] +public readonly struct MIDI_SystemRealtimeEventData +{ + public MIDI_SystemRealtimeEventData() + { + // owo + } +} + +[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 { @@ -98,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; @@ -114,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; @@ -123,13 +151,15 @@ 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 readonly float normalizedPressure => pressure / 127f; + + public MIDI_ChannelAftertouchEventData(in int _channel, in int _pressure) { channel = _channel; pressure = _pressure; @@ -137,7 +167,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; @@ -145,7 +175,9 @@ public readonly struct MIDI_AftertouchEventData public readonly int pressure; - public MIDI_AftertouchEventData(in int _channel, in int _note, in int _pressure) + public readonly float normalizedPressure => pressure / 127f; + + public MIDI_PolyphonicAftertouchEventData(in int _channel, in int _note, in int _pressure) { channel = _channel; note = _note; @@ -162,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; } } @@ -174,13 +211,19 @@ 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); + +[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_ChannelPressureEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ChannelAftertouchEvent.cs similarity index 59% rename from ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ChannelPressureEvent.cs rename to ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ChannelAftertouchEvent.cs index 7c0560c..59363c9 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ChannelPressureEvent.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ChannelAftertouchEvent.cs @@ -11,13 +11,14 @@ namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Devices; -[NodeName("MIDI Channel Pressure Event")] +[NodeName("MIDI Channel Aftertouch Event")] [NodeCategory("Obsidian/Devices/MIDI")] -public class MIDI_ChannelPressureEvent : VoidNode +[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); + NormalizedPressure.Write(eventData.normalizedPressure, 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/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_AftertouchEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PolyphonicAftertouchEvent.cs similarity index 61% rename from ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_AftertouchEvent.cs rename to ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PolyphonicAftertouchEvent.cs index 971854a..d65c205 100644 --- a/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_AftertouchEvent.cs +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_PolyphonicAftertouchEvent.cs @@ -13,11 +13,12 @@ namespace ProtoFlux.Runtimes.Execution.Nodes.Obsidian.Devices; [NodeName("MIDI Polyphonic Aftertouch Event")] [NodeCategory("Obsidian/Devices/MIDI")] -public class MIDI_AftertouchEvent : VoidNode +[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,45 +43,45 @@ 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); Pressure.Write(eventData.pressure, context); - NormalizedPressure.Write(eventData.pressure / 127f, context); + NormalizedPressure.Write(eventData.normalizedPressure, 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/MIDI_ProgramEvent.cs b/ProjectObsidian/ProtoFlux/Devices/MIDI/MIDI_ProgramEvent.cs new file mode 100644 index 0000000..ef80a5a --- /dev/null +++ b/ProjectObsidian/ProtoFlux/Devices/MIDI/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 Components.Devices.MIDI; + +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) + { + OnProgram(dev, in e, c); + }); + }; + _currentDevice.Write(device, context); + _program.Write(value, context); + device.Program += value; + } + else + { + _currentDevice.Clear(context); + _program.Clear(context); + } + } + + private void WriteProgramEventData(in MIDI_ProgramEventData eventData, FrooxEngineContext context) + { + Channel.Write(eventData.channel, context); + ProgramValue.Write(eventData.program, context); + } + + private void OnProgram(MIDI_InputDevice device, in MIDI_ProgramEventData eventData, FrooxEngineContext context) + { + WriteProgramEventData(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/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 c8c8b5c..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; @@ -104,13 +102,13 @@ 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(15, () => { UpdateLocale(); Settings.RegisterValueChanges(UpdateLocale); + RefreshDeviceLists(); }); - - RefreshDeviceLists(); } protected override void OnDispose()