diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi
index c89377f3..8bdde358 100644
--- a/build/staging/version/BundleInfo.wxi
+++ b/build/staging/version/BundleInfo.wxi
@@ -1,4 +1,4 @@
-
+
diff --git a/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp b/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp
index c5bce63b..56334c3c 100644
--- a/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp
+++ b/src/api/Abstraction/MidiSrvAbstraction/Midi2.MidiSrv.cpp
@@ -21,7 +21,7 @@ CMidi2MidiSrv::Initialize(
TraceLoggingPointer(this, "this")
);
- MIDISRV_CLIENTCREATION_PARAMS creationParams{ 0 };
+ MIDISRV_CLIENTCREATION_PARAMS creationParams{ };
PMIDISRV_CLIENT client{ nullptr };
wil::unique_rpc_binding bindingHandle;
@@ -48,8 +48,13 @@ CMidi2MidiSrv::Initialize(
creationParams.Flow = Flow;
creationParams.DataFormat = CreationParams->DataFormat;
+
// Todo: client side buffering requests to come from some service setting?
+ // - See https://github.com/microsoft/MIDI/issues/219 for details
+
creationParams.BufferSize = PAGE_SIZE;
+ //creationParams.BufferSize = 512; // Set this for debugging see https://github.com/microsoft/MIDI/issues/182 for all the drama :)
+
RETURN_IF_FAILED(GetMidiSrvBindingHandle(&bindingHandle));
diff --git a/src/api/Libs/MidiKs/MidiKs.cpp b/src/api/Libs/MidiKs/MidiKs.cpp
index ff39318d..d1f2932f 100644
--- a/src/api/Libs/MidiKs/MidiKs.cpp
+++ b/src/api/Libs/MidiKs/MidiKs.cpp
@@ -156,6 +156,7 @@ KSMidiDevice::PinSetState(
return S_OK;
}
+_Use_decl_annotations_
HRESULT
KSMidiDevice::ConfigureLoopedBuffer(ULONG& BufferSize
)
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 02e44936..3e14efba 100644
--- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs
+++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointMonitorCommand.cs
@@ -35,6 +35,13 @@ 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);
@@ -129,7 +136,7 @@ private void MonitorEndpointConnectionStatusInTheBackground(string endpointId)
while (_continueWatchingDevice)
{
- Thread.Sleep(125);
+ Thread.Sleep(100);
}
});
@@ -137,6 +144,11 @@ private void MonitorEndpointConnectionStatusInTheBackground(string endpointId)
deviceWatcherThread.Start();
}
+
+
+
+
+
public override int Execute(CommandContext context, Settings settings)
{
MidiMessageTable displayTable = new MidiMessageTable(settings.Verbose);
@@ -203,146 +215,226 @@ public override int Execute(CommandContext context, Settings settings)
UInt64 startTimestamp = 0;
UInt64 lastReceivedTimestamp = 0;
-
- //MidiMessageStruct msg;
-
UInt32 index = 0;
+
MonitorEndpointConnectionStatusInTheBackground(endpointId);
bool continueWaiting = true;
+ UInt64 outOfOrderMessageCount = 0;
- var messageListener = new Thread(() =>
+
+ // open the connection
+ if (connection.Open())
{
- connection.MessageReceived += (s, e) =>
- {
- // helps prevent any race conditions with main loop and its output
- if (!continueWaiting) return;
+ // Main message listener background thread -----------------------------------------------------
- //Console.WriteLine("DEBUG: MessageReceived");
- index++;
+ var messageListener = new Thread(() =>
+ {
+ UInt64 lastMessageTimestamp = 0;
- var receivedMessage = new ReceivedMidiMessage()
+ connection.MessageReceived += (s, e) =>
{
- Index = index,
- ReceivedTimestamp = MidiClock.Now,
- MessageTimestamp = e.Timestamp
- };
+ // helps prevent any race conditions with main loop and its output
+ if (!continueWaiting) return;
- receivedMessage.NumWords = e.FillWords(out receivedMessage.Word0, out receivedMessage.Word1, out receivedMessage.Word2, out receivedMessage.Word3);
+ //Console.WriteLine("DEBUG: MessageReceived");
+ index++;
- m_receivedMessagesQueue.Enqueue(receivedMessage);
+ var receivedMessage = new ReceivedMidiMessage()
+ {
+ Index = index,
+ ReceivedTimestamp = MidiClock.Now,
+ MessageTimestamp = e.Timestamp
+ };
- if (settings.SingleMessage)
- {
- continueWaiting = false;
- }
- };
+ if (e.Timestamp < lastMessageTimestamp)
+ {
+ outOfOrderMessageCount++;
+ }
- while (continueWaiting && !_hasEndpointDisconnected)
- {
- Thread.Sleep(0);
- }
+ lastMessageTimestamp = e.Timestamp;
- });
- messageListener.Start();
+ receivedMessage.NumWords = e.FillWords(out receivedMessage.Word0, out receivedMessage.Word1, out receivedMessage.Word2, out receivedMessage.Word3);
+ lock (m_receivedMessagesQueue)
+ {
+ m_receivedMessagesQueue.Enqueue(receivedMessage);
+ }
- // open the connection
- if (connection.Open())
- {
- while (continueWaiting)
+ if (settings.SingleMessage)
+ {
+ continueWaiting = false;
+ }
+ };
+
+ m_terminateMessageListenerThread.WaitOne();
+ });
+ messageListener.Start();
+
+
+ // Console display background thread -----------------------------------------------------
+ var messageConsoleDisplay = new Thread(() =>
{
- if (Console.KeyAvailable)
+ while (continueWaiting)
{
- var keyInfo = Console.ReadKey(true);
+ m_displayMessageThreadWakeup.WaitOne(5000);
- if (keyInfo.Key == ConsoleKey.Escape)
+ if (m_displayMessageQueue.Count > 0)
{
- continueWaiting = false;
+ ReceivedMidiMessage message;
- AnsiConsole.WriteLine();
- AnsiConsole.MarkupLine(Strings.MonitorEscapePressedMessage);
+ lock (m_displayMessageQueue)
+ {
+ message = m_displayMessageQueue.Dequeue();
+ }
+
+ if (startTimestamp == 0)
+ {
+ // gets timestamp of first message we receive and uses that so all others are an offset
+ startTimestamp = message.ReceivedTimestamp;
+ }
+
+ if (lastReceivedTimestamp == 0)
+ {
+ // gets timestamp of first message we receive and uses that so all others are an offset from previous message
+ lastReceivedTimestamp = message.ReceivedTimestamp;
+ }
+
+ if (message.Index == 1)
+ {
+ displayTable.OutputHeader();
+ }
+
+ // calculate offset from the last message received
+ var offsetMicroseconds = MidiClock.ConvertTimestampToMicroseconds(
+ message.ReceivedTimestamp - lastReceivedTimestamp);
+
+ // set our last received so we can calculate offsets
+ lastReceivedTimestamp = message.ReceivedTimestamp;
+
+
+ displayTable.OutputRow(message, offsetMicroseconds);
}
-
+
+ //Thread.Sleep(0);
}
+ });
+ messageConsoleDisplay.Start();
- if (_hasEndpointDisconnected)
- {
- continueWaiting = false;
- AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError(Strings.EndpointDisconnected));
- }
- else if (continueWaiting && m_receivedMessagesQueue.Count > 0)
+ // File writing background thread -----------------------------------------------------
+ if (captureWriter != null)
+ {
+ var messageFileWriter = new Thread(() =>
{
- lock (m_receivedMessagesQueue)
+ while (continueWaiting)
{
- int iter = 0;
+ m_fileMessageThreadWakeup.WaitOne(5000);
- while (m_receivedMessagesQueue.Count > 0 && iter < 50)
+ if (m_fileWriterMessagesQueue.Count > 0)
{
- iter++; // we need to take a break if we're being spammed, so this tracks iterations
-
- var message = m_receivedMessagesQueue.Dequeue();
+ ReceivedMidiMessage message;
- if (startTimestamp == 0)
+ lock (m_fileWriterMessagesQueue)
{
- // gets timestamp of first message we receive and uses that so all others are an offset
- startTimestamp = message.ReceivedTimestamp;
+ message = m_fileWriterMessagesQueue.Dequeue();
}
- if (lastReceivedTimestamp == 0)
- {
- // gets timestamp of first message we receive and uses that so all others are an offset from previous message
- lastReceivedTimestamp = message.ReceivedTimestamp;
- }
- // TODO: Maybe re-display the header if it's been a while since last header, and it is off-screen (index delta > some number)
+ // write header if first message
if (message.Index == 1)
{
- displayTable.OutputHeader();
+ captureWriter.WriteLine("#");
+ captureWriter.WriteLine($"# Windows MIDI Services Console Capture {DateTime.Now.ToLongDateString()}");
+ captureWriter.WriteLine($"# Endpoint: {endpointId.ToString()}");
+ captureWriter.WriteLine($"# Annotation: {settings.AnnotateCaptureFile.ToString()}");
+ captureWriter.WriteLine($"# Delimiter: {settings.FieldDelimiter.ToString()}");
+ captureWriter.WriteLine("#");
+ }
+ WriteMessageToFile(settings, captureWriter, message);
- if (captureWriter != null && capturingToFile)
- {
- captureWriter.WriteLine("#");
- captureWriter.WriteLine($"# Windows MIDI Services Console Capture {DateTime.Now.ToLongDateString()}");
- captureWriter.WriteLine($"# Endpoint: {endpointId.ToString()}");
- captureWriter.WriteLine($"# Annotation: {settings.AnnotateCaptureFile.ToString()}");
- captureWriter.WriteLine($"# Delimiter: {settings.FieldDelimiter.ToString()}");
- captureWriter.WriteLine("#");
- }
+ }
- }
+ //Thread.Sleep(0);
+ }
+ });
+ messageFileWriter.Start();
+ }
- // calculate offset from the last message received
- var offsetMicroseconds = MidiClock.ConvertTimestampToMicroseconds(message.ReceivedTimestamp - lastReceivedTimestamp);
- displayTable.OutputRow(message, offsetMicroseconds);
+ // Main UI loop -----------------------------------------------------
+ while (continueWaiting)
+ {
+ if (Console.KeyAvailable)
+ {
+ var keyInfo = Console.ReadKey(true);
- if (captureWriter != null)
- {
- WriteMessageToFile(settings, captureWriter, message);
- }
+ if (keyInfo.Key == ConsoleKey.Escape)
+ {
+ continueWaiting = false;
+ // wake up the threads so they terminate
+ m_terminateMessageListenerThread.Set();
+ m_fileMessageThreadWakeup.Set();
+ m_displayMessageThreadWakeup.Set();
- // set our last received so we can calculate offsets
- lastReceivedTimestamp = message.ReceivedTimestamp;
+ AnsiConsole.WriteLine();
+ AnsiConsole.MarkupLine("🛑 " + Strings.MonitorEscapePressedMessage);
+ }
+
+ }
+ if (_hasEndpointDisconnected)
+ {
+ continueWaiting = false;
+ m_terminateMessageListenerThread.Set();
+ m_displayMessageThreadWakeup.Set();
+ m_fileMessageThreadWakeup.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();
}
}
-
- // we don't need to update the display more often. A tight loop really ties up the CPU.
- // actual message receive timings are based on the worker thread
- if (continueWaiting) Thread.Sleep(125);
+
+ if (continueWaiting) Thread.Sleep(10);
}
}
else
{
AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError(Strings.ErrorUnableToOpenEndpoint));
return (int)MidiConsoleReturnCode.ErrorOpeningEndpointConnection;
+
}
_continueWatchingDevice = false;
@@ -352,9 +444,33 @@ public override int Execute(CommandContext context, Settings settings)
session.DisconnectEndpointConnection(connection.ConnectionId);
}
+ // Summary information
+
+ if (outOfOrderMessageCount > 0)
+ {
+ string message = "❎ " + outOfOrderMessageCount.ToString();
+
+ if (outOfOrderMessageCount > 1)
+ {
+ // multiple messages out of order
+ message += " messages received out of sent timestamp order.";
+ }
+ else
+ {
+ // single message out of order
+ message += " message received out of sent timestamp order.";
+ }
+
+ AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError(message));
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("✅ No messages received out of expected timestamp order.");
+ }
+
if (captureWriter != null)
{
- AnsiConsole.MarkupLine("Messages written to " + AnsiMarkupFormatter.FormatFileName(fileName));
+ AnsiConsole.MarkupLine("✅ Messages written to " + AnsiMarkupFormatter.FormatFileName(fileName));
captureWriter.Close();
}
diff --git a/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs b/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs
index 65a820cd..333f2916 100644
--- a/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs
+++ b/src/user-tools/midi-console/Midi/CustomTable/MidiMessageTable.cs
@@ -2,13 +2,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Midi2;
namespace Microsoft.Devices.Midi2.ConsoleApp
{
- internal struct MidiMessageTableColumn
+ internal class MidiMessageTableColumn
{
public int ParameterIndex { get; set; }
public string HeaderText { get; set; }
@@ -98,16 +99,28 @@ private void BuildStringFormats()
const int timestampOffsetValueColumnWidth = 9;
+ private bool m_verbose = false;
+ private int m_offsetColumnNumber = 2;
+ private int m_deltaColumnNumber = 5;
+
+ private string m_offsetValueDefaultColor = "darkseagreen";
+ private string m_offsetValueErrorColor = "red";
+
+ private string m_deltaValueDefaultColor = "lightskyblue3_1";
+ private string m_deltaValueErrorColor = "red";
+
public MidiMessageTable(bool verbose)
{
+ m_verbose = verbose;
+
// todo: localize strings
Columns.Add(new MidiMessageTableColumn(0, "Index", 8, true, "grey", ""));
Columns.Add(new MidiMessageTableColumn(1, "Message Timestamp", 19, false, "darkseagreen2", "N0")); // timestamp
- Columns.Add(new MidiMessageTableColumn(2, "From Last", timestampOffsetValueColumnWidth, false, "darkseagreen", "")); // offset
+ Columns.Add(new MidiMessageTableColumn(m_offsetColumnNumber, "From Last", timestampOffsetValueColumnWidth, false, m_offsetValueDefaultColor, "")); // offset
Columns.Add(new MidiMessageTableColumn(3, "", -2, true, "grey", "")); // offset label
if (verbose) Columns.Add(new MidiMessageTableColumn(4, "Received Timestamp", 19, false, "skyblue2", "N0")); // recv timestamp
- if (verbose) Columns.Add(new MidiMessageTableColumn(5, "Receive \u0394", timestampOffsetValueColumnWidth, false, "lightskyblue3_1", "")); // delta
+ if (verbose) Columns.Add(new MidiMessageTableColumn(m_deltaColumnNumber, "Receive \u0394", timestampOffsetValueColumnWidth, false, "lightskyblue3_1", "")); // delta
if (verbose) Columns.Add(new MidiMessageTableColumn(6, "", -2, true, "grey", "")); // delta label
Columns.Add(new MidiMessageTableColumn(7, "Words", 8, false, "deepskyblue1", ""));
Columns.Add(new MidiMessageTableColumn(8, "", 8, true, "deepskyblue2", ""));
@@ -115,7 +128,7 @@ public MidiMessageTable(bool verbose)
Columns.Add(new MidiMessageTableColumn(10, "", 8, true, "deepskyblue4", ""));
if (verbose) Columns.Add(new MidiMessageTableColumn(11, "Gr", 2, false, "indianred", ""));
if (verbose) Columns.Add(new MidiMessageTableColumn(12, "Ch", 2, true, "mediumorchid3", ""));
- Columns.Add(new MidiMessageTableColumn(13, "Message Type", -35, false,"steelblue1_1", ""));
+ if (verbose) Columns.Add(new MidiMessageTableColumn(13, "Message Type", -35, false,"steelblue1_1", ""));
BuildStringFormats();
}
@@ -130,7 +143,9 @@ public void OutputRow(ReceivedMidiMessage message, double deltaMicrosecondsFromP
{
string data = string.Empty;
- string detailedMessageType = MidiMessageUtility.GetMessageFriendlyNameFromFirstWord(message.Word0);
+ string detailedMessageType = string.Empty;
+
+ if (m_verbose) detailedMessageType = MidiMessageUtility.GetMessageFriendlyNameFromFirstWord(message.Word0);
string word0 = string.Empty;
string word1 = string.Empty;
@@ -165,36 +180,46 @@ public void OutputRow(ReceivedMidiMessage message, double deltaMicrosecondsFromP
double deltaValue = 0;
string deltaUnitLabel = string.Empty;
- double deltaSecheduledTimestampMicroseconds = 0.0;
-
- if (message.ReceivedTimestamp >= message.MessageTimestamp)
- {
- deltaSecheduledTimestampMicroseconds = MidiClock.ConvertTimestampToMicroseconds(message.ReceivedTimestamp - message.MessageTimestamp);
- }
- else
+ if (m_verbose)
{
- // we received the message early, so the offset is negative
- deltaSecheduledTimestampMicroseconds = -1 * MidiClock.ConvertTimestampToMicroseconds(message.MessageTimestamp - message.ReceivedTimestamp);
- }
+ double deltaSecheduledTimestampMicroseconds = 0.0;
+ if (message.ReceivedTimestamp >= message.MessageTimestamp)
+ {
+ deltaSecheduledTimestampMicroseconds = MidiClock.ConvertTimestampToMicroseconds(message.ReceivedTimestamp - message.MessageTimestamp);
+ }
+ else
+ {
+ // we received the message early, so the offset is negative
+ deltaSecheduledTimestampMicroseconds = -1 * MidiClock.ConvertTimestampToMicroseconds(message.MessageTimestamp - message.ReceivedTimestamp);
+ }
- AnsiConsoleOutput.ConvertToFriendlyTimeUnit(deltaSecheduledTimestampMicroseconds, out deltaValue, out deltaUnitLabel);
+ AnsiConsoleOutput.ConvertToFriendlyTimeUnit(deltaSecheduledTimestampMicroseconds, out deltaValue, out deltaUnitLabel);
+ }
string groupText = string.Empty;
- var messageType = MidiMessageUtility.GetMessageTypeFromMessageFirstWord(message.Word0);
-
- if (MidiMessageUtility.MessageTypeHasGroupField(messageType))
+ if (m_verbose)
{
- groupText = MidiMessageUtility.GetGroupFromMessageFirstWord(message.Word0).NumberForDisplay.ToString().PadLeft(2);
+ var messageType = MidiMessageUtility.GetMessageTypeFromMessageFirstWord(message.Word0);
+
+ if (MidiMessageUtility.MessageTypeHasGroupField(messageType))
+ {
+ groupText = MidiMessageUtility.GetGroupFromMessageFirstWord(message.Word0).NumberForDisplay.ToString().PadLeft(2);
+ }
}
string channelText = string.Empty;
- if (MidiMessageUtility.MessageTypeHasChannelField(messageType))
+ if (m_verbose)
{
- channelText = MidiMessageUtility.GetChannelFromMessageFirstWord(message.Word0).NumberForDisplay.ToString().PadLeft(2);
+ var messageType = MidiMessageUtility.GetMessageTypeFromMessageFirstWord(message.Word0);
+
+ if (MidiMessageUtility.MessageTypeHasChannelField(messageType))
+ {
+ channelText = MidiMessageUtility.GetChannelFromMessageFirstWord(message.Word0).NumberForDisplay.ToString().PadLeft(2);
+ }
}
// some cleanup
@@ -218,11 +243,23 @@ public void OutputRow(ReceivedMidiMessage message, double deltaMicrosecondsFromP
if (deltaValueText.Length > timestampOffsetValueColumnWidth)
{
deltaValueText = "######";
+ deltaUnitLabel = "";
+ Columns[m_deltaColumnNumber].DataColor = m_deltaValueErrorColor;
+ }
+ else
+ {
+ Columns[m_deltaColumnNumber].DataColor = m_deltaValueDefaultColor;
}
if (offsetValueText.Length > timestampOffsetValueColumnWidth)
{
offsetValueText = "######";
+ offsetUnitLabel = "";
+ Columns[m_offsetColumnNumber].DataColor = m_offsetValueErrorColor;
+ }
+ else
+ {
+ Columns[m_offsetColumnNumber].DataColor = m_offsetValueDefaultColor;
}