Skip to content

Commit

Permalink
Merge pull request #351 from microsoft/pete-dev
Browse files Browse the repository at this point in the history
Virtual device and docs updates
  • Loading branch information
Psychlist1972 authored Jun 21, 2024
2 parents 7100a16 + 74176d9 commit d7802db
Show file tree
Hide file tree
Showing 901 changed files with 15,867 additions and 4,270 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified build/staging/app-sdk/Arm64EC/mididiag.exe
Binary file not shown.
2 changes: 1 addition & 1 deletion build/staging/version/BundleInfo.wxi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Include>
<?define SetupVersionName="Developer Preview 6 x64" ?>
<?define SetupVersionNumber="1.0.24171.1742" ?>
<?define SetupVersionNumber="1.0.24173.1522" ?>
</Include>
24 changes: 11 additions & 13 deletions docs/developer-how-to/how-to-check-for-windows-midi-services.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,37 +46,36 @@ else

### How this works

The Windows Service is triggered to start via a specific call, which triggers an ETL event. The ETL event is what the service watches for to spin up. Typically, this takes only a second or two to happen.
The Windows Service is triggered to start via `EnsureServiceAvailable()`, which calls the service interface and triggers an ETL event. The ETL event is what the service watches for to spin up. Typically, this takes only a second or two to happen.

Once the service is started, all the devices it is responsible for begin to be enumerated.
Once the service hass started, all the devices it is responsible for begin to be enumerated. It will remain running until manually shut down or the PC is rebooted.

Once the service is demand-started, it remains running until manually shut down or the next reboot.
> Musicians may want to set the service to auto-start with Windows. It adds a little bit of time to Windows startup, but the devices will be available when needed.
## Bootstrap the Windows MIDI Services SDK runtime (Desktop apps only)

For desktop apps, other than the initializer, the rest of the SDK is installed centrally on the PC rather than shipped with applications. This enables bug and security fixes without asking each software developer to ship a new version of their application.

Because the SDK is shipped out-of-band from Windows itself, and is restricted to specific versions of Windows and devices (no current support on Xbox and Hololens, for example) the SDK must be bootstrapped/initialized so the application can find the WinRT types contained within.

Internally, the initializer uses a combination of Registration-free WinRT and the Detours library to hook into type resolution. To support the use of the initializer, the application must include a manifest file, named `AppName.exe.manifest` where "AppName" is the name of the executable.
Internally, the initializer uses a combination of Registration-free WinRT and the Detours library to hook into type resolution and activation. To support the use of the initializer, the application must include an entry in the application manifest file, named `AppName.exe.manifest` where "AppName" is the name of the executable.

Manifest contents:

```xml
<?xml version="1.0" encoding="utf-8"?>

<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyWindowsMidiServicesApplication.app"/>
<!-- This is the only manifest entry needed for desktop apps using MIDI -->

<!-- This is the only manifest entry needed for desktop apps -->
<!-- This specific DLL is shipped with the app itself -->
<file name="Microsoft.Windows.Devices.Midi2.Initialization.dll">
<activatableClass
name="Microsoft.Windows.Devices.Midi2.Initialization.IMidiServicesInitializerStatics"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
</file>

<!-- Other manifest entries are not impacted by this entry -->
</assembly>
```

Expand All @@ -89,18 +88,17 @@ In addition to the manifest, the application must include the `Microsoft.Windows
If the runtime is not present, but the service is present, the application can either prompt the user to download and install the runtime. That is an application-specific decision.

```cpp
auto uri = (MidiServicesInitializer::GetLatestRuntimeReleaseInstallerUri())
auto uri = MidiServicesInitializer::GetLatestRuntimeReleaseInstallerUri();
```

> Your desktop application installer can also include code to download and install the latest Windows MIDI Services runtime, rather than doing this after the application has already started.
> Your desktop application's installer can also include code to download and install the latest Windows MIDI Services runtime, rather than doing this after the application has already started.
## Use the SDK from packaged (Store etc.) apps

If the app is packaged using MSIX, the bootstrapper is not used. Instead, the app must declare all Windows MIDI Services WinRT types in its manifest. It must also declare a dependency on ... TODO


## Sample Code
If the app is packaged using MSIX, the bootstrapper is not used. Instead, the app must declare all Windows MIDI Services WinRT types in its manifest. It must also declare a dependency on ...

TODO

## Sample Code

The existing samples are in the process of being updated with this new bootstrapping code.
18 changes: 5 additions & 13 deletions docs/developer-how-to/how-to-create-loopback-endpoints.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
---
layout: page
title: Create Loopback Endpoints= Pairs
title: Create Loopback Endpoint Pairs
parent: Developer How-to
has_children: false
---

# How to create simple Loopback Endpoint Pairs at runtime

## How the Loopback Endpoint Pair works


## How to create a Loopback Endpoint Pair

We'll assume here you've already initialized Windows MIDI Services and created a session.

First, you define the two sides of the loopback. Because UMP endpoints are bidirectional, the loopback works from either direction: Messages set out on A arrive in on B, and those sent out on B arrive in on A.
Expand All @@ -31,7 +26,7 @@ definitionB.Description = L"The second description is optional, but is displayed
definitionB.UniqueId = L"3263827-OU812-5150"; // can be the same as the first one, but doesn't need to be.
```

