diff --git a/build/staging/reg/WinRTActivationEntries.cs b/build/staging/reg/WinRTActivationEntries.cs index 136c10fa7..7d4b1af0c 100644 --- a/build/staging/reg/WinRTActivationEntries.cs +++ b/build/staging/reg/WinRTActivationEntries.cs @@ -32,7 +32,8 @@ class RegistryEntries new RegEntry{ ClassName="Windows.Devices.Midi2.MidiChannelEndpointListener", ActivationType=0, Threading=0, TrustLevel=0 }, new RegEntry{ ClassName="Windows.Devices.Midi2.MidiGroupEndpointListener", ActivationType=0, Threading=0, TrustLevel=0 }, new RegEntry{ ClassName="Windows.Devices.Midi2.MidiMessageTypeEndpointListener", ActivationType=0, Threading=0, TrustLevel=0 }, - new RegEntry{ ClassName="Windows.Devices.Midi2.MidiVirtualDevice", ActivationType=0, Threading=0, TrustLevel=0 }, + new RegEntry{ ClassName="Windows.Devices.Midi2.MidiVirtualEndpointDevice", ActivationType=0, Threading=0, TrustLevel=0 }, + new RegEntry{ ClassName="Windows.Devices.Midi2.MidiVirtualEndpointDeviceDefinition", ActivationType=0, Threading=0, TrustLevel=0 }, new RegEntry{ ClassName="Windows.Devices.Midi2.MidiMessageReceivedEventArgs", ActivationType=0, Threading=0, TrustLevel=0 }, new RegEntry{ ClassName="Windows.Devices.Midi2.MidiEndpointConnection", ActivationType=0, Threading=0, TrustLevel=0 }, new RegEntry{ ClassName="Windows.Devices.Midi2.IMidiEndpointConnectionStatics", ActivationType=0, Threading=0, TrustLevel=0 }, diff --git a/build/staging/reg/WinRTActivationEntries.xml b/build/staging/reg/WinRTActivationEntries.xml index 6185867fa..6dbc0886f 100644 --- a/build/staging/reg/WinRTActivationEntries.xml +++ b/build/staging/reg/WinRTActivationEntries.xml @@ -134,7 +134,12 @@ trustLevel="Base" /> + diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi index c53aa8e4a..be10a3a13 100644 --- a/build/staging/version/BundleInfo.wxi +++ b/build/staging/version/BundleInfo.wxi @@ -1,4 +1,4 @@ - + diff --git a/diagnostics/trace-logging/TraceCaptureFile.etl b/diagnostics/trace-logging/TraceCaptureFile.etl index 977cde1eb..cbe6e49e2 100644 Binary files a/diagnostics/trace-logging/TraceCaptureFile.etl and b/diagnostics/trace-logging/TraceCaptureFile.etl differ diff --git a/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/App.xaml.cs b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/App.xaml.cs index eef3d66ba..0e264bd07 100644 --- a/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/App.xaml.cs +++ b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/App.xaml.cs @@ -16,9 +16,6 @@ using Windows.Foundation; using Windows.Foundation.Collections; -// To learn more about WinUI, the WinUI project structure, -// and more about our project templates, see: http://aka.ms/winui-project-info. - namespace MidiSample.AppToAppMidi { /// @@ -45,6 +42,6 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar m_window.Activate(); } - private Window m_window; + private Microsoft.UI.Xaml.Window m_window; } } diff --git a/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MainWindow.xaml b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MainWindow.xaml index 3197904a9..c105badb4 100644 --- a/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MainWindow.xaml +++ b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MainWindow.xaml @@ -8,54 +8,30 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - - - - - - - - - - - - - - - + + + - - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - diff --git a/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MainWindow.xaml.cs b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MainWindow.xaml.cs index a8dad0485..7e36cd5bb 100644 --- a/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MainWindow.xaml.cs +++ b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MainWindow.xaml.cs @@ -14,66 +14,129 @@ using Windows.AI.MachineLearning; using Windows.Foundation; using Windows.Foundation.Collections; - +using WinUIEx; using midi2 = Windows.Devices.Midi2; -// To learn more about WinUI, the WinUI project structure, -// and more about our project templates, see: http://aka.ms/winui-project-info. namespace MidiSample.AppToAppMidi { - /// - /// An empty window that can be used on its own or navigated to within a Frame. - /// - public sealed partial class MainWindow : Window - { + + public sealed partial class MainWindow : Microsoft.UI.Xaml.Window + { private midi2.MidiSession _session; private midi2.MidiEndpointConnection _connection; + public List Notes { get; } + public MainWindow() { this.InitializeComponent(); - this.Closed += MainWindow_Closed; + OpenConnection(); - this.AppWindow.MoveAndResize(new Windows.Graphics.RectInt32(100, 100, 600, 600)); + var notes = new byte[] { 50, 52, 53, 55, 57, 58, 60, 62, 64, 65, 67, 69, 70, 72, 74, 76 }; + + Notes = notes.Select(n=>new Note() { NoteNumber = n, Connection = _connection, GroupIndex = 0, ChannelIndex = 0 }).ToList(); - OpenConnection(); + //this.Closed += MainWindow_Closed; + + //this.AppWindow.MoveAndResize(new Windows.Graphics.RectInt32(100, 100, 600, 600)); + + this.SetWindowSize(500, 550); + this.SetIsAlwaysOnTop(true); + + this.Closed += MainWindow_Closed; } private void MainWindow_Closed(object sender, WindowEventArgs args) { + _session.DisconnectEndpointConnection(_connection.ConnectionId); _session.Dispose(); } + // in MIDI Services config file in Virtual Device MIDI section + //"createVirtualDevices": + //[ + // { + // "associationIdentifier" : "{1EDD815E-B44B-488D-96EE-E8C81093D6AC}", + // "shortUniqueId": "PMB_APP2_8675309", + // "name": "Pad Controller", + // "description" : "App to app MIDI pad controller device app" + // } + //] + // + //const string _appDeviceConnectionId = "\\\\?\\SWD#MIDISRV#MIDIU_VIRTDEV_PMB_APP2_8675309#{e7cce071-3c03-423f-88d3-f1045d02552b}"; private void OpenConnection() { - _session = midi2.MidiSession.CreateSession("App to app MIDI sample"); - _connection = _session.CreateEndpointConnection(midi2.MidiEndpointDeviceInformation.DiagnosticsLoopbackAEndpointId); - _connection.Open(); - } + System.Diagnostics.Debug.WriteLine("Open Connection enter"); - private byte GetMidiNoteNumberFromPad(Rectangle pad) - { - return byte.Parse(pad.Tag!.ToString()); - } - private void OnPadPointerPressed(object sender, PointerRoutedEventArgs e) - { - byte note = GetMidiNoteNumberFromPad((Rectangle)sender); - var message = midi2.MidiMessageBuilder.BuildMidi2ChannelVoiceMessage(0, 0, midi2.Midi2ChannelVoiceMessageStatus.NoteOn, 0, note, 1000); + // create our function blocks and endpoint info to be reported back through MIDI - _connection.SendMessagePacket(message); - } + var deviceDefinition = new midi2.MidiVirtualEndpointDeviceDefinition(); - private void OnPadPointerReleased(object sender, PointerRoutedEventArgs e) - { - byte note = GetMidiNoteNumberFromPad((Rectangle)sender); + deviceDefinition.FunctionBlocks.Add(new midi2.MidiFunctionBlock() + { + Number = 0, + IsActive = true, + Name = "Pads Output", + UIHint = midi2.MidiFunctionBlockUIHint.Sender, + FirstGroupIndex = 0, + GroupCount = 1, + Direction = midi2.MidiFunctionBlockDirection.Bidirectional, + Midi10Connection = midi2.MidiFunctionBlockMidi10.Not10, + MaxSystemExclusive8Streams = 0, + MidiCIMessageVersionFormat = 0 + }) ; + + deviceDefinition.AreFunctionBlocksStatic = true; + deviceDefinition.EndpointName = "Pad Controller App"; + deviceDefinition.EndpointProductInstanceId = "PMB_APP2_8675309"; // this needs to match pre-configuration for now + deviceDefinition.SupportsMidi2ProtocolMessages = true; + deviceDefinition.SupportsMidi1ProtocolMessages = true; + deviceDefinition.SupportsReceivingJRTimestamps = false; + deviceDefinition.SupportsSendingJRTimestamps = false; - var message = midi2.MidiMessageBuilder.BuildMidi2ChannelVoiceMessage(0, 0, midi2.Midi2ChannelVoiceMessageStatus.NoteOff, 0, note, 1000); + System.Diagnostics.Debug.WriteLine("Creating session"); - _connection.SendMessagePacket(message); + _session = midi2.MidiSession.CreateSession("App to app MIDI sample"); + + if (_session != null) + { + System.Diagnostics.Debug.WriteLine("Creating virtual device"); + + _connection = _session.CreateVirtualDeviceAndConnection(deviceDefinition); + + if (_connection != null) + { + _connection.MessageReceived += _connection_MessageReceived; + + System.Diagnostics.Debug.WriteLine("Connection created. About to open it."); + + if (_connection.Open()) + { + System.Diagnostics.Debug.WriteLine("Connection Opened"); + + this.AppWindow.Title = "App-to-app MIDI Pad Controller: Connected"; + } + else + { + System.Diagnostics.Debug.WriteLine("Connection Open Failed"); + this.AppWindow.Title = "App-to-app MIDI Pad Controller: (no connection)"; + } + } + } + else + { + System.Diagnostics.Debug.WriteLine("Session Open Failed"); + // unable to open session + } + } + + private void _connection_MessageReceived(midi2.IMidiMessageReceivedEventSource sender, midi2.MidiMessageReceivedEventArgs args) + { + System.Diagnostics.Debug.WriteLine("Message Received " + midi2.MidiMessageUtility.GetMessageFriendlyNameFromFirstWord(args.PeekFirstWord())); } } } diff --git a/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MidiSample.AppToAppMidi.csproj b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MidiSample.AppToAppMidi.csproj index 7258209dd..4b5cb392c 100644 --- a/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MidiSample.AppToAppMidi.csproj +++ b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/MidiSample.AppToAppMidi.csproj @@ -27,9 +27,10 @@ - - - + + + + diff --git a/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/Note.cs b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/Note.cs new file mode 100644 index 000000000..36b1bab6d --- /dev/null +++ b/get-started/midi-developers/app-developers/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi/Note.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using midi2 = Windows.Devices.Midi2; + +namespace MidiSample.AppToAppMidi +{ + + public class Note + { + public midi2.MidiEndpointConnection Connection { get; set; } + public byte NoteNumber { get; set; } + + public byte GroupIndex { get; set; } + + public byte ChannelIndex { get; set; } + + public void NoteOn() => Connection.SendMessagePacket( + midi2.MidiMessageBuilder.BuildMidi2ChannelVoiceMessage( + 0, + GroupIndex, + midi2.Midi2ChannelVoiceMessageStatus.NoteOn, + ChannelIndex, + (ushort)((ushort)NoteNumber << 8), + 1000)); + public void NoteOff() => Connection.SendMessagePacket( + midi2.MidiMessageBuilder.BuildMidi2ChannelVoiceMessage( + 0, + GroupIndex, + midi2.Midi2ChannelVoiceMessageStatus.NoteOff, + ChannelIndex, + (ushort)((ushort)NoteNumber << 8), + 0)); + } + + +} diff --git a/src/api/Abstraction/DiagnosticsAbstraction/Midi2.DiagnosticsEndpointManager.cpp b/src/api/Abstraction/DiagnosticsAbstraction/Midi2.DiagnosticsEndpointManager.cpp index dc3e46104..e41fde02f 100644 --- a/src/api/Abstraction/DiagnosticsAbstraction/Midi2.DiagnosticsEndpointManager.cpp +++ b/src/api/Abstraction/DiagnosticsAbstraction/Midi2.DiagnosticsEndpointManager.cpp @@ -118,7 +118,7 @@ CMidi2DiagnosticsEndpointManager::CreateLoopbackEndpoint( DEVPROP_BOOLEAN devPropTrue = DEVPROP_TRUE; DEVPROP_BOOLEAN devPropFalse = DEVPROP_FALSE; BYTE nativeDataFormat = MIDI_PROP_NATIVEDATAFORMAT_UMP; - uint32_t supportedDataFormat = (BYTE)MidiDataFormat::MidiDataFormat_UMP; + UINT32 supportedDataFormat = (UINT32)MidiDataFormat::MidiDataFormat_UMP; std::wstring description = L"Diagnostics loopback endpoint. For testing purposes."; @@ -131,7 +131,7 @@ CMidi2DiagnosticsEndpointManager::CreateLoopbackEndpoint( DEVPROP_TYPE_EMPTY, 0, nullptr}, {{PKEY_MIDI_SupportedDataFormats, DEVPROP_STORE_SYSTEM, nullptr}, - DEVPROP_TYPE_BYTE, static_cast(sizeof(BYTE)), &supportedDataFormat}, + DEVPROP_TYPE_UINT32, static_cast(sizeof(UINT32)), &supportedDataFormat}, {{DEVPKEY_DeviceInterface_FriendlyName, DEVPROP_STORE_SYSTEM, nullptr}, @@ -236,7 +236,7 @@ CMidi2DiagnosticsEndpointManager::CreatePingEndpoint( DEVPROP_BOOLEAN devPropTrue = DEVPROP_TRUE; DEVPROP_BOOLEAN devPropFalse = DEVPROP_FALSE; BYTE nativeDataFormat = MIDI_PROP_NATIVEDATAFORMAT_UMP; - BYTE supportedDataFormat = (BYTE)MidiDataFormat::MidiDataFormat_UMP; + UINT32 supportedDataFormat = (UINT32)MidiDataFormat::MidiDataFormat_UMP; auto endpointPurpose = (uint32_t)MidiEndpointDevicePurposePropertyValue::DiagnosticPing; @@ -252,7 +252,7 @@ CMidi2DiagnosticsEndpointManager::CreatePingEndpoint( DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(devPropFalse)), &devPropFalse}, {{PKEY_MIDI_SupportedDataFormats, DEVPROP_STORE_SYSTEM, nullptr}, - DEVPROP_TYPE_BYTE, static_cast(sizeof(BYTE)), &supportedDataFormat}, + DEVPROP_TYPE_UINT32, static_cast(sizeof(UINT32)), &supportedDataFormat}, diff --git a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiAbstraction.vcxproj b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiAbstraction.vcxproj index c0d58ed52..47cdf40bb 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiAbstraction.vcxproj +++ b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiAbstraction.vcxproj @@ -280,6 +280,7 @@ + @@ -298,6 +299,7 @@ + diff --git a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiAbstraction.vcxproj.filters b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiAbstraction.vcxproj.filters index 9bb09290c..d55f91a52 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiAbstraction.vcxproj.filters +++ b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiAbstraction.vcxproj.filters @@ -33,6 +33,9 @@ Source Files + + Source Files + @@ -73,6 +76,9 @@ Header Files + + Header Files + diff --git a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiBidi.cpp b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiBidi.cpp index b2f47c6cd..1b0286447 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiBidi.cpp +++ b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiBidi.cpp @@ -20,65 +20,77 @@ CMidi2VirtualMidiBiDi::Initialize( LONGLONG Context ) { + OutputDebugString(__FUNCTION__ L" - enter\n"); + TraceLoggingWrite( MidiVirtualMidiAbstractionTelemetryProvider::Provider(), - __FUNCTION__, + __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingPointer(this, "this") + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(endpointId, "endpoint id") ); m_callback = Callback; m_callbackContext = Context; - - m_endpointId = internal::ToUpperTrimmedWStringCopy(endpointId); + m_endpointId = internal::NormalizeEndpointInterfaceIdCopy(endpointId); - OutputDebugString(__FUNCTION__ L" Looking up Endpoint:"); - OutputDebugString(m_endpointId.c_str()); - - // This should use SWD properties and not a string search - - if (m_endpointId.find(MIDI_VIRT_INSTANCE_ID_DEVICE_PREFIX) != std::wstring::npos) + //if (Context != MIDI_PROTOCOL_MANAGER_ENDPOINT_CREATION_CONTEXT) { - OutputDebugString(__FUNCTION__ L" - endpoint id is a virtual device\n"); + OutputDebugString(__FUNCTION__ L" Looking up Endpoint:"); + OutputDebugString(m_endpointId.c_str()); - m_isDeviceSide = true; + HRESULT hr = S_OK; - RETURN_IF_FAILED(MidiEndpointTable::Current().OnDeviceConnected(m_endpointId, this)); - } - else if (m_endpointId.find(MIDI_VIRT_INSTANCE_ID_CLIENT_PREFIX) != std::wstring::npos) - { - OutputDebugString(__FUNCTION__ L" - endpoint id is a virtual client\n"); + // This should use SWD properties and not a string search - m_isDeviceSide = false; + if (internal::EndpointInterfaceIdContainsString(m_endpointId, MIDI_VIRT_INSTANCE_ID_DEVICE_PREFIX)) + { + OutputDebugString(__FUNCTION__ L" - endpoint id is a virtual device\n"); - RETURN_IF_FAILED(MidiEndpointTable::Current().OnClientConnected(m_endpointId, this)); + m_isDeviceSide = true; - } - else - { - OutputDebugString(__FUNCTION__ L" - endpoint id is unknown type\n"); - OutputDebugString(m_endpointId.c_str()); + LOG_IF_FAILED(hr = MidiEndpointTable::Current().OnDeviceConnected(m_endpointId, this)); + } + else if (internal::EndpointInterfaceIdContainsString(m_endpointId, MIDI_VIRT_INSTANCE_ID_CLIENT_PREFIX)) + { + OutputDebugString(__FUNCTION__ L" - endpoint id is a virtual client\n"); - // we don't understand this endpoint id + m_isDeviceSide = false; - return E_FAIL; - } + LOG_IF_FAILED(hr = MidiEndpointTable::Current().OnClientConnected(m_endpointId, this)); + } + else + { + OutputDebugString(__FUNCTION__ L" - endpoint id is unknown type\n"); + OutputDebugString(m_endpointId.c_str()); - return S_OK; + // we don't understand this endpoint id + hr = E_FAIL; + } + + return hr; + } + //else + //{ + // // we're in protocol negotiation + // m_isDeviceSide = false; + // return S_OK; + //} } HRESULT CMidi2VirtualMidiBiDi::Cleanup() { - // TODO: Cleanup here needs additional logic to tear down the client endpoint - // when this endpoint goes away + OutputDebugString(__FUNCTION__ L" - enter\n"); TraceLoggingWrite( MidiVirtualMidiAbstractionTelemetryProvider::Provider(), __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingPointer(this, "this") + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(m_endpointId.c_str(), "endpoint id"), + TraceLoggingBool(m_isDeviceSide, "is device side") ); m_callback = nullptr; @@ -86,12 +98,18 @@ CMidi2VirtualMidiBiDi::Cleanup() if (m_isDeviceSide) { + OutputDebugString(__FUNCTION__ L" - this is the device BiDi, so calling OnDeviceDisconnected\n"); + MidiEndpointTable::Current().OnDeviceDisconnected(m_endpointId); } + else + { + OutputDebugString(__FUNCTION__ L" - this is the client BiDi. Nothing needed here.\n"); + } - //m_LinkedClientBiDi->Release(); - m_linkedBiDiCallback = nullptr; - m_linkedBiDi = nullptr; + UnlinkAssociatedBiDi(); + + OutputDebugString(__FUNCTION__ L" - exit\n"); return S_OK; } @@ -104,6 +122,15 @@ CMidi2VirtualMidiBiDi::SendMidiMessage( LONGLONG Position ) { + TraceLoggingWrite( + MidiVirtualMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(m_endpointId.c_str(), "endpoint id"), + TraceLoggingBool(m_isDeviceSide, "is device side") + ); + // message received from the device RETURN_HR_IF_NULL(E_INVALIDARG, Message); @@ -129,6 +156,15 @@ CMidi2VirtualMidiBiDi::Callback( LONGLONG Context ) { + TraceLoggingWrite( + MidiVirtualMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(m_endpointId.c_str(), "endpoint id"), + TraceLoggingBool(m_isDeviceSide, "is device side") + ); + // message received from the client if (m_callback != nullptr) diff --git a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiBidi.h b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiBidi.h index 42e8c2b2c..ae22ae349 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiBidi.h +++ b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiBidi.h @@ -30,11 +30,19 @@ class CMidi2VirtualMidiBiDi : return S_OK; } + HRESULT UnlinkAssociatedBiDi() + { + m_linkedBiDi = nullptr; + m_linkedBiDiCallback = nullptr; + + return S_OK; + } + private: wil::com_ptr_nothrow m_linkedBiDi; - IMidiCallback* m_linkedBiDiCallback; - IMidiCallback* m_callback; + wil::com_ptr_nothrow m_linkedBiDiCallback; + wil::com_ptr_nothrow m_callback; LONGLONG m_callbackContext; diff --git a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.cpp b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.cpp index dbd3a868c..09a918517 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.cpp +++ b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.cpp @@ -65,7 +65,7 @@ _Use_decl_annotations_ HRESULT CMidi2VirtualMidiEndpointManager::Initialize( IUnknown* MidiDeviceManager, - IUnknown* /*midiEndpointProtocolManager*/, + IUnknown* MidiEndpointProtocolManager, LPCWSTR ConfigurationJson ) { @@ -79,8 +79,12 @@ CMidi2VirtualMidiEndpointManager::Initialize( ); RETURN_HR_IF(E_INVALIDARG, nullptr == MidiDeviceManager); + RETURN_HR_IF(E_INVALIDARG, nullptr == MidiEndpointProtocolManager); RETURN_IF_FAILED(MidiDeviceManager->QueryInterface(__uuidof(IMidiDeviceManagerInterface), (void**)&m_MidiDeviceManager)); + RETURN_IF_FAILED(MidiEndpointProtocolManager->QueryInterface(__uuidof(IMidiEndpointProtocolManagerInterface), (void**)&m_MidiProtocolManager)); + + m_TransportAbstractionId = AbstractionLayerGUID; // this is needed so MidiSrv can instantiate the correct transport m_ContainerId = m_TransportAbstractionId; // we use the transport ID as the container ID for convenience @@ -153,6 +157,7 @@ CMidi2VirtualMidiEndpointManager::ApplyJson(json::JsonObject jsonObject) deviceEntry.BaseEndpointName = GetJsonStringValue(jsonEntry, MIDI_VIRT_JSON_DEVICE_PROPERTY_NAME, L""); deviceEntry.Description = GetJsonStringValue(jsonEntry, MIDI_VIRT_JSON_DEVICE_PROPERTY_DESCRIPTION, L""); + // if no association id, or it already exists in the table, bail // if no unique Id, bail or maybe generate one @@ -173,6 +178,37 @@ CMidi2VirtualMidiEndpointManager::ApplyJson(json::JsonObject jsonObject) +_Use_decl_annotations_ +HRESULT +CMidi2VirtualMidiEndpointManager::DeleteClientEndpoint(std::wstring clientShortInstanceId) +{ + OutputDebugString(__FUNCTION__ L" - enter\n"); + + // get the instance id for the device + + if (m_MidiDeviceManager != nullptr) + { + //auto instanceId = GetSwdPropertyInstanceId(clientEndpointInterfaceId); + auto instanceId = internal::NormalizeDeviceInstanceIdCopy(clientShortInstanceId); + + if (instanceId != L"") + { + return m_MidiDeviceManager->RemoveEndpoint(instanceId.c_str()); + } + else + { + OutputDebugString(__FUNCTION__ L" - could not find instanceId property for client\n"); + + return E_FAIL; + } + } + else + { + // null device manager + return E_FAIL; + } +} + HRESULT CMidi2VirtualMidiEndpointManager::CreateParentDevice() @@ -181,7 +217,7 @@ CMidi2VirtualMidiEndpointManager::CreateParentDevice() // the parent device parameters are set by the transport (this) std::wstring parentDeviceName{ TRANSPORT_PARENT_DEVICE_NAME }; - std::wstring parentDeviceId{ TRANSPORT_PARENT_ID }; + std::wstring parentDeviceId{ internal::NormalizeDeviceInstanceIdCopy(TRANSPORT_PARENT_ID) }; SW_DEVICE_CREATE_INFO createInfo = {}; createInfo.cbSize = sizeof(createInfo); @@ -201,9 +237,9 @@ CMidi2VirtualMidiEndpointManager::CreateParentDevice() deviceIdMaxSize )); - m_parentDeviceId = std::wstring(newDeviceId); + m_parentDeviceId = internal::NormalizeDeviceInstanceIdCopy(newDeviceId); - OutputDebugString(__FUNCTION__ L" New parent device id: "); + OutputDebugString(__FUNCTION__ L" New parent device instance id: "); OutputDebugString(newDeviceId); OutputDebugString(L"\n"); @@ -212,10 +248,29 @@ CMidi2VirtualMidiEndpointManager::CreateParentDevice() +_Use_decl_annotations_ +HRESULT +CMidi2VirtualMidiEndpointManager::NegotiateAndRequestMetadata(std::wstring endpointId) +{ + bool preferToSendJRToEndpoint{ false }; + bool preferToReceiveJRFromEndpoint{ false }; + BYTE preferredProtocol{ MIDI_PROP_CONFIGURED_PROTOCOL_MIDI2 }; + WORD negotiationTimeoutMS{ 2000 }; + + RETURN_IF_FAILED(m_MidiProtocolManager->NegotiateAndRequestMetadata( + endpointId.c_str(), + preferToSendJRToEndpoint, + preferToReceiveJRFromEndpoint, + preferredProtocol, + negotiationTimeoutMS + )); + return S_OK; +} _Use_decl_annotations_ -HRESULT CMidi2VirtualMidiEndpointManager::CreateClientVisibleEndpoint( +HRESULT +CMidi2VirtualMidiEndpointManager::CreateClientVisibleEndpoint( MidiVirtualDeviceEndpointEntry& entry ) { @@ -228,7 +283,7 @@ HRESULT CMidi2VirtualMidiEndpointManager::CreateClientVisibleEndpoint( DEVPROP_BOOLEAN devPropTrue = DEVPROP_TRUE; // DEVPROP_BOOLEAN devPropFalse = DEVPROP_FALSE; BYTE nativeDataFormat = MIDI_PROP_NATIVEDATAFORMAT_UMP; - BYTE supportedDataFormat = (BYTE)MidiDataFormat::MidiDataFormat_UMP; + UINT32 supportedDataFormat = (UINT32)MidiDataFormat::MidiDataFormat_UMP; // this purpose is important because it controls how this shows up to clients auto endpointPurpose = (uint32_t)MidiEndpointDevicePurposePropertyValue::NormalMessageEndpoint; @@ -244,7 +299,7 @@ HRESULT CMidi2VirtualMidiEndpointManager::CreateClientVisibleEndpoint( DEVPROP_TYPE_EMPTY, 0, nullptr}, {{PKEY_MIDI_SupportedDataFormats, DEVPROP_STORE_SYSTEM, nullptr}, - DEVPROP_TYPE_BYTE, static_cast(sizeof(BYTE)), &supportedDataFormat}, + DEVPROP_TYPE_UINT32, static_cast(sizeof(UINT32)), &supportedDataFormat}, {{PKEY_MIDI_SupportsMulticlient, DEVPROP_STORE_SYSTEM, nullptr}, DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(devPropTrue)),& devPropTrue}, @@ -291,7 +346,8 @@ HRESULT CMidi2VirtualMidiEndpointManager::CreateClientVisibleEndpoint( createInfo.cbSize = sizeof(createInfo); - std::wstring instanceId = MIDI_VIRT_INSTANCE_ID_CLIENT_PREFIX + entry.ShortUniqueId; + std::wstring instanceId = internal::NormalizeDeviceInstanceIdCopy(MIDI_VIRT_INSTANCE_ID_CLIENT_PREFIX + entry.ShortUniqueId); + createInfo.pszInstanceId = instanceId.c_str(); @@ -325,15 +381,14 @@ HRESULT CMidi2VirtualMidiEndpointManager::CreateClientVisibleEndpoint( // loopback transport. m_MidiDeviceManager->DeleteAllEndpointInProtocolDiscoveredProperties(newDeviceInterfaceId); - // TODO: Invoke the protocol negotiator to now capture updated endpoint info. + // we need this for removal later + entry.CreatedShortClientInstanceId = instanceId; - entry.CreatedClientEndpointId = newDeviceInterfaceId; + entry.CreatedClientEndpointId = internal::NormalizeEndpointInterfaceIdCopy(newDeviceInterfaceId); //MidiEndpointTable::Current().AddCreatedEndpointDevice(entry); //MidiEndpointTable::Current().AddCreatedClient(entry.VirtualEndpointAssociationId, entry.CreatedClientEndpointId); - - OutputDebugString(__FUNCTION__ L": Complete\n"); return S_OK; @@ -354,7 +409,7 @@ HRESULT CMidi2VirtualMidiEndpointManager::CreateDeviceSideEndpoint( DEVPROP_BOOLEAN devPropTrue = DEVPROP_TRUE; DEVPROP_BOOLEAN devPropFalse = DEVPROP_FALSE; BYTE nativeDataFormat = MIDI_PROP_NATIVEDATAFORMAT_UMP; - BYTE supportedDataFormat = (BYTE)MidiDataFormat::MidiDataFormat_UMP; + UINT32 supportedDataFormat = (UINT32)MidiDataFormat::MidiDataFormat_UMP; // this purpose is important because it controls how this shows up to clients @@ -363,14 +418,14 @@ HRESULT CMidi2VirtualMidiEndpointManager::CreateDeviceSideEndpoint( OutputDebugString(__FUNCTION__ L": Building DEVPROPERTY interfaceDevProperties[]\n"); std::wstring endpointName = entry.BaseEndpointName + L" (Virtual MIDI Device)"; - std::wstring endpointDescription = entry.Description + L" (for use only by the device host application)"; + std::wstring endpointDescription = entry.Description + L" (This endpoint for use only by the device host application.)"; DEVPROPERTY interfaceDevProperties[] = { {{PKEY_MIDI_AssociatedUMP, DEVPROP_STORE_SYSTEM, nullptr}, DEVPROP_TYPE_EMPTY, 0, nullptr}, {{PKEY_MIDI_SupportedDataFormats, DEVPROP_STORE_SYSTEM, nullptr}, - DEVPROP_TYPE_BYTE, static_cast(sizeof(BYTE)), &supportedDataFormat}, + DEVPROP_TYPE_UINT32, static_cast(sizeof(UINT32)), &supportedDataFormat}, // the device side connection is NOT multi-client. Keep it just to the device app {{PKEY_MIDI_SupportsMulticlient, DEVPROP_STORE_SYSTEM, nullptr}, @@ -421,7 +476,7 @@ HRESULT CMidi2VirtualMidiEndpointManager::CreateDeviceSideEndpoint( createInfo.cbSize = sizeof(createInfo); - std::wstring instanceId = MIDI_VIRT_INSTANCE_ID_DEVICE_PREFIX + entry.ShortUniqueId; + std::wstring instanceId = internal::NormalizeDeviceInstanceIdCopy(MIDI_VIRT_INSTANCE_ID_DEVICE_PREFIX + entry.ShortUniqueId); createInfo.pszInstanceId = instanceId.c_str(); @@ -455,7 +510,7 @@ HRESULT CMidi2VirtualMidiEndpointManager::CreateDeviceSideEndpoint( // loopback transport. m_MidiDeviceManager->DeleteAllEndpointInProtocolDiscoveredProperties(newDeviceInterfaceId); - entry.CreatedDeviceEndpointId = newDeviceInterfaceId; + entry.CreatedDeviceEndpointId = internal::NormalizeEndpointInterfaceIdCopy(newDeviceInterfaceId); MidiEndpointTable::Current().AddCreatedEndpointDevice(entry); diff --git a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.h b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.h index 6a1184884..1590824be 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.h +++ b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.h @@ -35,6 +35,10 @@ class CMidi2VirtualMidiEndpointManager : HRESULT ApplyJson(_In_ json::JsonObject jsonObject); + HRESULT NegotiateAndRequestMetadata(_In_ std::wstring endpointInterfaceId); + + HRESULT DeleteClientEndpoint(_In_ std::wstring clientShortInstanceId); + //HRESULT DeleteEndpointPair( // _In_ GUID const VirtualEndpointAssociationGuid //); @@ -51,6 +55,7 @@ class CMidi2VirtualMidiEndpointManager : HRESULT CreateParentDevice(); wil::com_ptr_nothrow m_MidiDeviceManager; + wil::com_ptr_nothrow m_MidiProtocolManager; //json::JsonObject m_JsonObject{ nullptr }; }; diff --git a/src/api/Abstraction/VirtualMidiAbstraction/MidiEndpointTable.cpp b/src/api/Abstraction/VirtualMidiAbstraction/MidiEndpointTable.cpp index a766be2ee..10011894c 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/MidiEndpointTable.cpp +++ b/src/api/Abstraction/VirtualMidiAbstraction/MidiEndpointTable.cpp @@ -22,49 +22,6 @@ MidiEndpointTable& MidiEndpointTable::Current() } -//HRESULT -//GetDeviceVirtualEndpointAssociationId(_In_ std::wstring deviceId, _Inout_ std::wstring& associationId) -//{ -// OutputDebugString(__FUNCTION__ L" enter"); -// -// auto additionalProperties = winrt::single_threaded_vector(); -// additionalProperties.Append(winrt::to_hstring(STRING_PKEY_MIDI_SupportedDataFormats)); -// auto deviceInfo = DeviceInformation::CreateFromIdAsync(MidiDevice, additionalProperties, winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get(); -// -// auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_SupportedDataFormats)); -// if (prop) -// { -// OutputDebugString(__FUNCTION__ L" found property"); -// -// // this interface is pointing to a UMP interface, so use that instance id. -// DataFormat = (MidiDataFormat)winrt::unbox_value(prop); -// } -// else -// { -// OutputDebugString(__FUNCTION__ L" didn't find property"); -// // default to any -// DataFormat = MidiDataFormat::MidiDataFormat_Any; -// } -// -// OutputDebugString(__FUNCTION__ L" exiting OK"); -// -// return S_OK; -//} -// -// -// -// -// -// -// -// -// - - - - - - HRESULT MidiEndpointTable::Cleanup() { @@ -89,65 +46,46 @@ HRESULT MidiEndpointTable::AddCreatedEndpointDevice(MidiVirtualDeviceEndpointEnt return S_OK; } -std::wstring GetStringSWDProperty(_In_ std::wstring instanceId, _In_ std::wstring propertyName, _In_ std::wstring defaultValue) -{ - auto propertyKey = winrt::to_hstring(propertyName.c_str()); - - auto additionalProperties = winrt::single_threaded_vector(); - additionalProperties.Append(propertyKey); - - - auto deviceInfo = winrt::Windows::Devices::Enumeration::DeviceInformation::CreateFromIdAsync( - winrt::to_hstring(instanceId.c_str()), - additionalProperties, - winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get(); - - auto prop = deviceInfo.Properties().Lookup(propertyKey); - - if (prop) - { - OutputDebugString(__FUNCTION__ L" found property"); - - // this interface is pointing to a UMP interface, so use that instance id. - return (winrt::unbox_value(prop)).c_str(); - } - else - { - OutputDebugString(__FUNCTION__ L" didn't find property"); - // default to any - return defaultValue; - } - -} _Use_decl_annotations_ -HRESULT MidiEndpointTable::OnClientConnected(std::wstring clientInstanceId, CMidi2VirtualMidiBiDi* clientBiDi) +HRESULT +MidiEndpointTable::OnClientConnected(std::wstring clientEndpointInterfaceId, CMidi2VirtualMidiBiDi* clientBiDi) { // get the device BiDi, and then wire them together - try { OutputDebugString(__FUNCTION__ L""); - std::wstring cleanId = internal::ToUpperTrimmedWStringCopy(clientInstanceId); + std::wstring cleanId = internal::NormalizeEndpointInterfaceIdCopy(clientEndpointInterfaceId); // look up the association ID in SWD properties - auto associationId = internal::ToUpperTrimmedWStringCopy(GetStringSWDProperty(cleanId, STRING_PKEY_MIDI_VirtualMidiEndpointAssociator, L"")); + auto associationId = GetSwdPropertyVirtualEndpointAssociationId(clientEndpointInterfaceId); if (associationId != L"") { - // find this id in the table - auto entry = m_endpoints[associationId]; + if (m_endpoints.find(associationId) != m_endpoints.end()) + { + // find this id in the table + auto entry = m_endpoints[associationId]; - entry.MidiClientBiDi = clientBiDi; - entry.CreatedClientEndpointId = cleanId; + if (entry.MidiClientBiDi == nullptr) + { + entry.MidiClientBiDi = clientBiDi; + entry.CreatedClientEndpointId = cleanId; - entry.MidiDeviceBiDi->LinkAssociatedBiDi(clientBiDi); - clientBiDi->LinkAssociatedBiDi(entry.MidiDeviceBiDi); + entry.MidiDeviceBiDi->LinkAssociatedBiDi(clientBiDi); + clientBiDi->LinkAssociatedBiDi(entry.MidiDeviceBiDi); - m_endpoints[associationId] = entry; + m_endpoints[associationId] = entry; + } + } + else + { + // couldn't find the entry + OutputDebugString(__FUNCTION__ L" - unable to find device table entry"); + } } } CATCH_RETURN(); @@ -156,46 +94,66 @@ HRESULT MidiEndpointTable::OnClientConnected(std::wstring clientInstanceId, CMid } + _Use_decl_annotations_ -HRESULT MidiEndpointTable::OnDeviceConnected(std::wstring deviceInstanceId, CMidi2VirtualMidiBiDi* deviceBiDi) +HRESULT MidiEndpointTable::OnDeviceConnected(std::wstring deviceEndpointInterfaceId, CMidi2VirtualMidiBiDi* deviceBiDi) { - // get the device BiDi, and then wire them together - - try { OutputDebugString(__FUNCTION__ L""); - std::wstring cleanId = internal::ToUpperTrimmedWStringCopy(deviceInstanceId); - // look up the association ID in SWD properties - - auto associationId = internal::ToUpperTrimmedWStringCopy(GetStringSWDProperty(cleanId, STRING_PKEY_MIDI_VirtualMidiEndpointAssociator, L"")); + auto associationId = GetSwdPropertyVirtualEndpointAssociationId(deviceEndpointInterfaceId); if (associationId != L"") { - // find this id in the table - auto entry = m_endpoints[associationId]; + if (m_endpoints.find(associationId) != m_endpoints.end()) + { + // find this id in the table + auto entry = m_endpoints[associationId]; - entry.MidiDeviceBiDi = deviceBiDi; + if (entry.MidiDeviceBiDi == nullptr) + { + OutputDebugString(__FUNCTION__ L" - no registered device bidi yet\n"); - m_endpoints[associationId] = entry; + entry.MidiDeviceBiDi = deviceBiDi; + m_endpoints[associationId] = entry; - // if we have an endpoint manager, go ahead and create the client endpoint - if (m_endpointManager) - { - OutputDebugString(__FUNCTION__ L" - Creating client endpoint"); + } + else + { + // already created. Exit. This happens during protocol negotiation. + } - // create the client endpoint - RETURN_IF_FAILED(m_endpointManager->CreateClientVisibleEndpoint(entry)); - m_endpoints[associationId] = entry; - } - else - { - OutputDebugString(__FUNCTION__ L" - Endpoint Manager is null"); + // if we have an endpoint manager, go ahead and create the client endpoint + if (m_endpointManager && entry.CreatedClientEndpointId == L"") + { + entry.MidiClientBiDi = nullptr; + entry.CreatedClientEndpointId = L""; + + OutputDebugString(__FUNCTION__ L" - Creating client endpoint"); + + // create the client endpoint + RETURN_IF_FAILED(m_endpointManager->CreateClientVisibleEndpoint(entry)); + + OutputDebugString(__FUNCTION__ L" - Client endpoint created"); + + m_endpoints[associationId] = entry; + + + // LOG_IF_FAILED(m_endpointManager->NegotiateAndRequestMetadata(entry.CreatedClientEndpointId)); + + + } + else + { + OutputDebugString(__FUNCTION__ L" - Endpoint Manager is null or created client endpoint ID is not empty"); + + return E_FAIL; + } + - return E_FAIL; } } } @@ -205,15 +163,66 @@ HRESULT MidiEndpointTable::OnDeviceConnected(std::wstring deviceInstanceId, CMid } _Use_decl_annotations_ -HRESULT MidiEndpointTable::OnDeviceDisconnected(std::wstring /*deviceInstanceId*/) +HRESULT MidiEndpointTable::OnDeviceDisconnected(std::wstring deviceEndpointInterfaceId) { - OutputDebugString(__FUNCTION__ L""); + try + { + OutputDebugString(__FUNCTION__ L" - enter\n"); - //std::wstring deviceEndpointId{ deviceInstanceId }; - //internal::InPlaceToUpper(deviceEndpointId); + if (m_endpointManager != nullptr) + { + std::wstring associationId = GetSwdPropertyVirtualEndpointAssociationId(deviceEndpointInterfaceId); + if (associationId != L"") + { + OutputDebugString(__FUNCTION__ L" - Getting virtual endpoints table entry\n"); + + if (m_endpoints.find(associationId) != m_endpoints.end()) + { + auto entry = m_endpoints[associationId]; + + if (entry.MidiClientBiDi != nullptr) + { + // unlink the bidi devices + entry.MidiClientBiDi->UnlinkAssociatedBiDi(); + entry.MidiClientBiDi = nullptr; + } + + if (entry.MidiDeviceBiDi != nullptr) + { + // unlink the bidi devices + entry.MidiDeviceBiDi->UnlinkAssociatedBiDi(); + entry.MidiDeviceBiDi = nullptr; + + // deactivate the client + m_endpointManager->DeleteClientEndpoint(entry.CreatedShortClientInstanceId); + + entry.CreatedShortClientInstanceId = L""; + entry.CreatedClientEndpointId = L""; + } + + // update with changes + m_endpoints[associationId] = entry; + + } + else + { + OutputDebugString(__FUNCTION__ L" - no entry for associationId\n"); + } + } + else + { + OutputDebugString(__FUNCTION__ L" - unable to get association Id\n"); + } + } + else + { + OutputDebugString(__FUNCTION__ L" - endpoint manager is null\n"); + } - // tear down the client device and BiDi as well + OutputDebugString(__FUNCTION__ L" - exit\n"); + } + CATCH_LOG(); return S_OK; } diff --git a/src/api/Abstraction/VirtualMidiAbstraction/MidiEndpointTable.h b/src/api/Abstraction/VirtualMidiAbstraction/MidiEndpointTable.h index 3285db0f3..763eebae1 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/MidiEndpointTable.h +++ b/src/api/Abstraction/VirtualMidiAbstraction/MidiEndpointTable.h @@ -52,16 +52,18 @@ struct MidiVirtualDeviceEndpointEntry { - std::wstring VirtualEndpointAssociationId; // how the config entries associate endpoints. Typically a GUID - std::wstring BaseEndpointName; - std::wstring Description; - std::wstring ShortUniqueId; + std::wstring VirtualEndpointAssociationId{ L"" }; // how the config entries associate endpoints. Typically a GUID + std::wstring BaseEndpointName{ L"" }; + std::wstring Description{ L"" }; + std::wstring ShortUniqueId{ L"" }; - std::wstring CreatedDeviceEndpointId; // the device interface id - std::wstring CreatedClientEndpointId; + std::wstring CreatedDeviceEndpointId{ L"" }; // the device interface id + std::wstring CreatedClientEndpointId{ L"" }; + std::wstring CreatedShortClientInstanceId{ L"" }; - wil::com_ptr_nothrow MidiDeviceBiDi; - wil::com_ptr_nothrow MidiClientBiDi; + + wil::com_ptr_nothrow MidiDeviceBiDi{ nullptr }; + wil::com_ptr_nothrow MidiClientBiDi{ nullptr }; ~MidiVirtualDeviceEndpointEntry() { @@ -91,9 +93,9 @@ class MidiEndpointTable HRESULT AddCreatedEndpointDevice(_In_ MidiVirtualDeviceEndpointEntry& entry) noexcept; - HRESULT OnDeviceConnected(_In_ std::wstring deviceInstanceId, _In_ CMidi2VirtualMidiBiDi* deviceBiDi); - HRESULT OnClientConnected(_In_ std::wstring clientInstanceId, _In_ CMidi2VirtualMidiBiDi* clientBiDi); - HRESULT OnDeviceDisconnected(_In_ std::wstring deviceInstanceId); + HRESULT OnDeviceConnected(_In_ std::wstring deviceEndpointInterfaceId, _In_ CMidi2VirtualMidiBiDi* deviceBiDi); + HRESULT OnClientConnected(_In_ std::wstring clientEndpointInterfaceId, _In_ CMidi2VirtualMidiBiDi* clientBiDi); + HRESULT OnDeviceDisconnected(_In_ std::wstring deviceEndpointInterfaceId); HRESULT Cleanup(); diff --git a/src/api/Abstraction/VirtualMidiAbstraction/pch.h b/src/api/Abstraction/VirtualMidiAbstraction/pch.h index 99db8677c..85cce630f 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/pch.h +++ b/src/api/Abstraction/VirtualMidiAbstraction/pch.h @@ -77,10 +77,15 @@ namespace internal = ::Windows::Devices::Midi2::Internal; #include "mididevicemanagerinterface_i.c" #include "mididevicemanagerinterface.h" +#include "MidiEndpointProtocolManagerInterface_i.c" +#include "MidiEndpointProtocolManagerInterface.h" + + #include "dllmain.h" #include "MidiDefs.h" #include "MidiXProc.h" +#include "swd_shared.h" class CMidi2VirtualMidiEndpointManager; class CMidi2VirtualMidiBiDi; diff --git a/src/api/Abstraction/VirtualMidiAbstraction/swd_shared.cpp b/src/api/Abstraction/VirtualMidiAbstraction/swd_shared.cpp new file mode 100644 index 000000000..c1eac76c5 --- /dev/null +++ b/src/api/Abstraction/VirtualMidiAbstraction/swd_shared.cpp @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + + +#include "pch.h" + +#include "swd_shared.h" + +_Use_decl_annotations_ +std::wstring GetStringSwdProperty(std::wstring deviceInterfaceId, std::wstring propertyName, std::wstring defaultValue) +{ + auto propertyKey = winrt::to_hstring(propertyName.c_str()); + + auto additionalProperties = winrt::single_threaded_vector(); + additionalProperties.Append(propertyKey); + + + auto deviceInfo = winrt::Windows::Devices::Enumeration::DeviceInformation::CreateFromIdAsync( + winrt::to_hstring(deviceInterfaceId.c_str()), + additionalProperties, + winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get(); + + auto prop = deviceInfo.Properties().Lookup(propertyKey); + + if (prop) + { + OutputDebugString(__FUNCTION__ L" found property"); + + // this interface is pointing to a UMP interface, so use that instance id. + return (winrt::unbox_value(prop)).c_str(); + } + else + { + OutputDebugString(__FUNCTION__ L" didn't find property"); + // default to any + return defaultValue; + } + +} + + +_Use_decl_annotations_ +std::wstring GetSwdPropertyVirtualEndpointAssociationId(std::wstring deviceInterfaceId) +{ + std::wstring cleanId = internal::NormalizeEndpointInterfaceIdCopy(deviceInterfaceId); + + return internal::ToUpperTrimmedWStringCopy(GetStringSwdProperty(cleanId, STRING_PKEY_MIDI_VirtualMidiEndpointAssociator, L"")); +} + + +_Use_decl_annotations_ +std::wstring GetSwdPropertyInstanceId(std::wstring deviceInterfaceId) +{ + std::wstring cleanId = internal::NormalizeEndpointInterfaceIdCopy(deviceInterfaceId); + + return internal::NormalizeDeviceInstanceIdCopy(GetStringSwdProperty(cleanId, L"System.Devices.DeviceInstanceId", L"")); +} diff --git a/src/api/Abstraction/VirtualMidiAbstraction/swd_shared.h b/src/api/Abstraction/VirtualMidiAbstraction/swd_shared.h new file mode 100644 index 000000000..379cf1978 --- /dev/null +++ b/src/api/Abstraction/VirtualMidiAbstraction/swd_shared.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +// TODO: Post-NAMM, move this to a lib that all projects share + +#pragma once + +std::wstring GetSwdStringProperty(_In_ std::wstring deviceInterfaceId, _In_ std::wstring propertyName, _In_ std::wstring defaultValue); + +std::wstring GetSwdPropertyVirtualEndpointAssociationId(_In_ std::wstring deviceInterfaceId); + +std::wstring GetSwdPropertyInstanceId(_In_ std::wstring deviceInterfaceId); diff --git a/src/api/Client/Midi2Client-Projection/nuget/Windows.Devices.Midi2.nuspec b/src/api/Client/Midi2Client-Projection/nuget/Windows.Devices.Midi2.nuspec index 3844a56be..529ba15fd 100644 --- a/src/api/Client/Midi2Client-Projection/nuget/Windows.Devices.Midi2.nuspec +++ b/src/api/Client/Midi2Client-Projection/nuget/Windows.Devices.Midi2.nuspec @@ -2,7 +2,7 @@ Windows.Devices.Midi2 - 1.0.0-preview.3-0134 + 1.0.0-preview.3-0136 Microsoft Corporation Windows MIDI Services API. Minimum package necessary to use Windows MIDI Services from an app on a PC that has Windows MIDI Services installed. MIT diff --git a/src/api/Client/Midi2Client/MidiEndpointConnection.cpp b/src/api/Client/Midi2Client/MidiEndpointConnection.cpp index 13b5613e2..2e03a72bd 100644 --- a/src/api/Client/Midi2Client/MidiEndpointConnection.cpp +++ b/src/api/Client/Midi2Client/MidiEndpointConnection.cpp @@ -147,6 +147,8 @@ namespace winrt::Windows::Devices::Midi2::implementation midi2::MidiEndpointConnectionOptions options ) { + OutputDebugString(__FUNCTION__ L""); + internal::LogInfo(__FUNCTION__, L"Internal Initialize "); try @@ -176,6 +178,8 @@ namespace winrt::Windows::Devices::Midi2::implementation _Use_decl_annotations_ bool MidiEndpointConnection::Open() { + OutputDebugString(__FUNCTION__ L""); + internal::LogInfo(__FUNCTION__, L"Connection Open "); if (!IsOpen()) @@ -183,6 +187,8 @@ namespace winrt::Windows::Devices::Midi2::implementation // Activate the endpoint for this device. Will fail if the device is not a BiDi device if (!ActivateMidiStream(m_serviceAbstraction, __uuidof(IMidiBiDi), (void**)&m_endpointAbstraction)) { + OutputDebugString(__FUNCTION__ L" coult not activate MIDI stream"); + internal::LogGeneralError(__FUNCTION__, L"Could not activate MIDI Stream"); return false; @@ -239,6 +245,8 @@ namespace winrt::Windows::Devices::Midi2::implementation void MidiEndpointConnection::Close() { + OutputDebugString(__FUNCTION__ L""); + internal::LogInfo(__FUNCTION__, L"Connection Close"); if (m_closeHasBeenCalled) return; @@ -270,6 +278,8 @@ namespace winrt::Windows::Devices::Midi2::implementation MidiEndpointConnection::~MidiEndpointConnection() { + OutputDebugString(__FUNCTION__ L""); + if (!m_closeHasBeenCalled) { Close(); @@ -281,6 +291,8 @@ namespace winrt::Windows::Devices::Midi2::implementation void MidiEndpointConnection::InitializePlugins() noexcept { + OutputDebugString(__FUNCTION__ L""); + internal::LogInfo(__FUNCTION__, L"Initializing message processing plugins"); for (const auto& plugin : m_messageProcessingPlugins) @@ -300,6 +312,8 @@ namespace winrt::Windows::Devices::Midi2::implementation void MidiEndpointConnection::CallOnConnectionOpenedOnPlugins() noexcept { + OutputDebugString(__FUNCTION__ L""); + internal::LogInfo(__FUNCTION__, L"Notifying message processing plugins that the connection is opened"); for (const auto& plugin : m_messageProcessingPlugins) diff --git a/src/api/Client/Midi2Client/MidiFunctionBlock.h b/src/api/Client/Midi2Client/MidiFunctionBlock.h index 9efd8459e..85f443c58 100644 --- a/src/api/Client/Midi2Client/MidiFunctionBlock.h +++ b/src/api/Client/Midi2Client/MidiFunctionBlock.h @@ -62,7 +62,7 @@ namespace winrt::Windows::Devices::Midi2::implementation void InternalSetisReadOnly(_In_ bool isReadOnly) { m_isReadOnly = isReadOnly; } private: - bool m_isReadOnly{ true }; + bool m_isReadOnly{ false }; uint8_t m_number{ 0 }; winrt::hstring m_name{}; bool m_isActive{ false }; diff --git a/src/api/Client/Midi2Client/MidiSession.cpp b/src/api/Client/Midi2Client/MidiSession.cpp index 049ffac97..c74bb9e33 100644 --- a/src/api/Client/Midi2Client/MidiSession.cpp +++ b/src/api/Client/Midi2Client/MidiSession.cpp @@ -150,7 +150,7 @@ namespace winrt::Windows::Devices::Midi2::implementation } else { - internal::LogGeneralError(__FUNCTION__, L" WinRT Endpoint connection wouldn't start"); + internal::LogGeneralError(__FUNCTION__, L"WinRT Endpoint connection wouldn't start"); // TODO: Cleanup @@ -159,7 +159,7 @@ namespace winrt::Windows::Devices::Midi2::implementation } catch (winrt::hresult_error const& ex) { - internal::LogHresultError(__FUNCTION__, L" hresult exception connecting to endpoint. Service or endpoint may be unavailable, or endpoint may not be the correct type.", ex); + internal::LogHresultError(__FUNCTION__, L"hresult exception connecting to endpoint. Service or endpoint may be unavailable, or endpoint may not be the correct type.", ex); return nullptr; } @@ -185,13 +185,44 @@ namespace winrt::Windows::Devices::Midi2::implementation _Use_decl_annotations_ midi2::MidiEndpointConnection MidiSession::CreateVirtualDeviceAndConnection( - winrt::hstring /*endpointName*/, - winrt::hstring /*endpointDeviceInstanceId*/ + midi2::MidiVirtualEndpointDeviceDefinition const& deviceDefinition ) noexcept { - // TODO + OutputDebugString(__FUNCTION__ L""); - return nullptr; + // TEMP ============================================================================================ + // Until we can spin up a new virtual device from the API, we have to use a pre-configured + // endpoint. + + winrt::hstring endpointDeviceId = + L"\\\\?\\SWD#MIDISRV#MIDIU_VIRTDEV_" + + deviceDefinition.EndpointProductInstanceId() + + L"#" + midi2::MidiEndpointDeviceInformation::EndpointInterfaceClass(); + + OutputDebugString(endpointDeviceId.c_str()); + + // End TEMP ======================================================================================== + + + // create the connection + auto connection = CreateEndpointConnection(endpointDeviceId, nullptr, nullptr); + + if (connection) + { + auto virtualDevice = winrt::make_self(); + + virtualDevice->InternalSetDeviceDefinition(deviceDefinition); + virtualDevice->IsEnabled(true); + + // add the listener + connection.MessageProcessingPlugins().Append(*virtualDevice); + } + else + { + internal::LogGeneralError(__FUNCTION__, L"Could not create connection"); + } + + return connection; } diff --git a/src/api/Client/Midi2Client/MidiSession.h b/src/api/Client/Midi2Client/MidiSession.h index 8ee6aac92..fe614261d 100644 --- a/src/api/Client/Midi2Client/MidiSession.h +++ b/src/api/Client/Midi2Client/MidiSession.h @@ -63,8 +63,7 @@ namespace winrt::Windows::Devices::Midi2::implementation midi2::MidiEndpointConnection CreateVirtualDeviceAndConnection( - _In_ winrt::hstring endpointName, - _In_ winrt::hstring endpointDeviceInstanceId + _In_ midi2::MidiVirtualEndpointDeviceDefinition const& deviceDefinition ) noexcept; diff --git a/src/api/Client/Midi2Client/MidiSession.idl b/src/api/Client/Midi2Client/MidiSession.idl index b1708fdf1..eb130d36a 100644 --- a/src/api/Client/Midi2Client/MidiSession.idl +++ b/src/api/Client/Midi2Client/MidiSession.idl @@ -10,14 +10,11 @@ MIDI_IDL_IMPORT import "MidiEndpointConnection.idl"; - import "IMidiEndpointDefinedConnectionSettings.idl"; - import "MidiSessionSettings.idl"; - import "MidiEndpointConnectionOptions.idl"; +import "MidiVirtualEndpointDeviceDefinition.idl"; -//import "MidiVirtualDeviceManager.idl"; namespace Windows.Devices.Midi2 { @@ -59,9 +56,7 @@ namespace Windows.Devices.Midi2 // This creates the virtual device, and also adds the appropriate listener // to the returned endpoint connection MidiEndpointConnection CreateVirtualDeviceAndConnection( - String endpointName, - String endpointDeviceInstanceId - // TODO: Other options for creating the device. Maybe wrap this all up in an object. + MidiVirtualEndpointDeviceDefinition deviceDefinition ); // This will close and remove a single endpoint connection instance. diff --git a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.cpp b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.cpp index d58d10704..8cacd545e 100644 --- a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.cpp +++ b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.cpp @@ -36,11 +36,15 @@ namespace winrt::Windows::Devices::Midi2::implementation _Use_decl_annotations_ void MidiVirtualEndpointDevice::Initialize(midi2::IMidiEndpointConnectionSource const& endpointConnection) noexcept { + OutputDebugString(__FUNCTION__ L""); + m_endpointConnection = endpointConnection.as(); } void MidiVirtualEndpointDevice::OnEndpointConnectionOpened() noexcept { + OutputDebugString(__FUNCTION__ L""); + //throw hresult_not_implemented(); } @@ -49,12 +53,232 @@ namespace winrt::Windows::Devices::Midi2::implementation //throw hresult_not_implemented(); } + _Use_decl_annotations_ + void MidiVirtualEndpointDevice::SendFunctionBlockInfoNotificationMessage(midi2::MidiFunctionBlock const& fb) noexcept + { + OutputDebugString(__FUNCTION__ L""); + + auto functionBlockNotification = midi2::MidiStreamMessageBuilder::BuildFunctionBlockInfoNotificationMessage( + 0, + true, + fb.Number(), + fb.UIHint(), + fb.Midi10Connection(), + fb.Direction(), + fb.FirstGroupIndex(), + fb.GroupCount(), + fb.MidiCIMessageVersionFormat(), + fb.MaxSystemExclusive8Streams() + ); + + // TODO: Log if failed + m_endpointConnection.SendMessagePacket(functionBlockNotification); + } + + _Use_decl_annotations_ + void MidiVirtualEndpointDevice::SendFunctionBlockNameNotificationMessages(midi2::MidiFunctionBlock const& fb) noexcept + { + OutputDebugString(__FUNCTION__ L""); + + if (fb.Name() == L"") return; + + auto nameMessages = midi2::MidiStreamMessageBuilder::BuildFunctionBlockNameNotificationMessages( + 0, + fb.Number(), + fb.Name() + ); + + for (uint32_t i = 0; i < nameMessages.Size(); i++) + { + // TODO: Log if failed + m_endpointConnection.SendMessagePacket(nameMessages.GetAt(i)); + } + + } + _Use_decl_annotations_ void MidiVirtualEndpointDevice::ProcessIncomingMessage( - midi2::MidiMessageReceivedEventArgs const& /*args*/, - bool& /*skipFurtherListeners*/, - bool& /*skipMainMessageReceivedEvent*/) noexcept + midi2::MidiMessageReceivedEventArgs const& args, + bool& skipFurtherListeners, + bool& skipMainMessageReceivedEvent) noexcept { - //throw hresult_not_implemented(); + OutputDebugString(__FUNCTION__ L""); + + bool handled = false; + + if (args.MessageType() == MidiMessageType::Stream128) + { + MidiMessage128 message{}; + if (args.FillMessage128(message)) + { + + // if a endpoint discovery request, handle it with the data we have + if (internal::MessageIsEndpointDiscoveryRequest(message.Word0())) + { + uint8_t filterFlags = internal::GetEndpointDiscoveryMessageFilterFlagsFromSecondWord(message.Word1()); + + if (internal::EndpointDiscoveryFilterRequestsEndpointInfoNotification(filterFlags)) + { + // send endpoint info notification + + auto notification = midi2::MidiStreamMessageBuilder::BuildEndpointInformationNotificationMessage( + 0, + MIDI_PREFERRED_UMP_VERSION_MAJOR, + MIDI_PREFERRED_UMP_VERSION_MINOR, + m_areFunctionBlocksStatic, + (uint8_t)m_functionBlocks.Size(), + true, // TODO: Pull from properties supports midi 2.0 + true, // TODO: pull from properties supports midi 1.0 + false, // todo: pull from default JR timestamp handling + false // todo: pull from jr timestamp handling + ); + + m_endpointConnection.SendMessagePacket(notification); + } + + if (internal::EndpointDiscoveryFilterRequestsDeviceIdentityNotification(filterFlags)) + { + // send device identity notification + } + + if (internal::EndpointDiscoveryFilterRequestsEndpointNameNotification(filterFlags)) + { + // send endpoint name notification messages + } + + if (internal::EndpointDiscoveryFilterRequestsProductInstanceIdNotification(filterFlags)) + { + // send product instance id notification messages + + } + + if (internal::EndpointDiscoveryFilterRequestsStreamConfigurationNotification(filterFlags)) + { + // send stream configuration message + } + } + else if (internal::MessageIsFunctionBlockDiscoveryRequest(message.Word0())) + { + uint8_t filterFlags = internal::GetFunctionBlockDiscoveryMessageFilterFlagsFromFirstWord(message.Word0()); + + bool requestInfo = internal::FunctionBlockDiscoveryFilterRequestsInfoNotification(filterFlags); + bool requestName = internal::FunctionBlockDiscoveryFilterRequestsNameNotification(filterFlags); + + uint8_t fbNumber = internal::GetFunctionBlockNumberFromFunctionBlockDiscoveryRequestFirstWord(message.Word0()); + + if (fbNumber == MIDI_STREAM_MESSAGE_FUNCTION_BLOCK_REQUEST_ALL_FUNCTION_BLOCKS) + { + // send all function blocks + + for (uint8_t i = 0; i < (uint8_t)m_functionBlocks.Size(); i++) + { + if (requestInfo) SendFunctionBlockInfoNotificationMessage(m_functionBlocks.Lookup(i)); + if (requestName) SendFunctionBlockNameNotificationMessages(m_functionBlocks.Lookup(i)); + } + } + else + { + // send single function block + + if (m_functionBlocks.HasKey(fbNumber)) + { + auto fb = m_functionBlocks.Lookup(fbNumber); + + if (requestInfo) SendFunctionBlockInfoNotificationMessage(fb); + if (requestName) SendFunctionBlockNameNotificationMessages(fb); + } + else + { + // invalid fb number request + handled = false; + } + } + + } + else if (internal::MessageIsEndpointDiscoveryRequest(message.Word0())) + { + } + else + { + // something else + } + + + + } + else + { + // something went wrong filling this message type + } + + + } + else + { + // not a stream message. Ignore + + + } + + + if (handled && SuppressHandledMessages()) + { + skipFurtherListeners = true; + skipMainMessageReceivedEvent = true; + } + else + { + skipFurtherListeners = false; + skipMainMessageReceivedEvent = false; + } + + } + + + + + _Use_decl_annotations_ + void MidiVirtualEndpointDevice::InternalSetDeviceDefinition( + _In_ midi2::MidiVirtualEndpointDeviceDefinition definition) + { + try + { + OutputDebugString(__FUNCTION__ L""); + + // populate all the views, properties, blocks, etc. + + m_areFunctionBlocksStatic = definition.AreFunctionBlocksStatic(); + + for (uint8_t i = 0; i < definition.FunctionBlocks().Size(); i++) + { + auto fb = definition.FunctionBlocks().GetAt(i); + + // this is required, so we enforce it here + fb.Number(i); + + // TODO: Set the fb as read-only + + + + // add the block + m_functionBlocks.Insert(i, fb); + } + + m_endpointName = definition.EndpointName(); + m_endpointProductInstanceId = definition.EndpointProductInstanceId(); + + + // TODO: All the other stuff we'll want to report on + + + + + } + catch (...) + { + // todo log + } + } + } diff --git a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.h b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.h index 28e92132c..8a89a865f 100644 --- a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.h +++ b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.h @@ -64,6 +64,8 @@ namespace winrt::Windows::Devices::Midi2::implementation void OnEndpointConnectionOpened() noexcept; void Cleanup() noexcept; + void InternalSetDeviceDefinition(_In_ midi2::MidiVirtualEndpointDeviceDefinition definition); + void ProcessIncomingMessage( _In_ midi2::MidiMessageReceivedEventArgs const& args, _Out_ bool& skipFurtherListeners, @@ -71,6 +73,10 @@ namespace winrt::Windows::Devices::Midi2::implementation private: + void SendFunctionBlockInfoNotificationMessage(_In_ midi2::MidiFunctionBlock const& fb) noexcept; + void SendFunctionBlockNameNotificationMessages(_In_ midi2::MidiFunctionBlock const& fb) noexcept; + + //midi2::MidiVirtualEndpointDeviceDefinition m_virtualEndpointDeviceDefinition winrt::hstring m_id{}; // plugin id diff --git a/src/api/Client/Midi2Client/WinRTActivationEntries.txt b/src/api/Client/Midi2Client/WinRTActivationEntries.txt index c21c6d787..5654d2476 100644 --- a/src/api/Client/Midi2Client/WinRTActivationEntries.txt +++ b/src/api/Client/Midi2Client/WinRTActivationEntries.txt @@ -55,7 +55,8 @@ Windows.Devices.Midi2.MidiMessageTypeEndpointListener | 0 | 0 | 0 # Virtual Device Message Processing Plugins -Windows.Devices.Midi2.MidiVirtualDevice | 0 | 0 | 0 +Windows.Devices.Midi2.MidiVirtualEndpointDevice | 0 | 0 | 0 +Windows.Devices.Midi2.MidiVirtualEndpointDeviceDefinition | 0 | 0 | 0 # Events @@ -71,6 +72,7 @@ Windows.Devices.Midi2.IMidiEndpointConnectionStatics | 0 | 0 | 0 Windows.Devices.Midi2.MidiEndpointConnectionOptions | 0 | 0 | 0 Windows.Devices.Midi2.MidiStreamConfigurationSettings | 0 | 0 | 0 + # Service Windows.Devices.Midi2.MidiService | 0 | 0 | 0 diff --git a/src/api/Client/Midi2Client/pch.h b/src/api/Client/Midi2Client/pch.h index a5b4a28b2..2e86c7ad5 100644 --- a/src/api/Client/Midi2Client/pch.h +++ b/src/api/Client/Midi2Client/pch.h @@ -93,6 +93,9 @@ namespace midi2 = ::winrt::Windows::Devices::Midi2; #include "MidiMessageReceivedEventArgs.h" #include "MidiEndpointDeviceInformationUpdateEventArgs.h" +#include "MidiVirtualEndpointDevice.h" +#include "MidiVirtualEndpointDeviceDefinition.h" + #include "MidiSession.h" #include "MidiServicePingResponse.h" diff --git a/src/api/Inc/MidiDefs.h b/src/api/Inc/MidiDefs.h index 902d4a3be..03377c8cb 100644 --- a/src/api/Inc/MidiDefs.h +++ b/src/api/Inc/MidiDefs.h @@ -22,6 +22,9 @@ #define MIDI_OUTGOING_MESSAGE_QUEUE_MAX_MESSAGE_COUNT 10000 +#define MIDI_PROTOCOL_MANAGER_ENDPOINT_CREATION_CONTEXT (LONGLONG)3263827 + + // // Registry keys for global configuration. The settings app can write to some of these, so including in MidiDefs // @@ -117,7 +120,7 @@ DEFINE_MIDIDEVPROPKEY(PKEY_MIDI_SupportsMulticlient, 5); // DEVPROP_TYPE_BOO // For a MIDI 2 device, it will support MIDI_NATIVEDATAFORMAT_UMP // NOTE: These are actually in the MidiDataFormat enum, slightly different than the defines mentioned above. #define STRING_PKEY_MIDI_SupportedDataFormats MIDI_STRING_PKEY_GUID MIDI_STRING_PKEY_PID_SEPARATOR L"6" -DEFINE_MIDIDEVPROPKEY(PKEY_MIDI_SupportedDataFormats, 6); // DEVPROP_TYPE_BYTE uint8_t +DEFINE_MIDIDEVPROPKEY(PKEY_MIDI_SupportedDataFormats, 6); // DEVPROP_TYPE_UINT32 #define STRING_PKEY_MIDI_ManufacturerName MIDI_STRING_PKEY_GUID MIDI_STRING_PKEY_PID_SEPARATOR L"7" DEFINE_MIDIDEVPROPKEY(PKEY_MIDI_ManufacturerName, 7); // DEVPROP_TYPE_STRING @@ -143,7 +146,7 @@ DEFINE_MIDIDEVPROPKEY(PKEY_MIDI_IN_GroupTerminalBlocks, 50); // DEVPROP_TYPE DEFINE_MIDIDEVPROPKEY(PKEY_MIDI_OUT_GroupTerminalBlocks, 51); // DEVPROP_TYPE_BINARY #define STRING_PKEY_MIDI_AssociatedUMP MIDI_STRING_PKEY_GUID MIDI_STRING_PKEY_PID_SEPARATOR L"52" -DEFINE_MIDIDEVPROPKEY(PKEY_MIDI_AssociatedUMP, 52); // DEVPROP_TYPE_UINT64 +DEFINE_MIDIDEVPROPKEY(PKEY_MIDI_AssociatedUMP, 52); // DEVPROP_TYPE_STRING // iSerialNumber for USB #define STRING_PKEY_MIDI_SerialNumber MIDI_STRING_PKEY_GUID MIDI_STRING_PKEY_PID_SEPARATOR L"53" diff --git a/src/api/Inc/string_util.h b/src/api/Inc/string_util.h index 1a33d0ad0..65211c211 100644 --- a/src/api/Inc/string_util.h +++ b/src/api/Inc/string_util.h @@ -15,6 +15,7 @@ namespace Windows::Devices::Midi2::Internal { + inline void InPlaceToUpper(_Inout_ std::wstring &s) { std::transform(s.begin(), s.end(), s.begin(), towupper); @@ -56,12 +57,24 @@ namespace Windows::Devices::Midi2::Internal return ws; } + inline std::wstring ToLowerWStringCopy(_In_ std::wstring s) + { + std::wstring ws{ s }; + InPlaceToLower(ws); + + return ws; + } + inline std::wstring ToUpperTrimmedWStringCopy(_In_ std::wstring s) { return ToUpperWStringCopy(TrimmedWStringCopy(s)); } + inline std::wstring ToLowerTrimmedWStringCopy(_In_ std::wstring s) + { + return ToLowerWStringCopy(TrimmedWStringCopy(s)); + } // TODO: this could use substr and take one op instad of two inline winrt::hstring TrimmedHStringCopy(_In_ std::wstring ws) @@ -98,4 +111,40 @@ namespace Windows::Devices::Midi2::Internal } + + + // This is just to convert all GUIDs to the same case. It does + // not add or remove opening / closing brackets + inline std::wstring NormalizeGuidStringCopy(_In_ std::wstring guidString) + { + return ToUpperTrimmedWStringCopy(guidString); + } + + // This is for the device instance id. Not to be confused with the interface id + inline std::wstring NormalizeDeviceInstanceIdCopy(_In_ std::wstring deviceInstanceId) + { + return ToUpperTrimmedWStringCopy(deviceInstanceId); + } + + // This is for the endpoint device interface id (the long SWD id with the GUID) + inline std::wstring NormalizeEndpointInterfaceIdCopy(_In_ std::wstring endpointInterfaceId) + { + return ToLowerTrimmedWStringCopy(endpointInterfaceId); + } + + // used for searching for a substring in an endpoint interface id. Matches case with + // what NormalizeEndpointInterfaceIdCopy produces + inline bool EndpointInterfaceIdContainsString(_In_ std::wstring endpointInterfaceId, _In_ std::wstring searchFor) + { + auto id = NormalizeEndpointInterfaceIdCopy(endpointInterfaceId); + auto sub = ToLowerWStringCopy(searchFor); // match case with NormalizeEndpointInterfaceIdCopy + + if (id == L"" || sub == L"") + { + return false; + } + + return id.find(sub) != std::wstring::npos; + } + } diff --git a/src/api/Inc/ump_helpers.h b/src/api/Inc/ump_helpers.h index af6d3b26e..422e6ec22 100644 --- a/src/api/Inc/ump_helpers.h +++ b/src/api/Inc/ump_helpers.h @@ -350,6 +350,7 @@ namespace Windows::Devices::Midi2::Internal } + // endpoint discovery and function blocks inline bool GetFunctionBlockActiveFlagFromInfoNotificationFirstWord( @@ -391,6 +392,19 @@ namespace Windows::Devices::Midi2::Internal // Function Block Info Notification + inline bool MessageIsFunctionBlockDiscoveryRequest(_In_ uint32_t const firstWord) + { + if (GetUmpMessageTypeFromFirstWord(firstWord) == 0xF) + { + if (GetFormFromStreamMessageFirstWord(firstWord) == 0x0 && + GetStatusFromStreamMessageFirstWord(firstWord) == 0x10) + { + return true; + } + } + + return false; + } inline uint32_t BuildFunctionBlockDiscoveryRequestFirstWord( _In_ uint8_t functionBlockNumber, @@ -408,6 +422,31 @@ namespace Windows::Devices::Midi2::Internal return word; } + inline std::uint8_t GetFunctionBlockNumberFromFunctionBlockDiscoveryRequestFirstWord( + _In_ std::uint32_t const word0 + ) noexcept + { + return (std::uint8_t)(MIDIWORDBYTE3(word0)); + } + + + inline std::uint8_t GetFunctionBlockDiscoveryMessageFilterFlagsFromFirstWord( + _In_ std::uint32_t const word0 + ) noexcept + { + return (std::uint8_t)(MIDIWORDBYTE4(word0)); + } + + inline bool FunctionBlockDiscoveryFilterRequestsInfoNotification(_In_ std::uint8_t const filterBitmap) + { + return ((filterBitmap & 0x01) > 0); + } + + inline bool FunctionBlockDiscoveryFilterRequestsNameNotification(_In_ std::uint8_t const filterBitmap) + { + return ((filterBitmap & 0x02) > 0); + } + inline uint8_t GetFunctionBlockFirstGroupFromInfoNotificationSecondWord( _In_ uint32_t const word1 @@ -438,7 +477,49 @@ namespace Windows::Devices::Midi2::Internal } - // Endpoint Info Notification + // Endpoint Info Notification and related + + inline bool MessageIsEndpointDiscoveryRequest(_In_ std::uint32_t const firstWord) + { + if (GetUmpMessageTypeFromFirstWord(firstWord) == 0xF) + { + if (GetFormFromStreamMessageFirstWord(firstWord) == 0 && + GetStatusFromStreamMessageFirstWord(firstWord) == 0) + { + return true; + } + } + + return false; + } + + inline std::uint8_t GetEndpointDiscoveryMessageFilterFlagsFromSecondWord(_In_ std::uint32_t const secondWord) + { + return (std::uint8_t)MIDIWORDBYTE4(secondWord); + } + + inline bool EndpointDiscoveryFilterRequestsEndpointInfoNotification(_In_ std::uint8_t const filterBitmap) + { + return ((filterBitmap & 0x01) > 0); + } + + inline bool EndpointDiscoveryFilterRequestsDeviceIdentityNotification(_In_ std::uint8_t const filterBitmap) + { + return ((filterBitmap & 0x02) > 0); + } + + inline bool EndpointDiscoveryFilterRequestsEndpointNameNotification(_In_ std::uint8_t const filterBitmap) + { + return ((filterBitmap & 0x04) > 0); + } + inline bool EndpointDiscoveryFilterRequestsProductInstanceIdNotification(_In_ std::uint8_t const filterBitmap) + { + return ((filterBitmap & 0x08) > 0); + } + inline bool EndpointDiscoveryFilterRequestsStreamConfigurationNotification(_In_ std::uint8_t const filterBitmap) + { + return ((filterBitmap & 0x10) > 0); + } inline uint32_t BuildEndpointDiscoveryRequestFirstWord( _In_ uint8_t umpVersionMajor, @@ -515,12 +596,29 @@ namespace Windows::Devices::Midi2::Internal return (bool)((word1 & 0x00000001) > 0); } + + + + // Stream Configuration Request and Notification Messages + inline bool MessageIsStreamConfigurationRequest(_In_ std::uint32_t const firstWord) + { + if (GetUmpMessageTypeFromFirstWord(firstWord) == 0xF) + { + if (GetFormFromStreamMessageFirstWord(firstWord) == 0 && + GetStatusFromStreamMessageFirstWord(firstWord) == 5) + { + return true; + } + } + + return false; + } - inline uint32_t BuildStreamConfigurationRequestFirstWord( - _In_ uint8_t protocol, - _In_ bool endpointShouldExpectToReceiveJR, - _In_ bool endpointShouldSendJR + inline std::uint32_t BuildStreamConfigurationRequestFirstWord( + _In_ std::uint8_t const protocol, + _In_ bool const endpointShouldExpectToReceiveJR, + _In_ bool const endpointShouldSendJR ) { uint32_t word{ 0 }; diff --git a/src/api/Service/Exe/MidiClientManager.cpp b/src/api/Service/Exe/MidiClientManager.cpp index 40f1fa9c5..da9506b6a 100644 --- a/src/api/Service/Exe/MidiClientManager.cpp +++ b/src/api/Service/Exe/MidiClientManager.cpp @@ -110,10 +110,16 @@ GetDeviceSupportedDataFormat(_In_ std::wstring MidiDevice, _Inout_ MidiDataForma auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_SupportedDataFormats)); if (prop) { + OutputDebugString(__FUNCTION__ L" found property"); - // this interface is pointing to a UMP interface, so use that instance id. - DataFormat = (MidiDataFormat)winrt::unbox_value(prop); + DataFormat = MidiDataFormat::MidiDataFormat_Any; + try + { + // this interface is pointing to a UMP interface, so use that instance id. + DataFormat = (MidiDataFormat)winrt::unbox_value(prop); + } + CATCH_LOG(); } else { @@ -170,7 +176,11 @@ GetEndpointAlias(_In_ LPCWSTR MidiDevice, _In_ std::wstring& Alias, _In_ MidiFlo additionalProperties.Append(winrt::to_hstring(STRING_PKEY_MIDI_AssociatedUMP)); auto deviceInfo = DeviceInformation::CreateFromIdAsync(MidiDevice, additionalProperties, winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get(); + OutputDebugString(__FUNCTION__ L" looking up prop"); auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_AssociatedUMP)); + + OutputDebugString(__FUNCTION__ L" got prop. About to check for null"); + if (prop) { OutputDebugString(L"" __FUNCTION__ " STRING_PKEY_MIDI_AssociatedUMP property present"); diff --git a/src/api/Service/Exe/MidiDeviceManager.cpp b/src/api/Service/Exe/MidiDeviceManager.cpp index 823ea6df3..ff1ed2268 100644 --- a/src/api/Service/Exe/MidiDeviceManager.cpp +++ b/src/api/Service/Exe/MidiDeviceManager.cpp @@ -46,7 +46,10 @@ CMidiDeviceManager::Initialize( if (endpointManager != nullptr) { - auto initializeResult = endpointManager->Initialize((IUnknown*)this, (IUnknown*)EndpointProtocolManager.get(), transportSettingsJson.c_str()); + // need to do this to avoid an ambiguous IUnknown cast error + wil::com_ptr_nothrow protocolManager = EndpointProtocolManager.get(); + + auto initializeResult = endpointManager->Initialize((IUnknown*)this, (IUnknown*)protocolManager.get(), transportSettingsJson.c_str()); LOG_IF_FAILED(initializeResult); @@ -315,7 +318,7 @@ CMidiDeviceManager::ActivateEndpoint auto lock = m_MidiPortsLock.lock(); - OutputDebugString(__FUNCTION__ L": Checking for an existing port\n"); + OutputDebugString(__FUNCTION__ L": Checking for an existing port. \n"); const bool alreadyActivated = std::find_if(m_MidiPorts.begin(), m_MidiPorts.end(), [&](const std::unique_ptr& Port) { @@ -435,7 +438,7 @@ CMidiDeviceManager::ActivateEndpointInternal DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(devPropTrue)), &devPropTrue }); - midiPort->InstanceId = CreateInfo->pszInstanceId; + midiPort->InstanceId = internal::NormalizeDeviceInstanceIdCopy(CreateInfo->pszInstanceId); midiPort->MidiFlow = MidiFlow; midiPort->Enumerator = MidiOne?AUDIO_DEVICE_ENUMERATOR : MIDI_DEVICE_ENUMERATOR; @@ -538,7 +541,7 @@ CMidiDeviceManager::ActivateEndpointInternal if (DeviceInterfaceId) { - *DeviceInterfaceId = midiPort->DeviceInterfaceId.get(); + *DeviceInterfaceId = internal::NormalizeEndpointInterfaceIdCopy(midiPort->DeviceInterfaceId.get()).c_str(); } // success, transfer the midiPort to the list @@ -561,8 +564,7 @@ CMidiDeviceManager::UpdateEndpointProperties { OutputDebugString(L"\n" __FUNCTION__ " "); - std::wstring requestedInterfaceId(DeviceInterfaceId); - ::Windows::Devices::Midi2::Internal::InPlaceToLower(requestedInterfaceId); + auto requestedInterfaceId = internal::NormalizeEndpointInterfaceIdCopy(DeviceInterfaceId); //OutputDebugString(requestedInterfaceId.c_str()); //OutputDebugString(L"\n"); @@ -570,13 +572,10 @@ CMidiDeviceManager::UpdateEndpointProperties // locate the MIDIPORT auto item = std::find_if(m_MidiPorts.begin(), m_MidiPorts.end(), [&](const std::unique_ptr& Port) { - std::wstring portInterfaceId(Port->DeviceInterfaceId.get()); - - ::Windows::Devices::Midi2::Internal::InPlaceToLower(portInterfaceId); + auto portInterfaceId = internal::NormalizeEndpointInterfaceIdCopy(Port->DeviceInterfaceId.get()); // OutputDebugString((L" -- Checking " + portInterfaceId).c_str()); - return (portInterfaceId == requestedInterfaceId); }); @@ -698,15 +697,24 @@ CMidiDeviceManager::DeactivateEndpoint PCWSTR InstanceId ) { + OutputDebugString(__FUNCTION__ L" - enter. Cleaned instance id is: "); + + auto cleanId = internal::NormalizeDeviceInstanceIdCopy(InstanceId); + + OutputDebugString(cleanId.c_str()); + // there may be more than one SWD associated with this instance id, as we reuse // the instance id for the legacy SWD, it just has a different activator and InterfaceClass. do { // locate the MIDIPORT that identifies the swd + // NOTE: This uses InstanceId, not the Device Interface Id auto item = std::find_if(m_MidiPorts.begin(), m_MidiPorts.end(), [&](const std::unique_ptr& Port) { - // if this instance id already activated, then we cannot activate/create a second time, - if (InstanceId == Port->InstanceId) + // OutputDebugString(__FUNCTION__ L" Checking against stored instance id: "); + // OutputDebugString(Port->InstanceId.c_str()); + + if (cleanId == Port->InstanceId) { return true; } @@ -714,19 +722,23 @@ CMidiDeviceManager::DeactivateEndpoint return false; }); - // if the item was found + // exit if the item was not found. We're done. if (item == m_MidiPorts.end()) { break; } else { + OutputDebugString(__FUNCTION__ L" - Id found. Removing SWD\n"); + // Erasing this item from the list will free the unique_ptr and also trigger a SwDeviceClose on the item->SwDevice, // which will deactivate the device, done. m_MidiPorts.erase(item); } } while (TRUE); + OutputDebugString(__FUNCTION__ L" - exit\n"); + return S_OK; } @@ -751,6 +763,8 @@ CMidiDeviceManager::RemoveEndpoint HRESULT CMidiDeviceManager::Cleanup() { + OutputDebugString(__FUNCTION__ L"\n"); + m_MidiEndpointManagers.clear(); m_MidiPorts.clear(); diff --git a/src/api/Service/Exe/MidiEndpointProtocolManager.cpp b/src/api/Service/Exe/MidiEndpointProtocolManager.cpp index 156b11b50..06b3b176d 100644 --- a/src/api/Service/Exe/MidiEndpointProtocolManager.cpp +++ b/src/api/Service/Exe/MidiEndpointProtocolManager.cpp @@ -18,11 +18,125 @@ namespace internal = ::Windows::Devices::Midi2::Internal; // later to require the processor be added, and if an option is introduced // later, to ensure that it sets the option to forward messages along. + + + + + + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolManager::Initialize( + std::shared_ptr& ClientManager, + std::shared_ptr& DeviceManager +) +{ + OutputDebugString(__FUNCTION__ L""); + + m_clientManager = ClientManager; + m_deviceManager = DeviceManager; + + + m_allMessagesReceived.create(); + m_queueWorkerThreadWakeup.create(); + + // connect to the service. Needing a reference to this abstraction def + // creates a circular reference to the MidiSrv Abstraction. Not sure of + // a good way around that other than fixing up the ClientManager to make + // local connections with local handles reasonable. + RETURN_IF_FAILED(CoCreateInstance(__uuidof(Midi2MidiSrvAbstraction), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_serviceAbstraction))); + + try + { + // start background processing thread + + std::thread workerThread( + &CMidiEndpointProtocolManager::ThreadWorker, + this); + + m_queueWorkerThread = std::move(workerThread); + + // start up the worker thread + m_queueWorkerThread.detach(); + + } + CATCH_RETURN(); + + return S_OK; +} + + + + + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolManager::NegotiateAndRequestMetadata( + LPCWSTR DeviceInterfaceId, + BOOL PreferToSendJRTimestampsToEndpoint, + BOOL PreferToReceiveJRTimestampsFromEndpoint, + BYTE PreferredMidiProtocol, + WORD TimeoutMS +) noexcept +{ + OutputDebugString(__FUNCTION__ L""); + + ProtocolManagerWork work; + + // DEBUG + //TimeoutMS = 25000; + + + + work.EndpointInstanceId = DeviceInterfaceId; + work.PreferToSendJRTimestampsToEndpoint = PreferToSendJRTimestampsToEndpoint; + work.PreferToReceiveJRTimestampsFromEndpoint = PreferToReceiveJRTimestampsFromEndpoint; + work.PreferredMidiProtocol = PreferredMidiProtocol; + work.TimeoutMS = TimeoutMS; + + m_workQueue.push(std::move(work)); + + + // todo: signal event that there's new work + m_queueWorkerThreadWakeup.SetEvent(); + + + return S_OK; +} + + +HRESULT +CMidiEndpointProtocolManager::Cleanup() +{ + OutputDebugString(__FUNCTION__ L""); + + // TODO terminate any open threads and ensure they close up + + m_clientManager.reset(); + m_deviceManager.reset(); + + // clear the queue + while (m_workQueue.size() > 0) m_workQueue.pop(); + + m_shutdown = true; + m_queueWorkerThreadWakeup.SetEvent(); + + return S_OK; +} + + + + + + + + + _Use_decl_annotations_ HRESULT -CMidiEndpointProtocolNegotiationWorker::Callback(PVOID Data, UINT Size, LONGLONG Position, LONGLONG Context) +CMidiEndpointProtocolManager::Callback(PVOID Data, UINT Size, LONGLONG Position, LONGLONG Context) { - OutputDebugString(L"\n" __FUNCTION__); + OutputDebugString(L"\n" __FUNCTION__ L" - enter"); UNREFERENCED_PARAMETER(Position); UNREFERENCED_PARAMETER(Context); @@ -37,55 +151,59 @@ CMidiEndpointProtocolNegotiationWorker::Callback(PVOID Data, UINT Size, LONGLONG if (internal::GetUmpMessageTypeFromFirstWord(ump.word0) == MIDI_STREAM_MESSAGE_UMP_MESSAGE_TYPE) { - - // if message is a device identity message, set flag - auto messageStatus = internal::GetStatusFromStreamMessageFirstWord(ump.word0); switch (messageStatus) { case MIDI_STREAM_MESSAGE_STATUS_ENDPOINT_INFO_NOTIFICATION: - m_endpointInfoReceived = true; - m_declaredFunctionBlockCount = internal::GetEndpointInfoNotificationNumberOfFunctionBlocksFromSecondWord(ump.word1); + OutputDebugString(__FUNCTION__ L" - MIDI_STREAM_MESSAGE_STATUS_ENDPOINT_INFO_NOTIFICATION\n"); + m_currentWorkItem.TaskEndpointInfoReceived = true; + m_currentWorkItem.DeclaredFunctionBlockCount = internal::GetEndpointInfoNotificationNumberOfFunctionBlocksFromSecondWord(ump.word1); RequestAllFunctionBlocks(); break; case MIDI_STREAM_MESSAGE_STATUS_DEVICE_IDENTITY_NOTIFICATION: - m_deviceIdentityReceived = true; + OutputDebugString(__FUNCTION__ L" - MIDI_STREAM_MESSAGE_STATUS_DEVICE_IDENTITY_NOTIFICATION\n"); + m_currentWorkItem.TaskDeviceIdentityReceived = true; break; case MIDI_STREAM_MESSAGE_STATUS_STREAM_CONFIGURATION_NOTIFICATION: + OutputDebugString(__FUNCTION__ L" - MIDI_STREAM_MESSAGE_STATUS_STREAM_CONFIGURATION_NOTIFICATION\n"); ProcessStreamConfigurationRequest(ump); break; case MIDI_STREAM_MESSAGE_STATUS_FUNCTION_BLOCK_INFO_NOTIFICATION: - m_countFunctionBlocksReceived += 1; + OutputDebugString(__FUNCTION__ L" - MIDI_STREAM_MESSAGE_STATUS_FUNCTION_BLOCK_INFO_NOTIFICATION\n"); + m_currentWorkItem.CountFunctionBlocksReceived += 1; break; case MIDI_STREAM_MESSAGE_STATUS_FUNCTION_BLOCK_NAME_NOTIFICATION: + OutputDebugString(__FUNCTION__ L" - MIDI_STREAM_MESSAGE_STATUS_FUNCTION_BLOCK_NAME_NOTIFICATION\n"); if (internal::GetFormFromStreamMessageFirstWord(ump.word0) == MIDI_STREAM_MESSAGE_MULTI_FORM_COMPLETE || internal::GetFormFromStreamMessageFirstWord(ump.word0) == MIDI_STREAM_MESSAGE_MULTI_FORM_END) { - m_countFunctionBlockNamesReceived += 1; + m_currentWorkItem.CountFunctionBlockNamesReceived += 1; } break; case MIDI_STREAM_MESSAGE_STATUS_ENDPOINT_PRODUCT_INSTANCE_ID_NOTIFICATION: + OutputDebugString(__FUNCTION__ L" - MIDI_STREAM_MESSAGE_STATUS_ENDPOINT_PRODUCT_INSTANCE_ID_NOTIFICATION\n"); if (internal::GetFormFromStreamMessageFirstWord(ump.word0) == MIDI_STREAM_MESSAGE_MULTI_FORM_COMPLETE || internal::GetFormFromStreamMessageFirstWord(ump.word0) == MIDI_STREAM_MESSAGE_MULTI_FORM_END) { - m_endpointProductInstanceIdReceived = true; + m_currentWorkItem.TaskEndpointProductInstanceIdReceived = true; } break; case MIDI_STREAM_MESSAGE_STATUS_ENDPOINT_NAME_NOTIFICATION: + OutputDebugString(__FUNCTION__ L" - MIDI_STREAM_MESSAGE_STATUS_ENDPOINT_NAME_NOTIFICATION\n"); if (internal::GetFormFromStreamMessageFirstWord(ump.word0) == MIDI_STREAM_MESSAGE_MULTI_FORM_COMPLETE || internal::GetFormFromStreamMessageFirstWord(ump.word0) == MIDI_STREAM_MESSAGE_MULTI_FORM_END) { - m_endpointNameReceived = true; + m_currentWorkItem.TaskEndpointNameReceived = true; } break; @@ -93,37 +211,45 @@ CMidiEndpointProtocolNegotiationWorker::Callback(PVOID Data, UINT Size, LONGLONG OutputDebugString(L" Message is unidentified stream message\n"); break; } - - } else { // not a stream message. Ignore and move on + OutputDebugString(__FUNCTION__ L" - not a stream message\n"); + } } else { // couldn't fill the UMP. Shouldn't happen since we pre-validate + OutputDebugString(__FUNCTION__ L" - unable to fill UMP128 with the data\n"); + return E_FAIL; } } else { // Either null (hopefully not) or not a UMP128 so can't be a stream message. Fall out quickly + OutputDebugString(__FUNCTION__ L" - Not a message we're interested in\n" ); + } // check flags. If we've received everything, signal - if (m_endpointInfoReceived && - m_endpointNameReceived && - m_endpointProductInstanceIdReceived && - m_deviceIdentityReceived && - m_countFunctionBlockNamesReceived == m_declaredFunctionBlockCount && - m_countFunctionBlocksReceived == m_declaredFunctionBlockCount && - m_finalStreamNegotiationResponseReceived) + if (m_currentWorkItem.TaskEndpointInfoReceived && + m_currentWorkItem.TaskEndpointNameReceived && + m_currentWorkItem.TaskEndpointProductInstanceIdReceived && + m_currentWorkItem.TaskDeviceIdentityReceived && + m_currentWorkItem.CountFunctionBlockNamesReceived == m_currentWorkItem.DeclaredFunctionBlockCount && + m_currentWorkItem.CountFunctionBlocksReceived == m_currentWorkItem.DeclaredFunctionBlockCount && + m_currentWorkItem.TaskFinalStreamNegotiationResponseReceived) { + OutputDebugString(__FUNCTION__ L" - All messages received. Setting event\n"); + m_allMessagesReceived.SetEvent(); + + // done } @@ -131,11 +257,13 @@ CMidiEndpointProtocolNegotiationWorker::Callback(PVOID Data, UINT Size, LONGLONG } HRESULT -CMidiEndpointProtocolNegotiationWorker::RequestAllFunctionBlocks() +CMidiEndpointProtocolManager::RequestAllFunctionBlocks() { + OutputDebugString(__FUNCTION__ L""); + internal::PackedUmp128 ump{}; - if (m_endpoint) + if (m_currentWorkItem.Endpoint) { // first word ump.word0 = internal::BuildFunctionBlockDiscoveryRequestFirstWord( @@ -143,7 +271,7 @@ CMidiEndpointProtocolNegotiationWorker::RequestAllFunctionBlocks() MIDI_STREAM_MESSAGE_FUNCTION_BLOCK_REQUEST_MESSAGE_ALL_FILTER_FLAGS); // send it immediately - return m_endpoint->SendMidiMessage((byte*)&ump, (UINT)sizeof(ump), 0); + return m_currentWorkItem.Endpoint->SendMidiMessage((byte*)&ump, (UINT)sizeof(ump), 0); } return E_FAIL; @@ -151,12 +279,16 @@ CMidiEndpointProtocolNegotiationWorker::RequestAllFunctionBlocks() HRESULT -CMidiEndpointProtocolNegotiationWorker::RequestAllEndpointDiscoveryInformation() +CMidiEndpointProtocolManager::RequestAllEndpointDiscoveryInformation() { + OutputDebugString(__FUNCTION__ L""); + internal::PackedUmp128 ump{}; - if (m_endpoint) + if (m_currentWorkItem.Endpoint) { + OutputDebugString(__FUNCTION__ L" - sending discovery"); + // first word ump.word0 = internal::BuildEndpointDiscoveryRequestFirstWord(MIDI_PREFERRED_UMP_VERSION_MAJOR, MIDI_PREFERRED_UMP_VERSION_MINOR); @@ -165,7 +297,11 @@ CMidiEndpointProtocolNegotiationWorker::RequestAllEndpointDiscoveryInformation() internal::SetMidiWordMostSignificantByte4(ump.word1, filterBitmap); // send it immediately - return m_endpoint->SendMidiMessage((byte*)&ump, (UINT)sizeof(ump), 0); + return m_currentWorkItem.Endpoint->SendMidiMessage((byte*)&ump, (UINT)sizeof(ump), 0); + } + else + { + OutputDebugString(__FUNCTION__ L" - endpoint null"); } return E_FAIL; @@ -173,45 +309,47 @@ CMidiEndpointProtocolNegotiationWorker::RequestAllEndpointDiscoveryInformation() _Use_decl_annotations_ HRESULT -CMidiEndpointProtocolNegotiationWorker::ProcessStreamConfigurationRequest(internal::PackedUmp128 ump) +CMidiEndpointProtocolManager::ProcessStreamConfigurationRequest(internal::PackedUmp128 ump) { + OutputDebugString(__FUNCTION__ L""); + // see if all is what we want. If not, we'll send a request to change configuration. - if (m_endpoint) + if (m_currentWorkItem.Endpoint) { auto protocol = internal::GetStreamConfigurationNotificationProtocolFromFirstWord(ump.word0); auto endpointRxJR = internal::GetStreamConfigurationNotificationReceiveJRFromFirstWord(ump.word0); auto endpointTxJR = internal::GetStreamConfigurationNotificationTransmitJRFromFirstWord(ump.word0); - if (protocol != m_preferredMidiProtocol || - endpointRxJR != m_preferToSendJRTimestampsToEndpoint || - endpointTxJR != m_preferToReceiveJRTimestampsFromEndpoint) + if (protocol != m_currentWorkItem.PreferredMidiProtocol || + endpointRxJR != m_currentWorkItem.PreferToSendJRTimestampsToEndpoint || + endpointTxJR != m_currentWorkItem.PreferToReceiveJRTimestampsFromEndpoint) { - if (!m_alreadyTriedToNegotiationOnce) + if (!m_currentWorkItem.AlreadyTriedToNegotiationOnce) { internal::PackedUmp128 configurationRequestUmp{}; ump.word0 = internal::BuildStreamConfigurationRequestFirstWord( - m_preferredMidiProtocol, - m_preferToSendJRTimestampsToEndpoint, - m_preferToReceiveJRTimestampsFromEndpoint); + m_currentWorkItem.PreferredMidiProtocol, + m_currentWorkItem.PreferToSendJRTimestampsToEndpoint, + m_currentWorkItem.PreferToReceiveJRTimestampsFromEndpoint); - m_alreadyTriedToNegotiationOnce = true; + m_currentWorkItem.AlreadyTriedToNegotiationOnce = true; // send it immediately - return m_endpoint->SendMidiMessage((byte*)&configurationRequestUmp, (UINT)sizeof(configurationRequestUmp), 0); + return m_currentWorkItem.Endpoint->SendMidiMessage((byte*)&configurationRequestUmp, (UINT)sizeof(configurationRequestUmp), 0); } else { // we've already tried negotiating once. Don't do it again - m_finalStreamNegotiationResponseReceived = true; + m_currentWorkItem.TaskFinalStreamNegotiationResponseReceived = true; return S_OK; } } else { // all good on this try - m_finalStreamNegotiationResponseReceived = true; + m_currentWorkItem.TaskFinalStreamNegotiationResponseReceived = true; return S_OK; } @@ -222,20 +360,12 @@ CMidiEndpointProtocolNegotiationWorker::ProcessStreamConfigurationRequest(intern -_Use_decl_annotations_ HRESULT -CMidiEndpointProtocolNegotiationWorker::Start( - LPCWSTR DeviceInterfaceId, - BOOL PreferToSendJRTimestampsToEndpoint, - BOOL PreferToReceiveJRTimestampsFromEndpoint, - BYTE PreferredMidiProtocol, - WORD TimeoutMS, - wil::com_ptr_nothrow ServiceAbstraction) - +CMidiEndpointProtocolManager::ProcessCurrentWorkEntry() { - m_preferToSendJRTimestampsToEndpoint = PreferToSendJRTimestampsToEndpoint; - m_preferToReceiveJRTimestampsFromEndpoint = PreferToReceiveJRTimestampsFromEndpoint; - m_preferredMidiProtocol = PreferredMidiProtocol; + OutputDebugString(__FUNCTION__ L""); + OutputDebugString(m_currentWorkItem.EndpointInstanceId.c_str()); + // by using the existing abstractions and activation methods just // like any other client, we will get the transforms, including @@ -243,122 +373,90 @@ CMidiEndpointProtocolNegotiationWorker::Start( // metadata capture code here, and we don't need to make any // explicit call to the metadata capture transform plugin - RETURN_HR_IF_NULL(E_FAIL, ServiceAbstraction); - RETURN_IF_FAILED(ServiceAbstraction->Activate(__uuidof(IMidiBiDi), (void**)&m_endpoint)); + OutputDebugString(__FUNCTION__ L" - check for null service abstraction"); + RETURN_HR_IF_NULL(E_FAIL, m_serviceAbstraction); + + OutputDebugString(__FUNCTION__ L" - activate BiDi abstraction"); + RETURN_IF_FAILED(m_serviceAbstraction->Activate(__uuidof(IMidiBiDi), (void**)&(m_currentWorkItem.Endpoint))); // Create and open a connection to the endpoint, complete with metadata listeners + OutputDebugString(__FUNCTION__ L" - Initialize endpoint"); + DWORD mmcssTaskId{}; ABSTRACTIONCREATIONPARAMS abstractionCreationParams{ MidiDataFormat_UMP }; - RETURN_IF_FAILED(m_endpoint->Initialize( - DeviceInterfaceId, + RETURN_IF_FAILED(m_currentWorkItem.Endpoint->Initialize( + m_currentWorkItem.EndpointInstanceId.c_str(), &abstractionCreationParams, &mmcssTaskId, - this, - 0 + (IMidiCallback*)this, + MIDI_PROTOCOL_MANAGER_ENDPOINT_CREATION_CONTEXT )); - m_allMessagesReceived.create(); +// OutputDebugString(__FUNCTION__ L" - Resetting messages received event"); + +// if (m_allMessagesReceived.is_signaled()) m_allMessagesReceived.ResetEvent(); + + HRESULT hr = S_OK; // Send initial discovery request // the rest happens in response to messages in the callback - RETURN_IF_FAILED(RequestAllEndpointDiscoveryInformation()); + LOG_IF_FAILED(hr = RequestAllEndpointDiscoveryInformation()); - - // Wait until all metadata arrives or we timeout - if (!m_allMessagesReceived.wait(TimeoutMS)) + if (SUCCEEDED(hr)) { - // we didn't receive everything, but that's not a failure condition for this. + OutputDebugString(__FUNCTION__ L" - Requested discovery information"); + } + else + { + OutputDebugString(__FUNCTION__ L" - FAILED to request discovery information"); } - m_endpoint->Cleanup(); - - m_endpoint.reset(); - - return S_OK; -} - - - + if (SUCCEEDED(hr)) + { + // Wait until all metadata arrives or we timeout + if (!m_allMessagesReceived.wait(m_currentWorkItem.TimeoutMS)) + { + // we didn't receive everything, but that's not a failure condition for this. + } + } + if (m_allMessagesReceived.is_signaled()) m_allMessagesReceived.ResetEvent(); -_Use_decl_annotations_ -HRESULT -CMidiEndpointProtocolManager::Initialize( - std::shared_ptr& ClientManager, - std::shared_ptr& DeviceManager -) -{ - m_clientManager = ClientManager; - m_deviceManager = DeviceManager; + OutputDebugString(__FUNCTION__ L" - About to cleanup"); - // connect to the service. Needing a reference to this abstraction def - // creates a circular reference to the MidiSrv Abstraction. Not sure of - // a good way around that other than fixing up the ClientManager to make - // local connections with local handles reasonable. - RETURN_IF_FAILED(CoCreateInstance(__uuidof(Midi2MidiSrvAbstraction), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_serviceAbstraction))); + m_currentWorkItem.Endpoint->Cleanup(); + m_currentWorkItem.Endpoint = nullptr; - return S_OK; + return hr; } - - - -_Use_decl_annotations_ -HRESULT -CMidiEndpointProtocolManager::NegotiateAndRequestMetadata( - LPCWSTR DeviceInterfaceId, - BOOL PreferToSendJRTimestampsToEndpoint, - BOOL PreferToReceiveJRTimestampsFromEndpoint, - BYTE PreferredMidiProtocol, - WORD TimeoutMS -) noexcept +void CMidiEndpointProtocolManager::ThreadWorker() { - // We assume the endpoint manager which calls this function knows whether or not protocol - // negotiation etc. should happen. So we leave it up to the transport to correctly call - // this or not, and we just assume it made the correct choice. - // - // For now, we'll do this synchronously, but it should be in a separate thread in the future - - - CMidiEndpointProtocolNegotiationWorker worker; - - // TODO: Going to need to keep a map of these - //std::thread workerThread( - // &CMidiEndpointProtocolNegotiationWorker::Start(DeviceInterfaceId, PreferToSendJRTimestampsToEndpoint, PreferToReceiveJRTimestampsFromEndpoint, PreferredMidiProtocol, TimeoutMS), - // &worker); + while (!m_shutdown) + { + if (m_workQueue.size() > 0) + { + { + OutputDebugString(__FUNCTION__ L" - Item in queue. About to lock and pop :)"); - //m_queueWorkerThread = std::move(workerThread); + std::lock_guard lock{ m_queueMutex }; - // start up the worker thread - //m_queueWorkerThread.detach(); + m_currentWorkItem = std::move(m_workQueue.front()); + m_workQueue.pop(); + OutputDebugString(__FUNCTION__ L" - Obtained item from queue. About to process"); - // TODO: create and spin off a worker thread for this. + } - // Synchronous for first implementation - return worker.Start( - DeviceInterfaceId, - PreferToSendJRTimestampsToEndpoint, - PreferToReceiveJRTimestampsFromEndpoint, - PreferredMidiProtocol, - TimeoutMS, - m_serviceAbstraction - ); + // this will block until completed + LOG_IF_FAILED(ProcessCurrentWorkEntry()); + } + // todo + Sleep(300); + } } - - -HRESULT -CMidiEndpointProtocolManager::Cleanup() -{ - // TODO terminate any open threads and ensure they close up - - m_clientManager.reset(); - m_deviceManager.reset(); - - return S_OK; -} diff --git a/src/api/Service/Exe/MidiEndpointProtocolManager.h b/src/api/Service/Exe/MidiEndpointProtocolManager.h index d20306cb1..fa25d001a 100644 --- a/src/api/Service/Exe/MidiEndpointProtocolManager.h +++ b/src/api/Service/Exe/MidiEndpointProtocolManager.h @@ -8,52 +8,38 @@ #pragma once -class CMidiEndpointProtocolNegotiationWorker : public Microsoft::WRL::RuntimeClass< - Microsoft::WRL::RuntimeClassFlags, - IMidiCallback> -{ -public: - HRESULT Start( - _In_ LPCWSTR DeviceInterfaceId, - _In_ BOOL PreferToSendJRTimestampsToEndpoint, - _In_ BOOL PreferToReceiveJRTimestampsFromEndpoint, - _In_ BYTE PreferredMidiProtocol, - _In_ WORD TimeoutMS, - _In_ wil::com_ptr_nothrow ServiceAbstraction); - - STDMETHOD(Callback)(_In_ PVOID Data, _In_ UINT Size, _In_ LONGLONG Position, _In_ LONGLONG Context); - -private: - HRESULT RequestAllFunctionBlocks(); - HRESULT RequestAllEndpointDiscoveryInformation(); - HRESULT ProcessStreamConfigurationRequest(_In_ internal::PackedUmp128 ump); - - wil::com_ptr_nothrow m_endpoint; - - bool m_preferToSendJRTimestampsToEndpoint{ false }; - bool m_preferToReceiveJRTimestampsFromEndpoint{ false }; - uint8_t m_preferredMidiProtocol{ }; - - bool m_alreadyTriedToNegotiationOnce{ false }; - - wil::unique_event_nothrow m_allMessagesReceived; - bool m_endpointInfoReceived{ false }; - bool m_finalStreamNegotiationResponseReceived{ false }; - bool m_endpointNameReceived{ false }; - bool m_endpointProductInstanceIdReceived{ false }; - bool m_deviceIdentityReceived{ false }; - - uint8_t m_declaredFunctionBlockCount{ 0 }; - - uint8_t m_countFunctionBlocksReceived{ 0 }; - uint8_t m_countFunctionBlockNamesReceived{ 0 }; +#include +#include +struct ProtocolManagerWork +{ + std::wstring EndpointInstanceId{}; + bool PreferToSendJRTimestampsToEndpoint{ false }; + bool PreferToReceiveJRTimestampsFromEndpoint{ false }; + uint8_t PreferredMidiProtocol{}; + uint16_t TimeoutMS{ 2000 }; + + wil::com_ptr_nothrow Endpoint; + + bool AlreadyTriedToNegotiationOnce{ false }; + + bool TaskEndpointInfoReceived{ false }; + bool TaskFinalStreamNegotiationResponseReceived{ false }; + bool TaskEndpointNameReceived{ false }; + bool TaskEndpointProductInstanceIdReceived{ false }; + bool TaskDeviceIdentityReceived{ false }; + + uint8_t DeclaredFunctionBlockCount{ 0 }; + + uint8_t CountFunctionBlocksReceived{ 0 }; + uint8_t CountFunctionBlockNamesReceived{ 0 }; }; class CMidiEndpointProtocolManager : public Microsoft::WRL::RuntimeClass< Microsoft::WRL::RuntimeClassFlags, - IMidiEndpointProtocolManagerInterface> + IMidiEndpointProtocolManagerInterface, + IMidiCallback> { public: @@ -73,13 +59,36 @@ class CMidiEndpointProtocolManager : public Microsoft::WRL::RuntimeClass< _In_ WORD TimeoutMS ); + + STDMETHOD(Callback)(_In_ PVOID Data, _In_ UINT Size, _In_ LONGLONG Position, _In_ LONGLONG Context); + HRESULT Cleanup(); private: + void ThreadWorker(); + + HRESULT ProcessCurrentWorkEntry(); + + + HRESULT RequestAllFunctionBlocks(); + HRESULT RequestAllEndpointDiscoveryInformation(); + HRESULT ProcessStreamConfigurationRequest(_In_ internal::PackedUmp128 ump); + std::shared_ptr m_clientManager; std::shared_ptr m_deviceManager; + wil::com_ptr_nothrow m_serviceAbstraction; + std::mutex m_queueMutex; + std::queue m_workQueue; + + std::thread m_queueWorkerThread; + + ProtocolManagerWork m_currentWorkItem; + wil::unique_event_nothrow m_allMessagesReceived; + wil::unique_event_nothrow m_queueWorkerThreadWakeup; + // true if we're closing down + bool m_shutdown{ false }; }; diff --git a/src/api/Service/Exe/MidiTransformPipe.cpp b/src/api/Service/Exe/MidiTransformPipe.cpp index 5e76e2bc9..2c7a8fc3f 100644 --- a/src/api/Service/Exe/MidiTransformPipe.cpp +++ b/src/api/Service/Exe/MidiTransformPipe.cpp @@ -19,7 +19,7 @@ CMidiTransformPipe::Initialize( ) { wil::com_ptr_nothrow midiTransform; - TRANSFORMCREATIONPARAMS creationParams {0}; + TRANSFORMCREATIONPARAMS creationParams {}; m_TransformGuid = CreationParams->TransformGuid; m_DataFormatIn = CreationParams->DataFormatIn; diff --git a/src/api/Service/Inc/MidiDevicePipe.h b/src/api/Service/Inc/MidiDevicePipe.h index eab2f1ef6..d381add4b 100644 --- a/src/api/Service/Inc/MidiDevicePipe.h +++ b/src/api/Service/Inc/MidiDevicePipe.h @@ -35,7 +35,7 @@ class CMidiDevicePipe : public CMidiPipe private: wil::critical_section m_DevicePipeLock; - winrt::guid m_AbstractionGuid; + winrt::guid m_AbstractionGuid{}; wil::com_ptr_nothrow m_MidiBiDiDevice; wil::com_ptr_nothrow m_MidiInDevice; wil::com_ptr_nothrow m_MidiOutDevice; diff --git a/src/api/Service/Inc/MidiTransformPipe.h b/src/api/Service/Inc/MidiTransformPipe.h index 3574843ca..22bc135b8 100644 --- a/src/api/Service/Inc/MidiTransformPipe.h +++ b/src/api/Service/Inc/MidiTransformPipe.h @@ -37,9 +37,9 @@ class CMidiTransformPipe : public CMidiPipe private: wil::com_ptr_nothrow m_MidiDataTransform; - winrt::guid m_TransformGuid; - MidiDataFormat m_DataFormatIn; - MidiDataFormat m_DataFormatOut; - MidiFlow m_Flow; + winrt::guid m_TransformGuid{}; + MidiDataFormat m_DataFormatIn{}; + MidiDataFormat m_DataFormatOut{}; + MidiFlow m_Flow{}; }; diff --git a/src/api/Test/Libs/MidiSWEnum/MidiSWEnum.cpp b/src/api/Test/Libs/MidiSWEnum/MidiSWEnum.cpp index f61dd8c6f..9eb3df26c 100644 --- a/src/api/Test/Libs/MidiSWEnum/MidiSWEnum.cpp +++ b/src/api/Test/Libs/MidiSWEnum/MidiSWEnum.cpp @@ -72,7 +72,7 @@ MidiSWDeviceEnum::EnumerateDevices( prop = device.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_SupportedDataFormats)); RETURN_HR_IF_NULL(E_INVALIDARG, prop); - supportedDataFormats = (MidiDataFormat) winrt::unbox_value(prop); + supportedDataFormats = (MidiDataFormat) winrt::unbox_value(prop); prop = device.Properties().Lookup(winrt::to_hstring(L"System.Devices.InterfaceClassGuid")); RETURN_HR_IF_NULL(E_INVALIDARG, prop); diff --git a/src/api/Transform/EndpointMetadataListenerTransform/Midi2.EndpointMetadataListenerMidiTransform.cpp b/src/api/Transform/EndpointMetadataListenerTransform/Midi2.EndpointMetadataListenerMidiTransform.cpp index fd5c01ce4..a0d9940c6 100644 --- a/src/api/Transform/EndpointMetadataListenerTransform/Midi2.EndpointMetadataListenerMidiTransform.cpp +++ b/src/api/Transform/EndpointMetadataListenerTransform/Midi2.EndpointMetadataListenerMidiTransform.cpp @@ -157,6 +157,10 @@ CMidi2EndpointMetadataListenerMidiTransform::SendMidiMessage( if (internal::GetUmpMessageTypeFromFirstWord(ump.word0) == 0xF) { + OutputDebugString(__FUNCTION__ L" - Type F message. About to process."); + + // TODO: this should get thrown into a work queue and processed out of band + ProcessStreamMessage(ump, timestamp); } else diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointPropertiesCommand.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointPropertiesCommand.cs index 53f46e9f3..02ef3b993 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointPropertiesCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointPropertiesCommand.cs @@ -121,7 +121,7 @@ public override int Execute(CommandContext context, Settings settings) table.AddRow("Receives JR Timestamps", di.ConfiguredToReceiveJRTimestamps.ToString()); table.AddEmptyRow(); - table.AddRow(AnsiMarkupFormatter.FormatTableColumnHeading("Capabilities"), ""); + table.AddRow(AnsiMarkupFormatter.FormatTableColumnHeading("Declared Capabilities"), ""); table.AddRow("Multi-client", di.SupportsMultiClient.ToString()); table.AddRow("MIDI 1.0 Protocol", di.SupportsMidi10Protocol.ToString()); table.AddRow("MIDI 2.0 Protocol", di.SupportsMidi20Protocol.ToString()); @@ -146,9 +146,9 @@ public override int Execute(CommandContext context, Settings settings) table.AddEmptyRow(); table.AddEmptyRow(); table.AddRow(AnsiMarkupFormatter.FormatTableColumnHeading("Function Blocks"), ""); - table.AddEmptyRow(); table.AddRow("Static Function Blocks?", di.HasStaticFunctionBlocks.ToString()); table.AddRow("Declared Function Block Count", di.FunctionBlockCount.ToString()); + table.AddEmptyRow(); if (di.FunctionBlocks.Count > 0) { @@ -246,6 +246,11 @@ public override int Execute(CommandContext context, Settings settings) { table.AddEmptyRow(); table.AddRow(AnsiMarkupFormatter.FormatTableColumnHeading("Group Terminal Blocks"), ""); + + if (di.FunctionBlocks.Count > 0) + { + table.AddRow("Function blocks are present. Applications should use the Function Blocks and their group declarations instead of the Group Terminal Blocks.", ""); + } foreach (var groupTerminalBlock in di.GroupTerminalBlocks) { diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessagesFileCommand.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessagesFileCommand.cs index bb69f215f..f85fdf4bc 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessagesFileCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessagesFileCommand.cs @@ -100,6 +100,10 @@ public override int Execute(CommandContext context, Settings settings) AnsiConsole.MarkupLine(Strings.SendMessageSendingThroughEndpointLabel + ": " + AnsiMarkupFormatter.FormatDeviceInstanceId(endpointId)); AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("Temporary UI change: Only error lines will be displayed when sending messages."); + + + using var session = MidiSession.CreateSession($"{Strings.AppShortName} - {Strings.SendMessageSessionNameSuffix}"); if (session == null) { @@ -123,12 +127,66 @@ public override int Execute(CommandContext context, Settings settings) return (int)MidiConsoleReturnCode.ErrorOpeningEndpointConnection; } - var table = new Table(); + // TODO: Consider creating a message sender thread worker object that is shared + // between this and the send-message command // if not verbose, just show a status spinner + //AnsiConsole.Progress() + // .Start(ctx => + // { + // //if (settings.DelayBetweenMessages == 0 && (settings.Count * (settings.Words!.Length + 2)) > bufferWarningThreshold) + // //{ + // // AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatWarning(Strings.SendMessageFloodWarning)); + // // AnsiConsole.WriteLine(); + // //} + + // var sendTask = ctx.AddTask("[white]Sending messages[/]"); + // sendTask.MaxValue = settings.Count; + // sendTask.Value = 0; + + // messageSenderThread.Start(); + + + // AnsiConsole.MarkupLine(Strings.SendMessagePressEscapeToStopSendingMessage); + // AnsiConsole.WriteLine(); + // while (stillSending) + // { + // // check for input + + // if (Console.KeyAvailable) + // { + // var keyInfo = Console.ReadKey(true); + + // if (keyInfo.Key == ConsoleKey.Escape) + // { + // stillSending = false; + + // // wake up the threads so they terminate + // m_messageDispatcherThreadWakeup.Set(); + + // AnsiConsole.WriteLine(); + // AnsiConsole.MarkupLine("🛑 " + Strings.SendMessageEscapePressedMessage); + // } + + // } + + // sendTask.Value = messagesSent; + // ctx.Refresh(); + + // if (stillSending) Thread.Sleep(100); + // } + + // sendTask.Value = messagesSent; + + // }); // if verbose, spin up a live table + var table = new Table(); + + UInt32 countSkippedLines = 0; + UInt32 countFailedLines = 0; + UInt32 countMessagesSent = 0; AnsiConsole.Live(table) .Start(ctx => @@ -186,13 +244,19 @@ public override int Execute(CommandContext context, Settings settings) line = fileStream.ReadLine(); if (line == null) + { + countSkippedLines++; continue; + } lineNumber++; // skip over comments and white space if (LineIsIgnorable(line)) + { + countSkippedLines++; continue; + } // if we're using Auto for the delimiter, each line is evaluated in case the file is mixed // if you don't want to take this hit, specify the delimiter on the command line @@ -218,10 +282,19 @@ public override int Execute(CommandContext context, Settings settings) } // send the message - connection.SendMessageWordArray(timestamp, words, 0, (byte)words.Count()); + if (MidiEndpointConnection.SendMessageSucceeded(connection.SendMessageWordArray(timestamp, words, 0, (byte)words.Count()))) + { + countMessagesSent++; + } + else + { + countFailedLines++; + } + string detailedMessageType = MidiMessageUtility.GetMessageFriendlyNameFromFirstWord(words[0]); +#if false // display the sent data table.AddRow( AnsiMarkupFormatter.FormatGeneralNumber(lineNumber), @@ -232,10 +305,14 @@ public override int Execute(CommandContext context, Settings settings) ); ctx.Refresh(); - Thread.Sleep(settings.DelayBetweenMessages); +#endif + + } else { + countFailedLines++; + // invalid UMP table.AddRow( AnsiMarkupFormatter.FormatGeneralNumber(lineNumber), @@ -245,7 +322,11 @@ public override int Execute(CommandContext context, Settings settings) ); ctx.Refresh(); - Thread.Sleep(0); + } + + if (settings.DelayBetweenMessages > 0) + { + Thread.Sleep(settings.DelayBetweenMessages); } } else @@ -271,6 +352,22 @@ public override int Execute(CommandContext context, Settings settings) }); + if (countMessagesSent > 0) + { + AnsiConsole.WriteLine($"{countMessagesSent.ToString("N0")} message(s) sent."); + } + + if (countSkippedLines > 0) + { + AnsiConsole.WriteLine($"{countSkippedLines.ToString("N0")} lines skipped (empty or comments)."); + } + + if (countFailedLines > 0) + { + AnsiConsole.WriteLine(AnsiMarkupFormatter.FormatError($"{countFailedLines.ToString("N0")} lines with errors.")); + } + + if (session != null) session.Dispose(); diff --git a/src/user-tools/midi-console/Midi/Midi.csproj b/src/user-tools/midi-console/Midi/Midi.csproj index efb478b4d..d5a14373a 100644 --- a/src/user-tools/midi-console/Midi/Midi.csproj +++ b/src/user-tools/midi-console/Midi/Midi.csproj @@ -31,7 +31,7 @@ - +