From 73e9e8f1d3333b845250d88fce8ed697af48f4d3 Mon Sep 17 00:00:00 2001 From: Pete Brown Date: Sun, 21 Apr 2024 14:25:36 -0400 Subject: [PATCH] Doc and sample updates --- docs/api/README.md | 8 +- docs/api/service/MidiService.md | 61 ++++----- docs/api/service/README.md | 3 +- docs/api/simple-types/MidiChannel.md | 26 ++-- docs/api/simple-types/MidiGroup.md | 28 ++--- docs/api/simple-types/MidiUniqueId.md | 43 ++++--- docs/api/simple-types/README.md | 1 - .../api-client-basics.vcxproj | 8 +- samples/cpp-winrt/api-client-basics/main.cpp | 15 ++- .../api-client-basics/packages.config | 2 +- samples/cpp-winrt/api-client-basics/pch.h | 2 +- .../api-client-basics-cs/Program.cs | 118 ++++++++++-------- .../api-client-basics-cs.csproj | 2 +- src/api/Client/Midi2Client/MidiService.cpp | 26 ++-- src/api/Client/Midi2Client/MidiSession.cpp | 7 ++ src/api/Client/Midi2Client/pch.h | 1 + src/api/Client/Midi2Client/trace_logging.cpp | 2 +- 17 files changed, 187 insertions(+), 166 deletions(-) diff --git a/docs/api/README.md b/docs/api/README.md index e79cef901..2d8bfdf94 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -10,6 +10,7 @@ The Windows.Devices.Midi2 types are documented in these pages. Typical API workflow: +1. **Check for Windows MIDI Services availability**. Windows MIDI Services is not available on older versions of Windows, or on devices like Xbox, Hololens, and others. The first step is to make a call to check for availability of the services on Windows. If Windows MIDI Services is not available, you may want to fall back to an older MIDI API, or simply inform the user and terminate the application. 1. **Create a new session**, with an appropriate name. The name will be visible to users and so should be meaningful. Each application may open more than one session at a time (for example, different songs in a DAW, or different tabs in a browser). A single session manages the lifetime of the connections opened through it. 2. **Connect to an endpoint**. Typically, you'll get the endpoint's id through the enumeration functions. 3. **Wire up a MidiMessageReceived event handler**. This is how you will receive incoming messages from the endpoint. Messages are received individually, with one event per message. @@ -20,6 +21,10 @@ Typical API workflow: Enumeration is how you discover endpoints and get notified of endpoints when they are added, updated, or removed. For the best user experience, keep a `MidiEndpointDeviceWatcher` running in a background thread so you can monitor device removal, and property updates (name, function blocks, etc.) +## Service + +The MidiService class is a utility class which provides access to health and status information related to the MidiSrv Service. This is also where you can check to see if Windows MIDI Services is available on this PC. + ## Session Interaction with a MIDI Endpoint always starts with creating a session. @@ -52,6 +57,3 @@ A virtual device is the mechanism through which app-to-app MIDI works through th There are several simple or basic types used in Windows MIDI Services. These types provide formatting and validation to help ensure applications display data in similar ways. -## Service - -The MidiService class is a utility class which provides access to health and status information related to the MidiSrv Service. diff --git a/docs/api/service/MidiService.md b/docs/api/service/MidiService.md index 55ba06922..c5694ae4c 100644 --- a/docs/api/service/MidiService.md +++ b/docs/api/service/MidiService.md @@ -8,57 +8,50 @@ has_children: false # MidiService +## Remarks + The MidiService class contains a number of static functions which enable working with the service outside of a specific session. -## Static Functions : Reporting +### Service Health -| Static Function | Description | -|---|---| -| `GetInstalledTransportPlugins()` | Returns a list of `MidiServiceTransportPluginInformation` representing all service transport plugins (also called Abstractions) | -| `GetInstalledMessageProcessingPlugins()` | Returns a list of `MidiServiceMessageProcessingPluginInformation` objects representing all service message processing plugins (also called Transforms) | -| `GetActiveSessions()` | Returns a list of `MidiSessionInformation` detailing all active Windows MIDI Services sessions on this PC. | +| `IsAvailable()` | Returns true of Windows MIDI Services is available on this PC. Calling this function is typically the first step in using Windows MIDI Services in your application. | +| `PingService(UInt8)` | Send the specified count of ping messages to the ping endpoint and report on the status and time. Return if the responses are not received in an internally calculated timeout period. | +| `PingService(UInt8, UInt32)` | Send the specified count of ping messages to the ping endpoint and report on the status and time. Return if responses are not received in the specified timeout period (milliseconds). | -## Static Functions : Loopback Endpoints - -| Static Function | Description | -|---|---| -| `CreateTemporaryLoopbackEndpoints(associationId, endpointA, endpointB)` | Create a pair of loopback endpoints which will live until removed through the API or the service is restarted. | -| `RemoveTemporaryLoopbackEndpoints(associationId)` | Remove a pair of temporary loopback endpoints. | +Pinging the Windows service uses the same mechanism as sending any UMP message. The actual message sent is a prioprietary message. (At the time this was created, there was no standard MIDI 2.0 UMP ping message). The message itself is sent to the diagnostics endpoint in the service, which is implemented like any other transport. Therefore, the speed of the pings here and the success of the ping process is a reasonable indicator of service, cross-process queue, and client API health. -Applications creating endpoints for app-to-app MIDI should generally use the Virtual Device support built into the API. However, applications may need to create lightweight loopback endpoints without the protocol negotiation, MIDI 2.0 discovery process, and lifetime management provided by the Virtual Device support. For those scenarios, we have a simple loopback endpoint type. +The diagnostic ping endpoint does not understand any other type of message, and should not be used by applications other than through the ping functions here. -Loopback endpoints created by the user and stored in the configuration file will persist after the service is restarted or the PC rebooted. Loopback endpoints created through this API call are temporary, and will disappear if the service is restarted. In both cases, this feature requires that the loopback endpoint transport is installed and enabled. +The ping does not tell you if a specific transport or device is in a bad state. For example, if a specific USB MIDI device has crashed, this ping message will still work because it is not sent out over USB. -## Static Functions : Runtime Configuration +Here's an example of ping responses through the MIDI console app -| Static Function | Description | -|---|---| -| `UpdateTransportPluginConfiguration(configurationUpdate)` | Sends an update to the service to be used by a transport plugin ("Abstraction") | -| `UpdateProcessingPluginConfiguration(configurationUpdate)` | Sends an update to the service to be used by a message processing plugin ("Transform") | +![MIDI Console Ping](./console-ping.png) -For plugins which support updates at runtime, developers of those plugins should create configuration WinRT types which implement the required configuration interfaces, and create the JSON that is used in the service. In this way, third-party service transport and message processing plugins can be created and configured without changes to the API. +### Reporting -> Note: In version 1 of the API, only transports can be configured at runtime. We're working on enabling configuration of message processing plugins. The API is a no-op. +| `GetInstalledTransportPlugins()` | Returns a list of `MidiServiceTransportPluginInformation` representing all service transport plugins (also called Abstractions) | +| `GetInstalledMessageProcessingPlugins()` | Returns a list of `MidiServiceMessageProcessingPluginInformation` objects representing all service message processing plugins (also called Transforms) | +| `GetActiveSessions()` | Returns a list of `MidiSessionInformation` detailing all active Windows MIDI Services sessions on this PC. | -## Static Functions : Service Health +### Loopback Endpoints -| Static Function | Description | -|---|---| -| `PingService(pingCount)` | Send one or more ping messages to the ping endpoint and report on the status and time. Return if the responses are not received in a calculated timeout period. | -| `PingService(pingCount, timeoutMilliseconds)` | Send one or more ping messages to the ping endpoint and report on the status and time. Return if responses are not received in the specified timeout period. | +| `CreateTemporaryLoopbackEndpoints(Guid, MidiServiceLoopbackEndpointDefinition, MidiServiceLoopbackEndpointDefinition)` | Create a pair of loopback endpoints which will live until removed through the API or the service is restarted. | +| `RemoveTemporaryLoopbackEndpoints(Guid)` | Remove a pair of temporary loopback endpoints when provided their association Id Guid. | -### The ping process +Applications creating endpoints for app-to-app MIDI should generally use the Virtual Device support built into the API. However, applications may need to create lightweight loopback endpoints without the protocol negotiation, MIDI 2.0 discovery process, and lifetime management provided by the Virtual Device support. For those scenarios, we have a simple loopback endpoint type. -Pinging the Windows service uses the same mechanism as sending any UMP message. The actual message sent is a prioprietary message. (At the time this was created, there was no standard MIDI 2.0 UMP ping message). The message itself is sent to the diagnostics endpoint in the service, which is implemented like any other transport. Therefore, the speed of the pings here and the success of the ping process is a reasonable indicator of service, cross-process queue, and client API health. +Loopback endpoints created by the user and stored in the configuration file will persist after the service is restarted or the PC rebooted. Loopback endpoints created through this API call are temporary, and will disappear if the service is restarted. In both cases, this feature requires that the loopback endpoint transport is installed and enabled. -The diagnostic ping endpoint does not understand any other type of message, and should not be used by applications other than through the ping functions here. +### Runtime Configuration -The ping does not tell you if a specific transport or device is in a bad state. For example, if a specific USB MIDI device has crashed, this ping message will still work because it is not sent out over USB. +| `UpdateTransportPluginConfiguration(IMidiServiceTransportPluginConfiguration)` | Sends an update to the service to be used by a transport plugin ("Abstraction") | +| `UpdateProcessingPluginConfiguration(IMidiServiceMessageProcessingPluginConfiguration)` | Sends an update to the service to be used by a message processing plugin ("Transform") | -Here's an example of ping responses through the MIDI console app +For plugins which support updates at runtime, developers of those plugins should create configuration WinRT types which implement the required configuration interfaces, and create the JSON that is used in the service. In this way, third-party service transport and message processing plugins can be created and configured without changes to the API. -![MIDI Console Ping](./console-ping.png) +> Note: In version 1 of the API, only transports can be configured at runtime. We're working on enabling configuration of message processing plugins. The API is a no-op. -## IDL +## See also -[MidiService IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiService.idl) +[MidiService IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiService.idl) \ No newline at end of file diff --git a/docs/api/service/README.md b/docs/api/service/README.md index 9b099aec8..7da2bfba5 100644 --- a/docs/api/service/README.md +++ b/docs/api/service/README.md @@ -7,5 +7,4 @@ has_children: true # Service -The MidiService class is a utility class which provides access to health and status information related to the MidiSrv Service. - +The MidiService class is a utility class which provides access to health and status information related to the MidiSrv Service. This is also where you can check to see if Windows MIDI Services is available on this PC. diff --git a/docs/api/simple-types/MidiChannel.md b/docs/api/simple-types/MidiChannel.md index 5f25fdf35..7bc773f96 100644 --- a/docs/api/simple-types/MidiChannel.md +++ b/docs/api/simple-types/MidiChannel.md @@ -8,33 +8,29 @@ has_children: false # MidiChannel -The MidiChannel class is used to provide formatting and data validation for MIDI 1.0 and MIDI 2.0 channels. +## Remarks + +The `MidiChannel` class is used to provide formatting and data validation for MIDI 1.0 and MIDI 2.0 channels. For clarity, the 0-15 value used in all messages is the `Index` and the 1-16 value those are mapped to for user display, is the `NumberForDisplay`. + +## Constructors + +| `MidiChannel` | Create an empty MidiChannel object (Index 0) | +| `MidiChannel(UInt8)` | Create a MidiChannel with the specified channel Index (0-15) | ## Properties -| Property | Description | -| --------------- | ----------- | | `Index` | The data value, or channel Index (0-15) | | `NumberForDisplay` | The number that should be displayed in any UI. (1-16) | ## Static Properties -| Static Property | Description | -| --------------- | ----------- | | `LabelShort` | Returns the localized abbreviation. For example, "Ch" in English. | | `LabelFull` | Returns the localized full name. For example, "Channel" in English. | -## Functions - -| Function | Description | -| --------------- | ----------- | -| `MidiChannel()` | Constructs an empty `MidiChannel` | -| `MidiChannel(index)` | Constructs a `MidiChannel` with the specified index | +## Static Methods -## Static Functions +| `IsValidChannelIndex(UInt8)` | Verifies that the provided index is valid (between 0 and 15) | -| Static Function | Description | -| --------------- | ----------- | -| `IsValidChannelIndex(index)` | Verifies that the provided index is valid (between 0 and 15) | +## See also [MidiChannel IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiChannel.idl) diff --git a/docs/api/simple-types/MidiGroup.md b/docs/api/simple-types/MidiGroup.md index af231668e..75336070f 100644 --- a/docs/api/simple-types/MidiGroup.md +++ b/docs/api/simple-types/MidiGroup.md @@ -8,33 +8,29 @@ has_children: false # MidiGroup -The `MidiGroup` class is used to provide formatting and data validation for UMP (Universal MIDI Packet) groups. +## Remarks + +The `MidiGroup` class is used to provide formatting and data validation for MIDI 2.0 groups. For clarity, the 0-15 value used in all messages is the `Index` and the 1-16 value those are mapped to for user display, is the `NumberForDisplay`. + +## Constructors + +| `MidiGroup` | Create an empty MidiGroup object (Index 0) | +| `MidiChannel(UInt8)` | Create a MidiChannel with the specified channel Index (0-15) | ## Properties -| Property | Description | -| --------------- | ----------- | -| `Index` | The data value, or group Index (0-15) | +| `Index` | The data value, or channel Index (0-15) | | `NumberForDisplay` | The number that should be displayed in any UI. (1-16) | ## Static Properties -| Static Property | Description | -| --------------- | ----------- | | `LabelShort` | Returns the localized abbreviation. For example, "Gr" in English. | | `LabelFull` | Returns the localized full name. For example, "Group" in English. | -## Functions - -| Function | Description | -| --------------- | ----------- | -| `MidiGroup()` | Constructs an empty `MidiGroup` | -| `MidiGroup(index)` | Constructs a `MidiGroup` with the specified index | +## Static Methods -## Static Functions +| `IsValidGroupIndex(UInt8)` | Verifies that the provided index is valid (between 0 and 15) | -| Static Function | Description | -| --------------- | ----------- | -| `IsValidGroupIndex(index)` | Verifies that the provided index is valid (between 0 and 15) | +## See also [MidiGroup IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiGroup.idl) diff --git a/docs/api/simple-types/MidiUniqueId.md b/docs/api/simple-types/MidiUniqueId.md index bad3a0016..a6fad7610 100644 --- a/docs/api/simple-types/MidiUniqueId.md +++ b/docs/api/simple-types/MidiUniqueId.md @@ -8,43 +8,42 @@ has_children: false # MidiUniqueId -The `MidiUniqueId` class is used to provide formatting and data validation for MIDI-CI MUID types used in Function Blocks and MIDI CI transactions. +## Remarks + +The `MidiUniqueId` class is used to provide formatting and data validation for MIDI-CI MUID (MIDI Unique Id) types used in Function Blocks and MIDI CI transactions. In the specification, Byte1 is the LSB and Byte4 is the MSB. We follow that convention here. +## Constructors + +| `MidiUniqueId()` | Constructs an empty `MidiUniqueId` | +| `MidiUniqueId(UInt32)` | Constructs the `MidiUniqueId` from the given 28 bit integer | +| `MidiUniqueId(UInt8, UInt8, UInt8, UInt8)` | Constructs a `MidiUniqueId` with the specified seven-bit bytes | + ## Properties -| Property | Description | -| --------------- | ----------- | -| `Byte1` | The data value for byte 1 of the MUID | -| `Byte2` | The data value for byte 2 of the MUID | -| `Byte3` | The data value for byte 3 of the MUID | -| `Byte4` | The data value for byte 4 of the MUID | +| `Byte1` | The data value for byte 1 of the MUID (LSB) | +| `Byte2` | The data value for byte 2 of the MUID (second-most LSB) | +| `Byte3` | The data value for byte 3 of the MUID (second-most MSB) | +| `Byte4` | The data value for byte 4 of the MUID (MSB) | | `As28BitInteger` | The data value converted to a 28 bit integer | -| `IsBroadcast` | True if this is the broadcast MUID value | -| `IsReserved` | True if this is the reserved MUID value | +| `IsBroadcast` | True if this is the broadcast MUID value from the MIDI CI specification | +| `IsReserved` | True if this is the reserved MUID value from the MIDI CI specification | ## Static Properties | Static Property | Description | | --------------- | ----------- | -| `LabelShort` | Returns the localized abbreviation. | -| `LabelFull` | Returns the localized full name. | - -## Functions - -| Function | Description | -| --------------- | ----------- | -| `MidiUniqueId()` | Constructs an empty `MidiUniqueId` | -| `MidiUniqueId(integer28bit)` | Constructs the `MidiUniqueId` from the given 28 bit integer | -| `MidiUniqueId(byte1, byte2, byte3, byte4)` | Constructs a `MidiUniqueId` with the specified bytes | +| `LabelShort` | Returns the localized abbreviation for use in UI. | +| `LabelFull` | Returns the localized full name for use in UI. | -## Static Functions +## Static Methods | Function | Description | | --------------- | ----------- | -| `CreateBroadcast()` | Constructs a broadcast `MidiUniqueId` | -| `CreateRandom()` | Constructs a random `MidiUniqueId` | +| `CreateBroadcast()` | Constructs a broadcast `MidiUniqueId` per the MIDI CI specification | +| `CreateRandom()` | Constructs a random `MidiUniqueId` per the MIDI CI specification | +## See also [MidiUniqueId IDL](https://github.com/microsoft/MIDI/blob/main/src/api/Client/Midi2Client/MidiUniqueId.idl) diff --git a/docs/api/simple-types/README.md b/docs/api/simple-types/README.md index 2b2ef7344..1b00580f7 100644 --- a/docs/api/simple-types/README.md +++ b/docs/api/simple-types/README.md @@ -8,4 +8,3 @@ has_children: true # Simple Types There are several simple or basic types used in Windows MIDI Services. These types provide formatting and validation to help ensure applications display data in similar ways. - diff --git a/samples/cpp-winrt/api-client-basics/api-client-basics.vcxproj b/samples/cpp-winrt/api-client-basics/api-client-basics.vcxproj index efdc9e88e..24ac173dc 100644 --- a/samples/cpp-winrt/api-client-basics/api-client-basics.vcxproj +++ b/samples/cpp-winrt/api-client-basics/api-client-basics.vcxproj @@ -1,6 +1,6 @@ - + true @@ -150,7 +150,7 @@ - + @@ -158,7 +158,7 @@ - - + + \ No newline at end of file diff --git a/samples/cpp-winrt/api-client-basics/main.cpp b/samples/cpp-winrt/api-client-basics/main.cpp index 52bf704a2..998c19646 100644 --- a/samples/cpp-winrt/api-client-basics/main.cpp +++ b/samples/cpp-winrt/api-client-basics/main.cpp @@ -8,10 +8,7 @@ #include "pch.h" #include -using namespace winrt::Windows::Devices::Midi2; // API - -// standard WinRT enumeration support. This is how you find attached devices. -using namespace winrt::Windows::Devices::Enumeration; +using namespace winrt::Microsoft::Devices::Midi2; // API // where you find types like IAsyncOperation, IInspectable, etc. namespace foundation = winrt::Windows::Foundation; @@ -21,6 +18,16 @@ int main() { winrt::init_apartment(); + // Check to see if Windows MIDI Services is installed and running on this PC + if (!MidiService::IsAvailable()) + { + // you may wish to fallback to an older MIDI API if it suits your application's workflow + std::cout << std::endl << "Windows MIDI Services is not running on this PC" << std::endl; + + return 1; + } + + // create the MIDI session, giving us access to Windows MIDI Services. An app may open // more than one session. If so, the session name should be meaningful to the user, like // the name of a browser tab, or a project. diff --git a/samples/cpp-winrt/api-client-basics/packages.config b/samples/cpp-winrt/api-client-basics/packages.config index 9fca0dc22..4a07d7353 100644 --- a/samples/cpp-winrt/api-client-basics/packages.config +++ b/samples/cpp-winrt/api-client-basics/packages.config @@ -1,5 +1,5 @@  + - \ No newline at end of file diff --git a/samples/cpp-winrt/api-client-basics/pch.h b/samples/cpp-winrt/api-client-basics/pch.h index fe2803d1a..8dc5dcb95 100644 --- a/samples/cpp-winrt/api-client-basics/pch.h +++ b/samples/cpp-winrt/api-client-basics/pch.h @@ -3,4 +3,4 @@ #include #include -#include \ No newline at end of file +#include \ No newline at end of file diff --git a/samples/csharp-net/api-client-basics-cs/Program.cs b/samples/csharp-net/api-client-basics-cs/Program.cs index 12c29fb23..8b06d537c 100644 --- a/samples/csharp-net/api-client-basics-cs/Program.cs +++ b/samples/csharp-net/api-client-basics-cs/Program.cs @@ -1,80 +1,92 @@ -using Windows.Devices.Midi2; +using Microsoft.Devices.Midi2; using Windows.Devices.Enumeration; using System; // for comments on what each step does, please see the C++/WinRT sample // the code is almost identical -Console.WriteLine("Creating session"); +Console.WriteLine("Checking for Windows MIDI Services"); -// session implements IDisposable -using (var session = MidiSession.CreateSession("API Sample Session")) +// you only need to check this once +if (!MidiService.IsAvailable()) { - var endpointAId = MidiEndpointDeviceInformation.DiagnosticsLoopbackAEndpointId; - var endpointBId = MidiEndpointDeviceInformation.DiagnosticsLoopbackBEndpointId; - - Console.WriteLine("Connecting to Sender UMP Endpoint: " + endpointAId); - Console.WriteLine("Connecting to Receiver UMP Endpoint: " + endpointBId); + // In your application, you may decide it is appropriate to fall back to an older MIDI API + Console.WriteLine("Windows MIDI Services is not available"); +} +else +{ + Console.WriteLine("Creating session"); + // session implements IDisposable + using (var session = MidiSession.CreateSession("API Sample Session")) + { + var endpointAId = MidiEndpointDeviceInformation.DiagnosticsLoopbackAEndpointId; + var endpointBId = MidiEndpointDeviceInformation.DiagnosticsLoopbackBEndpointId; - var sendEndpoint = session.CreateEndpointConnection(endpointAId); - var receiveEndpoint = session.CreateEndpointConnection(endpointBId); + Console.WriteLine("Connecting to Sender UMP Endpoint: " + endpointAId); + Console.WriteLine("Connecting to Receiver UMP Endpoint: " + endpointBId); - // c# allows local functions. This is nicer than anonymous because we can unregister it by name - void MessageReceivedHandler(object sender, MidiMessageReceivedEventArgs args) - { - var ump = args.GetMessagePacket(); + var sendEndpoint = session.CreateEndpointConnection(endpointAId); + var receiveEndpoint = session.CreateEndpointConnection(endpointBId); - Console.WriteLine(); - Console.WriteLine("Received UMP"); - Console.WriteLine("- Current Timestamp: " + MidiClock.Now); - Console.WriteLine("- UMP Timestamp: " + ump.Timestamp); - Console.WriteLine("- UMP Msg Type: " + ump.MessageType); - Console.WriteLine("- UMP Packet Type: " + ump.PacketType); - Console.WriteLine("- Message: " + MidiMessageUtility.GetMessageFriendlyNameFromFirstWord(args.PeekFirstWord())); - if (ump is MidiMessage32) + // c# allows local functions. This is nicer than anonymous because we can unregister it by name + void MessageReceivedHandler(object sender, MidiMessageReceivedEventArgs args) { - var ump32 = ump as MidiMessage32; + var ump = args.GetMessagePacket(); - if (ump32 != null) - Console.WriteLine("- Word 0: 0x{0:X}", ump32.Word0); - } - }; + Console.WriteLine(); + Console.WriteLine("Received UMP"); + Console.WriteLine("- Current Timestamp: " + MidiClock.Now); + Console.WriteLine("- UMP Timestamp: " + ump.Timestamp); + Console.WriteLine("- UMP Msg Type: " + ump.MessageType); + Console.WriteLine("- UMP Packet Type: " + ump.PacketType); + Console.WriteLine("- Message: " + MidiMessageUtility.GetMessageFriendlyNameFromFirstWord(args.PeekFirstWord())); - receiveEndpoint.MessageReceived += MessageReceivedHandler; + if (ump is MidiMessage32) + { + var ump32 = ump as MidiMessage32; - Console.WriteLine("Opening endpoint connection"); + if (ump32 != null) + Console.WriteLine("- Word 0: 0x{0:X}", ump32.Word0); + } + }; - // once you have wired up all your event handlers, added any filters/listeners, etc. - // You can open the connection. Doing this will query the cache for the in-protocol - // endpoint information and function blocks. If not there, it will send out the requests - // which will come back asynchronously with responses. - receiveEndpoint.Open(); - sendEndpoint.Open(); + receiveEndpoint.MessageReceived += MessageReceivedHandler; + Console.WriteLine("Opening endpoint connection"); - Console.WriteLine("Creating MIDI 1.0 Channel Voice 32-bit UMP..."); + // once you have wired up all your event handlers, added any filters/listeners, etc. + // You can open the connection. Doing this will query the cache for the in-protocol + // endpoint information and function blocks. If not there, it will send out the requests + // which will come back asynchronously with responses. + receiveEndpoint.Open(); + sendEndpoint.Open(); - var ump32 = MidiMessageBuilder.BuildMidi1ChannelVoiceMessage( - MidiClock.Now, // use current timestamp - new MidiGroup(5), // group 5 - Midi1ChannelVoiceMessageStatus.NoteOn, // 9 - new MidiChannel(3), // channel 3 - 120, // note 120 - hex 0x78 - 100); // velocity 100 hex 0x64 - sendEndpoint.SendSingleMessagePacket((IMidiUniversalPacket)ump32); // could also use the SendWords methods, etc. + Console.WriteLine("Creating MIDI 1.0 Channel Voice 32-bit UMP..."); - Console.WriteLine(" ** Wait for the message to arrive, and then press enter to cleanup. ** "); - Console.ReadLine(); + var ump32 = MidiMessageBuilder.BuildMidi1ChannelVoiceMessage( + MidiClock.Now, // use current timestamp + new MidiGroup(5), // group 5 + Midi1ChannelVoiceMessageStatus.NoteOn, // 9 + new MidiChannel(3), // channel 3 + 120, // note 120 - hex 0x78 + 100); // velocity 100 hex 0x64 - // you should unregister the event handler as well - receiveEndpoint.MessageReceived -= MessageReceivedHandler; + sendEndpoint.SendSingleMessagePacket((IMidiUniversalPacket)ump32); // could also use the SendWords methods, etc. - // not strictly necessary if the session is going out of scope or is in a using block - session.DisconnectEndpointConnection(sendEndpoint.ConnectionId); - session.DisconnectEndpointConnection(receiveEndpoint.ConnectionId); -} + Console.WriteLine(" ** Wait for the message to arrive, and then press enter to cleanup. ** "); + Console.ReadLine(); + + // you should unregister the event handler as well + receiveEndpoint.MessageReceived -= MessageReceivedHandler; + + // not strictly necessary if the session is going out of scope or is in a using block + session.DisconnectEndpointConnection(sendEndpoint.ConnectionId); + session.DisconnectEndpointConnection(receiveEndpoint.ConnectionId); + } + +} diff --git a/samples/csharp-net/api-client-basics-cs/api-client-basics-cs.csproj b/samples/csharp-net/api-client-basics-cs/api-client-basics-cs.csproj index 64f73a3e2..26cebf5ec 100644 --- a/samples/csharp-net/api-client-basics-cs/api-client-basics-cs.csproj +++ b/samples/csharp-net/api-client-basics-cs/api-client-basics-cs.csproj @@ -10,8 +10,8 @@ + - diff --git a/src/api/Client/Midi2Client/MidiService.cpp b/src/api/Client/Midi2Client/MidiService.cpp index a13438329..98df167fa 100644 --- a/src/api/Client/Midi2Client/MidiService.cpp +++ b/src/api/Client/Midi2Client/MidiService.cpp @@ -8,21 +8,31 @@ #include "pch.h" -#include "MidiService.h" #include "MidiService.g.cpp" -#include "ping_ump_types.h" - -#include -#include - namespace MIDI_CPP_NAMESPACE::implementation { - // returns True if the MIDI Service is available on this PC bool MidiService::IsAvailable() { - // TODO: Need to check an install marker. No checks that require elevation. + // We may want other ways to check this in the future. Need to find the most robust approaches + + try + { + //auto serviceAbstraction = winrt::create_instance(__uuidof(Midi2MidiSrvAbstraction), CLSCTX_ALL); + auto serviceAbstraction = winrt::try_create_instance(__uuidof(Midi2MidiSrvAbstraction), CLSCTX_ALL); + + // winrt::try_create_instance indicates failure by returning an empty com ptr + if (serviceAbstraction == nullptr) + { + return false; + } + } + catch (...) + { + // winrt::create_instance fails by throwing an exception + return false; + } return true; } diff --git a/src/api/Client/Midi2Client/MidiSession.cpp b/src/api/Client/Midi2Client/MidiSession.cpp index 8827a2376..033c7b369 100644 --- a/src/api/Client/Midi2Client/MidiSession.cpp +++ b/src/api/Client/Midi2Client/MidiSession.cpp @@ -40,6 +40,7 @@ namespace MIDI_CPP_NAMESPACE::implementation } else { + // this can happen is service is not available or running return nullptr; } } @@ -49,6 +50,12 @@ namespace MIDI_CPP_NAMESPACE::implementation return nullptr; } + catch (...) + { + internal::LogGeneralError(__FUNCTION__, L"Exception initializing creating session. Service may be unavailable."); + + return nullptr; + } } diff --git a/src/api/Client/Midi2Client/pch.h b/src/api/Client/Midi2Client/pch.h index c6de68cbc..7c2765e69 100644 --- a/src/api/Client/Midi2Client/pch.h +++ b/src/api/Client/Midi2Client/pch.h @@ -79,6 +79,7 @@ namespace internal = ::WindowsMidiServicesInternal; #include "json_helpers.h" #include "swd_helpers.h" #include "resource_util.h" +#include "ping_ump_types.h" //#include "MidiXProc.h" #include "resource.h" diff --git a/src/api/Client/Midi2Client/trace_logging.cpp b/src/api/Client/Midi2Client/trace_logging.cpp index ea20f37de..32d87c8c7 100644 --- a/src/api/Client/Midi2Client/trace_logging.cpp +++ b/src/api/Client/Midi2Client/trace_logging.cpp @@ -98,7 +98,7 @@ namespace WindowsMidiServicesInternal const char* location, const wchar_t* message) noexcept { - //OutputDebugString(L"" __FUNCTION__ L"API General Error. Use tracing provider for details."); + OutputDebugString(L"" __FUNCTION__ L"API General Error. Use tracing provider for details."); if (!g_traceLoggingRegistered) RegisterTraceLogging();