Next, create the transient (meaning they are not in the config file and recreated after a reboot) loopback endpoint pair using the above definitions
Next, create the transient (meaning they are not in the config file and therefore are not recreated after a reboot) loopback endpoint pair using the above definitions

```cpp
MidiLoopbackEndpointCreationConfig creationConfig(m_associationId, definitionA, definitionB);
Expand All @@ -40,7 +35,7 @@ auto response = MidiLoopbackEndpointManager::CreateTransientLoopbackEndpoints(cr

if (response.Success)
{
std::wcout << L"Endpoints created successfully" << std::endl << std::endl;
std::cout << "Endpoints created successfully" << std::endl << std::endl;

std::cout
<< "Loopback Endpoint A: " << std::endl
Expand All @@ -60,18 +55,15 @@ else
// failed to create the loopback pair. It may be that the unique
// Ids are already in use.
}

```
One thing you may have noticed in the listing above is the use of an association Id. This identifier is just a GUID you generate to associate the endpoint pairs together. This is what establishes the relationship between them.
One thing you may have noticed in the listing above is the use of an **association Id**. This identifier is a GUID you generate and then use to associate the endpoint pairs together. This is what establishes the relationship between the two endpoints.
```cpp
winrt::guid m_associationId = winrt::Windows::Foundation::GuidHelper::CreateNewGuid();
```

That's all that's needed. You can connect to either endpoint and use it as you would any other.

> Note: Loopback Endpoint Pairs are not currently visible to the WinMM MIDI 1.0 API. There are complexities with that API when devices are added and removed at runtime. It's possible these devices will never be visibile to WinMM MIDI 1.0. For full functionality, we recommend using the new Windows MIDI Services SDK.
That's all that's needed. You can connect to and open either endpoint and use it as you would any other.

## Sample Code

Expand Down
140 changes: 125 additions & 15 deletions docs/developer-how-to/how-to-create-virtual-ump-device.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,145 @@ parent: Developer How-to
has_children: false
---

# How to Create Virtual UMP Devices at Runtime
# How to Create Virtual Devices at Runtime

If you develop an application which should appear as a new MIDI device to other applications on Windows, you want to create a Virtual UMP Device. Your app may be a controller app, a sound generator/synthesizer, or a bridge to accessibility or other controllers. Anything a hardware MIDI device can do is open to you here.

## How Virtual Devices work

A virtual device enables an application to appear as a UMP Endpoint to other applications.

[More information on how Virtual Devices work may be found here](../endpoints/virtual-device-app.md).

## Steps to Create a Virtual Device

1. Create a MIDI session
2. Define the Virtual MIDI Device, its function blocks, and other properties
3. Create the Device and get the `EndpointDeviceId` for the device application
4. Connect to the Device like any other MIDI connection
5. Wire up event handlers for message received and optionally for protocol negotiation
6. Open the connection
7. Respond to any protocol negotiation or message received events
8. When the application no longer needs to expose the virtual device, close the connection.
1. [Check for and bootstrap Windows MIDI Services](./how-to-check-for-windows-midi-services.md)
2. Create a MIDI session
3. Define the Virtual MIDI Device, its function blocks, and other properties
4. Create the Device and get the `EndpointDeviceId` for the device-side application endpoint
5. Connect to the Device like any other MIDI connection
6. Wire up event handlers for message received and optionally for stream configuration
7. Open the connection
8. Respond to any protocol negotiation or message received events
9. When the application no longer needs to expose the virtual device, close the connection.

On the service-side, the Virtual Device works like any other native UMP MIDI 2.0 device, including for endpoint metadata capture and protocol negotiation.

More details available in the Endpoints documentation.
## Code

### Example
We'll assume you've already performed the [Windows MIDI Services bootstrapping steps](./how-to-check-for-windows-midi-services.md).

TODO
The first step is to define the virtual device by creating the different metadata declarations and then assemble them together using the `MidiVirtualDeviceCreationConfig` type.

This information is all required so that the virtual device responder can handle the MIDI 2.0 endpoint discovery and protocol negotiation messages on your behalf. This removes the complexity of message parsing and (in the case of names and ids) message assembly.

