From 5aa8d08c779107dfb7e5729a89f772dca53ffb73 Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Tue, 16 Jan 2024 14:23:16 -0500 Subject: [PATCH 1/3] Attempting to harden the service against malformed json Likely needs a bit more, but this is a start --- .../configuration/Default.midiconfig.json | 4 +- build/staging/version/BundleInfo.wxi | 2 +- .../Midi2.KSMidiEndpointManager.cpp | 98 +++++++++++-------- .../Midi2.KSMidiEndpointManager.h | 2 +- .../Service/Exe/MidiConfigurationManager.cpp | 90 ++++++++++------- 5 files changed, 112 insertions(+), 84 deletions(-) diff --git a/build/staging/configuration/Default.midiconfig.json b/build/staging/configuration/Default.midiconfig.json index 9d175fd3b..d6efd29ea 100644 --- a/build/staging/configuration/Default.midiconfig.json +++ b/build/staging/configuration/Default.midiconfig.json @@ -10,9 +10,7 @@ { "{26FA740D-469C-4D33-BEB1-3885DE7D6DF1}": { - "_comment": "KS MIDI (USB etc.)", - - + "_comment": "KS MIDI (USB etc.)" }, "{C95DCD1F-CDE3-4C2D-913C-528CB8A4CBE6}": { diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi index 8bdde3584..3f700834c 100644 --- a/build/staging/version/BundleInfo.wxi +++ b/build/staging/version/BundleInfo.wxi @@ -1,4 +1,4 @@ - + diff --git a/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.cpp b/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.cpp index 8b1da668f..1d450e866 100644 --- a/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.cpp +++ b/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.cpp @@ -47,7 +47,10 @@ CMidi2KSMidiEndpointManager::Initialize( if (jsonString != L"") { - m_jsonObject = json::JsonObject::Parse(configurationJson); + if (json::JsonObject::TryParse(configurationJson, m_jsonObject)) + { + // worked + } } } } @@ -475,68 +478,77 @@ _Use_decl_annotations_ HRESULT CMidi2KSMidiEndpointManager::ApplyUserConfiguration(std::wstring deviceInterfaceId) { - // if we have no configuration, that's ok - if (m_jsonObject == nullptr) return S_OK; + OutputDebugString(L"\n" __FUNCTION__); + try + { + // if we have no configuration, that's ok + if (m_jsonObject == nullptr) return S_OK; - std::vector endpointProperties; - // for now, we only support lookup by the deviceInterfaceId, so this code is easier + std::vector endpointProperties; - winrt::hstring endpointSettingsKey = winrt::to_hstring(MIDI_CONFIG_JSON_ENDPOINT_IDENTIFIER_SWD) + deviceInterfaceId; + // for now, we only support lookup by the deviceInterfaceId, so this code is easier - //OutputDebugString(L"\n" __FUNCTION__ L" Key: "); - //OutputDebugString(endpointSettingsKey.c_str()); - //OutputDebugString(L"\n"); + winrt::hstring endpointSettingsKey = winrt::to_hstring(MIDI_CONFIG_JSON_ENDPOINT_IDENTIFIER_SWD) + deviceInterfaceId; + //OutputDebugString(L" Key: "); + //OutputDebugString(endpointSettingsKey.c_str()); + //OutputDebugString(L"\n"); - if (m_jsonObject.HasKey(endpointSettingsKey)) - { - json::JsonObject endpointSettings = m_jsonObject.GetNamedObject(endpointSettingsKey); - // Get the user-specified endpoint name - if (endpointSettings != nullptr && endpointSettings.HasKey(MIDI_CONFIG_JSON_ENDPOINT_USER_SUPPLIED_NAME_PROPERTY_KEY)) + if (m_jsonObject.HasKey(endpointSettingsKey)) { - auto name = endpointSettings.GetNamedString(MIDI_CONFIG_JSON_ENDPOINT_USER_SUPPLIED_NAME_PROPERTY_KEY); + json::JsonObject endpointSettings = m_jsonObject.GetNamedObject(endpointSettingsKey); - endpointProperties.push_back({ {PKEY_MIDI_UserSuppliedEndpointName, DEVPROP_STORE_SYSTEM, nullptr}, - DEVPROP_TYPE_STRING, static_cast((name.size() + 1) * sizeof(WCHAR)), (PVOID)name.c_str() }); - } + // Get the user-specified endpoint name + if (endpointSettings != nullptr && endpointSettings.HasKey(MIDI_CONFIG_JSON_ENDPOINT_USER_SUPPLIED_NAME_PROPERTY_KEY)) + { + auto name = endpointSettings.GetNamedString(MIDI_CONFIG_JSON_ENDPOINT_USER_SUPPLIED_NAME_PROPERTY_KEY); - // Get the user-specified endpoint description - if (endpointSettings != nullptr && endpointSettings.HasKey(MIDI_CONFIG_JSON_ENDPOINT_USER_SUPPLIED_DESCRIPTION_PROPERTY_KEY)) - { - auto description = endpointSettings.GetNamedString(MIDI_CONFIG_JSON_ENDPOINT_USER_SUPPLIED_DESCRIPTION_PROPERTY_KEY); + endpointProperties.push_back({ {PKEY_MIDI_UserSuppliedEndpointName, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_STRING, static_cast((name.size() + 1) * sizeof(WCHAR)), (PVOID)name.c_str() }); + } - endpointProperties.push_back({ {PKEY_MIDI_UserSuppliedDescription, DEVPROP_STORE_SYSTEM, nullptr}, - DEVPROP_TYPE_STRING, static_cast((description.size() + 1) * sizeof(WCHAR)), (PVOID)description.c_str() }); - } + // Get the user-specified endpoint description + if (endpointSettings != nullptr && endpointSettings.HasKey(MIDI_CONFIG_JSON_ENDPOINT_USER_SUPPLIED_DESCRIPTION_PROPERTY_KEY)) + { + auto description = endpointSettings.GetNamedString(MIDI_CONFIG_JSON_ENDPOINT_USER_SUPPLIED_DESCRIPTION_PROPERTY_KEY); - // Get the user-specified multiclient override - if (endpointSettings != nullptr && endpointSettings.HasKey(MIDI_CONFIG_JSON_ENDPOINT_FORCE_SINGLE_CLIENT_PROPERTY_KEY)) - { - auto forceSingleClient = endpointSettings.GetNamedBoolean(MIDI_CONFIG_JSON_ENDPOINT_FORCE_SINGLE_CLIENT_PROPERTY_KEY); + endpointProperties.push_back({ {PKEY_MIDI_UserSuppliedDescription, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_STRING, static_cast((description.size() + 1) * sizeof(WCHAR)), (PVOID)description.c_str() }); + } - if (forceSingleClient) + // Get the user-specified multiclient override + if (endpointSettings != nullptr && endpointSettings.HasKey(MIDI_CONFIG_JSON_ENDPOINT_FORCE_SINGLE_CLIENT_PROPERTY_KEY)) { - DEVPROP_BOOLEAN devPropFalse = DEVPROP_FALSE; + auto forceSingleClient = endpointSettings.GetNamedBoolean(MIDI_CONFIG_JSON_ENDPOINT_FORCE_SINGLE_CLIENT_PROPERTY_KEY); - endpointProperties.push_back({ {PKEY_MIDI_SupportsMulticlient, DEVPROP_STORE_SYSTEM, nullptr}, - DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(devPropFalse)), (PVOID)&devPropFalse }); + if (forceSingleClient) + { + DEVPROP_BOOLEAN devPropFalse = DEVPROP_FALSE; + + endpointProperties.push_back({ {PKEY_MIDI_SupportsMulticlient, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(devPropFalse)), (PVOID)&devPropFalse }); + } } - } - // apply supported property changes. - if (endpointProperties.size() > 0) - { - return m_MidiDeviceManager->UpdateEndpointProperties( - (LPCWSTR)deviceInterfaceId.c_str(), - (ULONG)endpointProperties.size(), - (PVOID)endpointProperties.data()); + // apply supported property changes. + if (endpointProperties.size() > 0) + { + return m_MidiDeviceManager->UpdateEndpointProperties( + (LPCWSTR)deviceInterfaceId.c_str(), + (ULONG)endpointProperties.size(), + (PVOID)endpointProperties.data()); + } } - } - return S_OK; + return S_OK; + } + catch (...) + { + return E_FAIL; + } } diff --git a/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.h b/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.h index d2b9f32d3..c595d7ab1 100644 --- a/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.h +++ b/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.h @@ -63,7 +63,7 @@ class CMidi2KSMidiEndpointManager : wil::unique_event m_EnumerationCompleted{wil::EventOptions::None}; // may want to change this to the actual json object. - winrt::Windows::Data::Json::JsonObject m_jsonObject{}; + winrt::Windows::Data::Json::JsonObject m_jsonObject{ nullptr }; HRESULT ApplyUserConfiguration(_In_ std::wstring deviceInterfaceId); }; diff --git a/src/api/Service/Exe/MidiConfigurationManager.cpp b/src/api/Service/Exe/MidiConfigurationManager.cpp index 5e8f1d7de..a9e609030 100644 --- a/src/api/Service/Exe/MidiConfigurationManager.cpp +++ b/src/api/Service/Exe/MidiConfigurationManager.cpp @@ -341,7 +341,7 @@ std::wstring CMidiConfigurationManager::GetCurrentConfigurationFileName() noexce HRESULT CMidiConfigurationManager::Initialize() { - //OutputDebugString(L"" __FUNCTION__); + OutputDebugString(L"\n" __FUNCTION__); // load the current configuration @@ -371,13 +371,10 @@ HRESULT CMidiConfigurationManager::Initialize() // try to read the text from the file fileContents = winrt::Windows::Storage::FileIO::ReadTextAsync(file).get(); - - //OutputDebugString(L"" __FUNCTION__); - //OutputDebugString(fileContents.c_str()); } catch (...) { - OutputDebugString(L"Exception opening json config file"); + OutputDebugString(L"Exception opening json config file\n"); // file does not exist or we can't open it // we don't fail if no configuration file, we just don't config anything @@ -397,20 +394,28 @@ HRESULT CMidiConfigurationManager::Initialize() try { - m_jsonObject = json::JsonObject::Parse(fileContents); + if (json::JsonObject::TryParse(fileContents, m_jsonObject)) + { + // worked + OutputDebugString(L"Parsing json worked\n"); + } + else + { + OutputDebugString(L"Parsing json failed\n"); + } } CATCH_LOG() } else { - OutputDebugString(L"Config file contents are empty"); + OutputDebugString(L"Config file contents are empty\n"); } } else { // config file is missing - OutputDebugString(L"Config file is missing"); + OutputDebugString(L"Config file is missing, but that's ok.\n"); return S_OK; } @@ -422,31 +427,37 @@ HRESULT CMidiConfigurationManager::Initialize() _Use_decl_annotations_ std::wstring CMidiConfigurationManager::GetConfigurationForTransportAbstraction(GUID abstractionGuid) const noexcept { - OutputDebugString(L"" __FUNCTION__); + OutputDebugString(L"\n" __FUNCTION__); - auto key = GuidToString(abstractionGuid); + try + { + OutputDebugString(L"" __FUNCTION__); -// OutputDebugString(key.c_str()); + auto key = GuidToString(abstractionGuid); - if (m_jsonObject != nullptr) - { - // probably need to normalize these to ignore case. Not sure if WinRT Json dictionary is case-sensitive - if (m_jsonObject.HasKey(winrt::to_hstring(MIDI_CONFIG_JSON_TRANSPORT_PLUGIN_SETTINGS_OBJECT))) - { - auto plugins = m_jsonObject.GetNamedObject(MIDI_CONFIG_JSON_TRANSPORT_PLUGIN_SETTINGS_OBJECT); + // OutputDebugString(key.c_str()); - if (plugins.HasKey(key)) + if (m_jsonObject != nullptr) + { + // probably need to normalize these to ignore case. Not sure if WinRT Json dictionary is case-sensitive + if (m_jsonObject.HasKey(winrt::to_hstring(MIDI_CONFIG_JSON_TRANSPORT_PLUGIN_SETTINGS_OBJECT))) { - auto thisPlugin = plugins.GetNamedObject(key); + auto plugins = m_jsonObject.GetNamedObject(MIDI_CONFIG_JSON_TRANSPORT_PLUGIN_SETTINGS_OBJECT); - std::wstring jsonString = (std::wstring)thisPlugin.Stringify(); + if (plugins.HasKey(key)) + { + auto thisPlugin = plugins.GetNamedObject(key); + + std::wstring jsonString = (std::wstring)thisPlugin.Stringify(); - //OutputDebugString(jsonString.c_str()); + //OutputDebugString(jsonString.c_str()); - return jsonString; + return jsonString; + } } } } + CATCH_LOG(); return L""; } @@ -456,31 +467,37 @@ std::wstring CMidiConfigurationManager::GetConfigurationForTransportAbstraction( _Use_decl_annotations_ std::wstring CMidiConfigurationManager::GetConfigurationForEndpointProcessingTransform(GUID abstractionGuid) const noexcept { - // OutputDebugString(L"" __FUNCTION__); + OutputDebugString(L"\n" __FUNCTION__); + + try + { + // OutputDebugString(L"" __FUNCTION__); - auto key = GuidToString(abstractionGuid); + auto key = GuidToString(abstractionGuid); -// OutputDebugString(key.c_str()); + // OutputDebugString(key.c_str()); - if (m_jsonObject != nullptr) - { - // probably need to normalize these to ignore case. Not sure if WinRT Json dictionary is case-sensitive - if (m_jsonObject.HasKey(winrt::to_hstring(MIDI_CONFIG_JSON_ENDPOINT_PROCESSING_PLUGIN_SETTINGS_OBJECT))) + if (m_jsonObject != nullptr) { - auto plugins = m_jsonObject.GetNamedObject(MIDI_CONFIG_JSON_ENDPOINT_PROCESSING_PLUGIN_SETTINGS_OBJECT); - - if (plugins.HasKey(key)) + // probably need to normalize these to ignore case. Not sure if WinRT Json dictionary is case-sensitive + if (m_jsonObject.HasKey(winrt::to_hstring(MIDI_CONFIG_JSON_ENDPOINT_PROCESSING_PLUGIN_SETTINGS_OBJECT))) { - auto thisPlugin = plugins.GetNamedObject(key); + auto plugins = m_jsonObject.GetNamedObject(MIDI_CONFIG_JSON_ENDPOINT_PROCESSING_PLUGIN_SETTINGS_OBJECT); - std::wstring jsonString = (std::wstring)thisPlugin.Stringify(); + if (plugins.HasKey(key)) + { + auto thisPlugin = plugins.GetNamedObject(key); - OutputDebugString(jsonString.c_str()); + std::wstring jsonString = (std::wstring)thisPlugin.Stringify(); - return jsonString; + OutputDebugString(jsonString.c_str()); + + return jsonString; + } } } } + CATCH_LOG(); return L""; } @@ -489,6 +506,7 @@ std::wstring CMidiConfigurationManager::GetConfigurationForEndpointProcessingTra HRESULT CMidiConfigurationManager::Cleanup() noexcept { + OutputDebugString(L"\n" __FUNCTION__); return S_OK; From b6f844516e50dd176ec29b5068b6d9e10fd5ba10 Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Tue, 16 Jan 2024 14:23:53 -0500 Subject: [PATCH 2/3] Another significant performance improvement in console monitoring This one actually keeps up with incoming messages --- .../Endpoint/EndpointMonitorCommand.cs | 189 +++++++++++++----- .../Endpoint/EndpointSendMessageCommand.cs | 20 +- .../Midi/CustomTable/MidiMessageTable.cs | 12 ++ .../Midi/Resources/Strings.Designer.cs | 9 + .../midi-console/Midi/Resources/Strings.resx | 3 + 5 files changed, 186 insertions(+), 47 deletions(-) diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs index 3e14efbac..5738b9ece 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs @@ -34,14 +34,9 @@ internal class EndpointMonitorCommand : Command { // we have this struct so we can separate the relatively fast received processing // and its calculations from the comparatively slow displays processing - private Queue m_receivedMessagesQueue = new Queue(); - private Queue m_displayMessageQueue = new Queue(); - private Queue m_fileWriterMessagesQueue = new Queue(); - - - private static AutoResetEvent m_displayMessageThreadWakeup = new AutoResetEvent(false); - private static AutoResetEvent m_fileMessageThreadWakeup = new AutoResetEvent(false); - private static AutoResetEvent m_terminateMessageListenerThread = new AutoResetEvent(false); + private Queue m_receivedMessagesQueue = new Queue(1000); + private Queue m_displayMessageQueue = new Queue(1000); + private Queue m_fileWriterMessagesQueue = new Queue(1000); @@ -85,6 +80,11 @@ public sealed class Settings : MessageListenerCommandSettings [DefaultValue(CaptureFieldDelimiter.Space)] public CaptureFieldDelimiter FieldDelimiter { get; set; } + //[EnumLocalizedDescription("ParameterMonitorEndpointSkipToKeepUp", typeof(CaptureFieldDelimiter))] + //[CommandOption("-k|--keep-up-display")] + //[DefaultValue(false)] + //public bool SkipToKeepUp { get; set; } + } @@ -151,6 +151,13 @@ private void MonitorEndpointConnectionStatusInTheBackground(string endpointId) public override int Execute(CommandContext context, Settings settings) { + using AutoResetEvent m_displayMessageThreadWakeup = new AutoResetEvent(false); + using AutoResetEvent m_fileMessageThreadWakeup = new AutoResetEvent(false); + using AutoResetEvent m_messageDispatcherThreadWakeup = new AutoResetEvent(false); + + using AutoResetEvent m_terminateMessageListenerThread = new AutoResetEvent(false); + + MidiMessageTable displayTable = new MidiMessageTable(settings.Verbose); bool capturingToFile = false; @@ -217,6 +224,10 @@ public override int Execute(CommandContext context, Settings settings) UInt64 lastReceivedTimestamp = 0; UInt32 index = 0; + UInt64 firstMessageReceivedEventTimestamp = 0; + UInt64 lastMessageReceivedEventTimestamp = 0; + UInt32 countMessagesReceived = 0; + MonitorEndpointConnectionStatusInTheBackground(endpointId); @@ -235,6 +246,14 @@ public override int Execute(CommandContext context, Settings settings) connection.MessageReceived += (s, e) => { + // this captures stats to tell us how long it takes to receive and queue messages + lastMessageReceivedEventTimestamp = MidiClock.Now; + if (firstMessageReceivedEventTimestamp == 0) firstMessageReceivedEventTimestamp = lastMessageReceivedEventTimestamp; + countMessagesReceived++; + + // this captures intended timestamps, not actual + if (startTimestamp == 0) startTimestamp = e.Timestamp; + // helps prevent any race conditions with main loop and its output if (!continueWaiting) return; @@ -263,6 +282,8 @@ public override int Execute(CommandContext context, Settings settings) m_receivedMessagesQueue.Enqueue(receivedMessage); } + m_messageDispatcherThreadWakeup.Set(); + if (settings.SingleMessage) { continueWaiting = false; @@ -274,12 +295,14 @@ public override int Execute(CommandContext context, Settings settings) messageListener.Start(); + const UInt32 minMessagesToLagBeforeSkip = 10; + // Console display background thread ----------------------------------------------------- var messageConsoleDisplay = new Thread(() => { while (continueWaiting) { - m_displayMessageThreadWakeup.WaitOne(5000); + if (m_displayMessageQueue.Count == 0) m_displayMessageThreadWakeup.WaitOne(5000); if (m_displayMessageQueue.Count > 0) { @@ -290,6 +313,7 @@ public override int Execute(CommandContext context, Settings settings) message = m_displayMessageQueue.Dequeue(); } + if (startTimestamp == 0) { // gets timestamp of first message we receive and uses that so all others are an offset @@ -314,7 +338,6 @@ public override int Execute(CommandContext context, Settings settings) // set our last received so we can calculate offsets lastReceivedTimestamp = message.ReceivedTimestamp; - displayTable.OutputRow(message, offsetMicroseconds); } @@ -324,6 +347,59 @@ public override int Execute(CommandContext context, Settings settings) messageConsoleDisplay.Start(); + var messageDispatcherThread = new Thread(() => + { + while (continueWaiting) + { + if (m_receivedMessagesQueue.Count == 0) m_messageDispatcherThreadWakeup.WaitOne(5000); + + if (continueWaiting && m_receivedMessagesQueue.Count > 0) + { + // we load up the various queues here + + ReceivedMidiMessage message; + + // pull from the incoming messages queue + lock (m_receivedMessagesQueue) + { + message = m_receivedMessagesQueue.Dequeue(); + } + + + //if (settings.SkipToKeepUp && m_displayMessageQueue.Count >= minMessagesToLagBeforeSkip) + //{ + // // display queue is backed up. Skip displaying if user has specified that. + + // lock (m_displayMessageQueue) + // { + // m_displayMessageQueue.Clear(); + // } + //} + + // add to the display queue + lock (m_displayMessageQueue) + { + m_displayMessageQueue.Enqueue(message); + } + m_displayMessageThreadWakeup.Set(); + + + // add to the file writer queue if we're capturing to file + if (captureWriter != null) + { + lock (m_fileWriterMessagesQueue) + { + m_fileWriterMessagesQueue.Enqueue(message); + } + m_fileMessageThreadWakeup.Set(); + } + } + } + + }); + messageDispatcherThread.Start(); + + // File writing background thread ----------------------------------------------------- if (captureWriter != null) { @@ -331,7 +407,7 @@ public override int Execute(CommandContext context, Settings settings) { while (continueWaiting) { - m_fileMessageThreadWakeup.WaitOne(5000); + if (m_fileWriterMessagesQueue.Count == 0) m_fileMessageThreadWakeup.WaitOne(5000); if (m_fileWriterMessagesQueue.Count > 0) { @@ -355,7 +431,6 @@ public override int Execute(CommandContext context, Settings settings) } WriteMessageToFile(settings, captureWriter, message); - } //Thread.Sleep(0); @@ -365,7 +440,7 @@ public override int Execute(CommandContext context, Settings settings) } - // Main UI loop ----------------------------------------------------- + // Main UI loop and moving messages to output queues ----------------------------------------- while (continueWaiting) { if (Console.KeyAvailable) @@ -380,6 +455,7 @@ public override int Execute(CommandContext context, Settings settings) m_terminateMessageListenerThread.Set(); m_fileMessageThreadWakeup.Set(); m_displayMessageThreadWakeup.Set(); + m_messageDispatcherThreadWakeup.Set(); AnsiConsole.WriteLine(); AnsiConsole.MarkupLine("🛑 " + Strings.MonitorEscapePressedMessage); @@ -393,41 +469,12 @@ public override int Execute(CommandContext context, Settings settings) m_terminateMessageListenerThread.Set(); m_displayMessageThreadWakeup.Set(); m_fileMessageThreadWakeup.Set(); + m_messageDispatcherThreadWakeup.Set(); AnsiConsole.MarkupLine("❎ " + AnsiMarkupFormatter.FormatError(Strings.EndpointDisconnected)); } - else if (continueWaiting && m_receivedMessagesQueue.Count > 0) - { - // we load up the various queues here - - ReceivedMidiMessage message; - - // pull from the incoming messages queue - lock (m_receivedMessagesQueue) - { - message = m_receivedMessagesQueue.Dequeue(); - } - - - // add to the display queue - lock (m_displayMessageQueue) - { - m_displayMessageQueue.Enqueue(message); - } - m_displayMessageThreadWakeup.Set(); - - // add to the file writer queue if we're capturing to file - if (captureWriter != null) - { - lock (m_fileWriterMessagesQueue) - { - m_fileWriterMessagesQueue.Enqueue(message); - } - m_fileMessageThreadWakeup.Set(); - } - } - if (continueWaiting) Thread.Sleep(10); + if (continueWaiting) Thread.Sleep(100); } } else @@ -444,7 +491,55 @@ public override int Execute(CommandContext context, Settings settings) session.DisconnectEndpointConnection(connection.ConnectionId); } - // Summary information + // Summary information --------------------------------------------------------------------------------- + + if (lastMessageReceivedEventTimestamp >= firstMessageReceivedEventTimestamp) + { + string message = "➡️ "; + + if (countMessagesReceived == 0) + { + message += "[red]No[/] messages received"; + } + else if (countMessagesReceived == 1) + { + message += "[green]One[/] message received"; + } + else + { + message += $"[steelblue1]{countMessagesReceived.ToString("N0")}[/] messages received"; + } + + // calculate total receive time, not total display time + + UInt64 totalTicks = lastMessageReceivedEventTimestamp - firstMessageReceivedEventTimestamp; + double totalSeconds = MidiClock.ConvertTimestampToMilliseconds(totalTicks) / 1000; + + message += $" and processed over [steelblue1]{totalSeconds.ToString("N2")}[/] seconds, "; + + UInt64 averageTicksPerMessage = totalTicks / countMessagesReceived; + double averageMicroseconds = MidiClock.ConvertTimestampToMicroseconds(averageTicksPerMessage); + double averageMilliseconds = MidiClock.ConvertTimestampToMilliseconds(averageTicksPerMessage); + + if (averageMicroseconds > 1000000) + { + // display in seconds + double averageSeconds = averageMicroseconds / 1000000; + message += $"[steelblue1]{averageSeconds.ToString("N4")} s[/] per message."; + } + else if (averageMilliseconds > 1) + { + // display in milliseconds + message += $"[steelblue1]{averageMilliseconds.ToString("N2")} ms[/] per message."; + } + else + { + // display in microseconds + message += $"[steelblue1]{averageMilliseconds.ToString("N2")} μs[/] per message."; + } + + AnsiConsole.MarkupLine(message); + } if (outOfOrderMessageCount > 0) { @@ -468,6 +563,8 @@ public override int Execute(CommandContext context, Settings settings) AnsiConsole.MarkupLine("✅ No messages received out of expected timestamp order."); } + // AnsiConsole.MarkupLine($"{MidiClock.ConvertTimestampToMilliseconds(displayTable.TotalRenderingTicks).ToString("N4")} milliseconds spent just on output rendering."); + if (captureWriter != null) { AnsiConsole.MarkupLine("✅ Messages written to " + AnsiMarkupFormatter.FormatFileName(fileName)); @@ -476,7 +573,7 @@ public override int Execute(CommandContext context, Settings settings) } - return 0; + return (int)MidiConsoleReturnCode.Success; } diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessageCommand.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessageCommand.cs index 5e2828778..547e55662 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessageCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessageCommand.cs @@ -139,6 +139,17 @@ public override int Execute(CommandContext context, Settings settings) } + + + // todo: we need to break messaging sending out to a bg thread so + // we can make it as fast as possible + var messageSenderThread = new Thread(() => + { + }); + + + + UInt64 maxTimestampScheduled = 0; AnsiConsole.Progress() @@ -196,7 +207,14 @@ public override int Execute(CommandContext context, Settings settings) messageFailures ++; } - Thread.Sleep(settings.DelayBetweenMessages); + if (settings.DelayBetweenMessages > 0) + { + Thread.Sleep(settings.DelayBetweenMessages); + } + else + { + // we just tight loop + } } if (messageFailures > 0) diff --git a/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs b/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs index 333f29164..a7256e582 100644 --- a/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs +++ b/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs @@ -37,6 +37,10 @@ internal class MidiMessageTable private string _separatorLine = string.Empty; private string _messageFormat = string.Empty; + private UInt64 _totalRenderingTicks = 0; + + public UInt64 TotalRenderingTicks { get { return _totalRenderingTicks; } } + public List Columns { get; private set; } = new List(); private void BuildStringFormats() @@ -141,6 +145,9 @@ public void OutputHeader() public void OutputRow(ReceivedMidiMessage message, double deltaMicrosecondsFromPreviousMessage) { + //Console.WriteLine(message.Index); + //return; + string data = string.Empty; string detailedMessageType = string.Empty; @@ -262,6 +269,7 @@ public void OutputRow(ReceivedMidiMessage message, double deltaMicrosecondsFromP Columns[m_offsetColumnNumber].DataColor = m_offsetValueDefaultColor; } + UInt64 outputStart = MidiClock.Now; AnsiConsole.MarkupLine( _messageFormat, @@ -278,6 +286,10 @@ public void OutputRow(ReceivedMidiMessage message, double deltaMicrosecondsFromP detailedMessageType ); + UInt64 outputEnd = MidiClock.Now; + + _totalRenderingTicks += outputEnd - outputStart; + } } diff --git a/src/user-tools/midi-console/Midi/Resources/Strings.Designer.cs b/src/user-tools/midi-console/Midi/Resources/Strings.Designer.cs index d8b8563f7..6760f9581 100644 --- a/src/user-tools/midi-console/Midi/Resources/Strings.Designer.cs +++ b/src/user-tools/midi-console/Midi/Resources/Strings.Designer.cs @@ -681,6 +681,15 @@ internal static string ParameterMonitorEndpointSingleMessage { } } + /// + /// Looks up a localized string similar to If you are monitoring very fast input from a device, the display can start to lag behind considerably. In that case, set this to true and messages will be skipped (only on the display output) to try to keep the display output roughly in time with the messages received.. + /// + internal static string ParameterMonitorEndpointSkipToKeepUp { + get { + return ResourceManager.GetString("ParameterMonitorEndpointSkipToKeepUp", resourceCulture); + } + } + /// /// Looks up a localized string similar to Provide additional columns of information for each message. /// diff --git a/src/user-tools/midi-console/Midi/Resources/Strings.resx b/src/user-tools/midi-console/Midi/Resources/Strings.resx index 310047bd1..53650da8e 100644 --- a/src/user-tools/midi-console/Midi/Resources/Strings.resx +++ b/src/user-tools/midi-console/Midi/Resources/Strings.resx @@ -324,6 +324,9 @@ Wait for a single incoming message only + + If you are monitoring very fast input from a device, the display can start to lag behind considerably. In that case, set this to true and messages will be skipped (only on the display output) to try to keep the display output roughly in time with the messages received. + Provide additional columns of information for each message From cbfaf4fcc7156e7c9e9de3341954022ba13265ec Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Tue, 16 Jan 2024 14:24:08 -0500 Subject: [PATCH 3/3] Fix setup package DLL ref count --- src/oob-setup/api-package/WindowsMidiServices.wxs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/oob-setup/api-package/WindowsMidiServices.wxs b/src/oob-setup/api-package/WindowsMidiServices.wxs index 2b75781cb..2a5ea651e 100644 --- a/src/oob-setup/api-package/WindowsMidiServices.wxs +++ b/src/oob-setup/api-package/WindowsMidiServices.wxs @@ -44,7 +44,6 @@ @@ -66,7 +65,6 @@