> When creating the device's software device id (SWD) only the first 32 characters of the `ProductInstanceId` are used. This must be unique among all **virtual UMP devices** currently running in Windows MIDI Services, or else the device creation will fail. One recommendation for uniqueness is to use a GUID with all non-alphanumeric characters removed. Another would be to use the app name and an internal index or differentiator.
```cpp
// endpoint information returned from endpoint discovery
midi2::MidiDeclaredEndpointInfo declaredEndpointInfo{ };
declaredEndpointInfo.Name = endpointSuppliedName;
declaredEndpointInfo.ProductInstanceId = L"PMB_APP2_3263827"; // must be unique
declaredEndpointInfo.SpecificationVersionMajor = 1; // see latest MIDI 2 UMP spec
declaredEndpointInfo.SpecificationVersionMinor = 1; // see latest MIDI 2 UMP spec
declaredEndpointInfo.SupportsMidi10Protocol = true;
declaredEndpointInfo.SupportsMidi20Protocol = true;
declaredEndpointInfo.SupportsReceivingJitterReductionTimestamps = false;
declaredEndpointInfo.SupportsSendingJitterReductionTimestamps = false;
declaredEndpointInfo.HasStaticFunctionBlocks = true;

## Sample Code
midi2::MidiDeclaredDeviceIdentity declaredDeviceIdentity{ };
// todo: set any device identity values if you want. This is optional

midi2::MidiEndpointUserSuppliedInfo userSuppliedInfo{ };
userSuppliedInfo.Name = userSuppliedName; // for names, this will bubble to the top in priority
userSuppliedInfo.Description = userSuppliedDescription;

// create the config type to aggregate all this info
virt::MidiVirtualDeviceCreationConfig config(
transportSuppliedName, // this could be a different "transport-supplied" name value here
transportSuppliedDescription, // transport-supplied description
transportSuppliedManufacturerName, // transport-supplied company name
declaredEndpointInfo, // for endpoint discovery
declaredDeviceIdentity, // for endpoint discovery
userSuppliedInfo
);
```
We're not quite done yet, however. The config type is also where you'll set function blocks. At least one function block is needed.
```cpp
// Function blocks. The MIDI 2 UMP specification covers the meanings of these values
midi2::MidiFunctionBlock block1{ };
block1.Number(0);
block1.Name(L"Pads Output");
block1.IsActive(true);
block1.UIHint(midi2::MidiFunctionBlockUIHint::Sender);
block1.FirstGroupIndex(0);
block1.GroupCount(1);
block1.Direction(midi2::MidiFunctionBlockDirection::Bidirectional);
block1.RepresentsMidi10Connection(midi2::MidiFunctionBlockRepresentsMidi10Connection::Not10);
block1.MaxSystemExclusive8Streams(0);
block1.MidiCIMessageVersionFormat(0);
config.FunctionBlocks().Append(block1);
midi2::MidiFunctionBlock block2{ };
block2.Number(1);
block2.Name(L"A Function Block");
block2.IsActive(true);
block2.UIHint(midi2::MidiFunctionBlockUIHint::Sender);
block2.FirstGroupIndex(1);
block2.GroupCount(2);
block2.Direction(midi2::MidiFunctionBlockDirection::Bidirectional);
block2.RepresentsMidi10Connection(midi2::MidiFunctionBlockRepresentsMidi10Connection::Not10);
block2.MaxSystemExclusive8Streams(0);
block2.MidiCIMessageVersionFormat(0);
config.FunctionBlocks().Append(block2);
```

Now, the virtual device is fully defined. The next step is to open a session and then actually create the device in the service.

* [C# Sample](https://github.com/microsoft/MIDI/tree/main/samples/csharp-net/app-to-app-midi-cs)
```cpp
// create the session. The name here is just convenience.
m_session = midi2::MidiSession::Create(config.Name());

if (m_session == nullptr) return; // return if unable to create session

// create the virtual device, so we can get the endpoint device id to connect to
m_virtualDevice = virt::MidiVirtualDeviceManager::CreateVirtualDevice(config);

if (m_virtualDevice == nullptr) return; // return if unable to create virtual device

// create the endpoint connection to the device-side endpoint
// to prevent confusion, this endpoint is not enumerated to
// apps when using the standard set of enumeration filters
m_connection = m_session.CreateEndpointConnection(
m_virtualDevice.DeviceEndpointDeviceId());

// add the virtual device as a message processing plugin so it receives the messages
m_connection.AddMessageProcessingPlugin(m_virtualDevice);

// wire up the stream configuration request received handler
auto streamEventToken = m_virtualDevice.StreamConfigRequestReceived(
{ this, &MainWindow::OnStreamConfigurationRequestReceived });

// wire up the message received handler on the connection itself
auto messageEventToken = m_connection.MessageReceived(
{ this, &MainWindow::OnMidiMessageReceived });

// the client-side endpoint will become visible to other apps once Open() completes
m_connection.Open();
```
From there, you may send and receive messages just like with any other endpoint.
## Troubleshooting
What can cause a failure in virtual device creation? Assuming the service is installed and working properly, the main thing to check will be to ensure that the unique Id provided is actually unique. The unique Id is used as the differentiator in the SWD Id, without any additional hashing or obfuscation, so it must be unique among all virtual devices currently running. When in doubt, one practice to ensure uniqueness is to use a GUID by formatting as string and removing all non alpha-numeric characters. The unique Id is just large enough to hold that string.
## Sample Code
> Note: Virtual UMP devices are not currently visible to the WinMM MIDI 1.0 API (the API used by most MIDI 1.0 apps on Windows). There are enumeration complexities with that API when devices are added and removed at runtime, which is part of why we needed to create a new API anyway. It's possible these devices will never be visibile to WinMM MIDI 1.0. For full functionality, we recommend using the new Windows MIDI Services SDK.
* [C++ WinUI Sample](https://github.com/microsoft/MIDI/tree/main/samples/cpp-winrt/virtual-device-app-winui)
* [C# WinUI Sample](https://github.com/microsoft/MIDI/tree/main/samples/csharp-net/virtual-device-app-winui)
4 changes: 2 additions & 2 deletions docs/developer-how-to/how-to-enumerate-endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ That is the equivalent of passing in a sort order of the name, and a filter of t
```cpp
auto endpointList = MidiEndpointDeviceInformation::FindAll(
MidiEndpointDeviceInformationSortOrder::Name,
MidiEndpointDeviceInformationFilters::AllTypicalEndpoints
MidiEndpointDeviceInformationFilters::AllStandardEndpoints
);
```

Expand All @@ -30,7 +30,7 @@ The application may then iterate through the list, reading the properties as nee

Windows MIDI Services has a very rich set of properties available for a UMP Endpoint. This information includes hardware and other transport information, parent device information, user-supplied information, and in the case of a MIDI 2.0 UMP Endpoint, declared information from endpoint discovery and protocol negotiation carried out within the Windows service.

For more details, see the `MidiEndpointDeviceInformation` class documentation. You may also use the MIDI Console application to see all of the properties (including the raw property data if you choose to) for an endpoint.
For more details, see the [`MidiEndpointDeviceInformation`](../sdk-winrt-core/enumeration/MidiEndpointDeviceInformation.md) class documentation. You may also use the [MIDI Console application](../console/midi-console.md) to see all of the properties (including the raw property data if you choose to) for an endpoint.

## Sample Code

Expand Down
8 changes: 4 additions & 4 deletions docs/developer-how-to/how-to-watch-endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ In Windows MIDI Services, we've provided a specialized version of the device wat
## Events

To use the `MidiEndpointDeviceWatcher`, first wire up handlers for the `Added`, `Removed`, and `Updated` events. Optionally, you may wire up handlers for the `EnumerationCompleted` event to be notified when initial enumeration has finished, and the `Stopped` event to know when the watcher has been stopped by a call to the `Stop` method.
To use the [`MidiEndpointDeviceWatcher`](../sdk-winrt-core/enumeration/MidiEndpointDeviceWatcher.md), first wire up handlers for the `Added`, `Removed`, and `Updated` events. Optionally, you may wire up handlers for the `EnumerationCompleted` event to be notified when initial enumeration has finished, and the `Stopped` event to know when the watcher has been stopped by a call to the `Stop` method.

Once the event handlers have been wired up, create the watcher using the static `Create` function.

Expand All @@ -26,16 +26,16 @@ auto watcher = MidiEndpointDeviceWatcher::Create();
If you wish to use a filter list that differs from the default (the default is appropriate for most applications, as it filters out diagnostics and other endpoints not typically shown to end users) you may use the overloaded Create function. For example, to show only native UMP endpoints, not translated MIDI 1.0 devices, you would do this:

```cpp
auto watcher = MidiEndpointDeviceWatcher::Create(MidiEndpointDeviceInformationFilters::IncludeClientUmpFormatNative);
auto watcher = MidiEndpointDeviceWatcher::Create(MidiEndpointDeviceInformationFilters::StandardNativeUniversalMidiPacketFormat);
```

And, conversely, to show only up-converted MIDI 1.0 byte format endpoints:

```cpp
auto watcher = MidiEndpointDeviceWatcher::Create(MidiEndpointDeviceInformationFilters::IncludeClientByteFormatNative);
auto watcher = MidiEndpointDeviceWatcher::Create(MidiEndpointDeviceInformationFilters::StandardNativeMidi1ByteFormat);
```

The default is to include both, which is also represented by `MidiEndpointDeviceInformationFilters::AllTypicalEndpoints`.
The default is to include both, which is also represented by `MidiEndpointDeviceInformationFilters::AllStandardEndpoints`.

## Accessing the list

Expand Down
Loading

0 comments on commit d7802db

Please sign in to comment.