diff --git a/build/build.cake b/build/build.cake index 9f5d7f04..15eb96f1 100644 --- a/build/build.cake +++ b/build/build.cake @@ -2,10 +2,13 @@ //#addin nuget:?package=Cake.Compression&version=0.3.0 // =========================================================================================== -string setupVersionName = "Developer Preview 5"; -// =========================================================================================== - +string setupVersionName = "Developer Preview 6"; +// we set these here, especially the time, so it's the same for all platforms in the single build +string setupBuildMajorMinor = "1.0"; +string setupBuildDateNumber = DateTime.Now.ToString("yy") + DateTime.Now.DayOfYear.ToString("000"); // YYddd where ddd is the day number for the year +string setupBuildTimeNumber = DateTime.Now.ToString("HHmm"); // HHmm +// =========================================================================================== @@ -18,8 +21,8 @@ var ridArm64 = "win10-arm64"; var frameworkVersion = "net8.0-windows10.0.20348.0"; -//var platformTargets = new[]{PlatformTarget.x64, PlatformTarget.ARM64}; -var platformTargets = new[]{PlatformTarget.x64}; +var platformTargets = new[]{PlatformTarget.x64, PlatformTarget.ARM64}; +//var platformTargets = new[]{PlatformTarget.x64}; @@ -180,6 +183,7 @@ Task("SetupEnvironment") CopyFiles(System.IO.Path.Combine(outputDir, "*.pdb"), copyToDir); CopyFiles(System.IO.Path.Combine(outputDir, "MidiSrv.exe"), copyToDir); + CopyFiles(System.IO.Path.Combine(outputDir, "mididmp.exe"), copyToDir); CopyFiles(System.IO.Path.Combine(outputDir, "Windows.Devices.Midi2.winmd"), copyToDir); @@ -188,7 +192,6 @@ Task("SetupEnvironment") // copy the C++ header for the API CopyFiles(System.IO.Path.Combine(generatedFilesDir, "Windows.Devices.Midi2.h"), copyToDir); - CopyFiles(System.IO.Path.Combine(outputDir, "mididmp.exe"), copyToDir); @@ -216,10 +219,6 @@ Task("SetupEnvironment") CopyFiles(System.IO.Path.Combine(generatedFilesDir, "impl/Windows.Foundation.Collections.2.h"), System.IO.Path.Combine(apiBareCopyToDir, "winrt/impl/")); CopyFiles(System.IO.Path.Combine(generatedFilesDir, "impl/Windows.Devices.Midi2.2.h"), System.IO.Path.Combine(apiBareCopyToDir, "winrt/impl/")); - - - - CopyFiles(System.IO.Path.Combine(copyToDir, "Windows.Devices.Midi2.dll"), apiBareCopyToDir); CopyFiles(System.IO.Path.Combine(copyToDir, "Windows.Devices.Midi2.winmd"), apiBareCopyToDir); CopyFiles(System.IO.Path.Combine(copyToDir, "Windows.Devices.Midi2.pri"), apiBareCopyToDir); @@ -228,81 +227,16 @@ Task("SetupEnvironment") }); -Task("BuildApiActivationRegEntries") - .IsDependentOn("BuildServiceAndAPI") - .DoesForEach(platformTargets, plat => -{ - Information("\nBuilding WinRT Activation Entries for " + plat.ToString()); - // read the file of dependencies - var sourceFileName = System.IO.Path.Combine(apiAndServiceStagingDir, plat.ToString(), "WinRTActivationEntries.txt"); - var wxiDestinationFileName = System.IO.Path.Combine(apiAndServiceStagingDir, plat.ToString(), "WinRTActivationEntries.wxi"); - - const string parentHKLMRegKey = "SOFTWARE\\Microsoft\\WindowsRuntime\\ActivatableClassId\\"; - const string wixWinrtLibFileName = "[API_INSTALLFOLDER]Windows.Devices.Midi2.dll"; - - if (!System.IO.File.Exists(sourceFileName)) - throw new ArgumentException("Missing WinRT Activation entries file " + sourceFileName); - - using (StreamReader reader = System.IO.File.OpenText(sourceFileName)) - { - using (StreamWriter wxiWriter = System.IO.File.CreateText(wxiDestinationFileName)) - { - wxiWriter.WriteLine(""); - - string line; - - while ((line = reader.ReadLine()) != null) - { - string trimmedLine = line.Trim(); - - if (string.IsNullOrWhiteSpace(trimmedLine) || trimmedLine.StartsWith("#")) - { - // comment or empty line - continue; - } - - // pipe-delimited lines - var elements = trimmedLine.Split('|'); - - if (elements.Count() != 4) - throw new ArgumentException("Bad line: " + trimmedLine); - - // entries in order are - // ClassName | ActivationType | Threading | TrustLevel - - string className = elements[0].Trim(); - string activationType = elements[1].Trim(); - string threading = elements[2].Trim(); - string trustLevel = elements[3].Trim(); - - wxiWriter.WriteLine($""); - - wxiWriter.WriteLine($" "); - wxiWriter.WriteLine($" "); - wxiWriter.WriteLine($" "); - wxiWriter.WriteLine($" "); - - wxiWriter.WriteLine(""); - - } - - wxiWriter.WriteLine(""); - - } - } - -}); Task("BuildApiActivationRegEntriesCSharp") .IsDependentOn("BuildServiceAndAPI") - .DoesForEach(platformTargets, plat => + .Does(() => { - Information("\nBuilding WinRT Activation Entries for " + plat.ToString()); + Information("\nBuilding WinRT Activation Entries "); - // read the file of dependencies - var sourceFileName = System.IO.Path.Combine(apiAndServiceStagingDir, plat.ToString(), "WinRTActivationEntries.txt"); + var sourceFileName = System.IO.Path.Combine(apiAndServiceStagingDir, "x64", "WinRTActivationEntries.txt"); var csDestinationFileName = System.IO.Path.Combine(registrySettingsStagingDir, "WinRTActivationEntries.cs"); // const string parentHKLMRegKey = "SOFTWARE\\Microsoft\\WindowsRuntime\\ActivatableClassId\\"; @@ -370,12 +304,12 @@ Task("BuildApiActivationRegEntriesCSharp") Task("BuildApiActivationRegEntriesInternal") .IsDependentOn("BuildServiceAndAPI") - .DoesForEach(platformTargets, plat => + .Does(() => { - Information("\nBuilding WinRT Internaml XML Activation Entries for " + plat.ToString()); + Information("\nBuilding WinRT Internaml XML Activation Entries"); // read the file of dependencies - var sourceFileName = System.IO.Path.Combine(apiAndServiceStagingDir, plat.ToString(), "WinRTActivationEntries.txt"); + var sourceFileName = System.IO.Path.Combine(apiAndServiceStagingDir, "x64", "WinRTActivationEntries.txt"); var destinationFileName = System.IO.Path.Combine(registrySettingsStagingDir, "WinRTActivationEntries.xml"); if (!System.IO.File.Exists(sourceFileName)) @@ -450,36 +384,8 @@ Task("PackAPIProjection") }); - -Task("BuildSDK") - .IsDependentOn("BuildServiceAndAPI") - .IsDependentOn("PackAPIProjection") - .DoesForEach(platformTargets, plat => -{ - Information("\nBuilding SDK for " + plat.ToString()); - - // TODO - - -}); - - -Task("PackSDKProjection") - .IsDependentOn("BuildSDK") - .Does(() => -{ - Information("\nPacking SDK..."); - - // TODO - - -}); - - - Task("BuildConsoleApp") .IsDependentOn("PackAPIProjection") -/* .IsDependentOn("PackSDKProjection") */ .DoesForEach(platformTargets, plat => { // TODO: Update nuget ref in console app to the new version @@ -535,7 +441,6 @@ Task("BuildConsoleApp") Task("BuildSettingsApp") .IsDependentOn("PackAPIProjection") - .IsDependentOn("PackSDKProjection") .DoesForEach(platformTargets, plat => { // TODO: Update nuget ref in settings app to the new version @@ -589,49 +494,24 @@ Task("BuildSettingsApp") }); - string finalSetupName = string.Empty; +string finalSetupNamex64 = string.Empty; +string finalSetupNameArm64 = string.Empty; Task("BuildInstaller") .IsDependentOn("SetupEnvironment") .IsDependentOn("BuildServiceAndAPI") .IsDependentOn("BuildApiActivationRegEntriesCSharp") -/* .IsDependentOn("BuildSDK") */ .IsDependentOn("BuildSettingsApp") .IsDependentOn("BuildConsoleApp") .DoesForEach(platformTargets, plat => { - /*var buildSettings = new MSBuildSettings - { - MaxCpuCount = 0, - Configuration = configuration, - AllowPreviewVersion = allowPreviewVersionOfBuildTools, - PlatformTarget = plat, - Verbosity = Verbosity.Minimal, - }; - - MSBuild(setupSolutionFile, buildSettings); */ - - // have to build these projects using dotnet build, or else the nuget references just die if you use MSBuild or VS - - //var apiProjectDir = System.IO.Path.Combine(setupSolutionDir, "api-package"); - //var apiProjectFile = System.IO.Path.Combine(apiProjectDir, "api-package.wixproj"); + Information($"\nBuilding Installer for {plat.ToString()} {configuration}"); var mainBundleProjectDir = System.IO.Path.Combine(setupSolutionDir, "main-bundle"); - + var apiSetupProjectDir = System.IO.Path.Combine(setupSolutionDir, "api-package"); var consoleOnlySetupProjectDir = System.IO.Path.Combine(setupSolutionDir, "console-package"); var settingsOnlySetupProjectDir = System.IO.Path.Combine(setupSolutionDir, "settings-package"); - //var mainBundleProjectFile = System.IO.Path.Combine(mainBundleProjectDir, "main-bundle.wixproj"); - - //var regActionsProjectDir = System.IO.Path.Combine(setupSolutionDir, "RegistryCustomActions"); - //var regActionsProjectFile = System.IO.Path.Combine(regActionsProjectDir, "RegistryCustomActions.csproj"); - - - - // if we don't set platform here, it always ends up as a 32 bit installer - // configuration - // buildSettings.MSBuildSettings.Properties["Platform"] = plat; - string rid = ""; if (plat == PlatformTarget.x64) rid = ridX64; @@ -648,10 +528,7 @@ Task("BuildInstaller") // // - string setupBuildMajorMinor = "1.0"; - string setupBuildDateNumber = DateTime.Now.ToString("yy") + DateTime.Now.DayOfYear.ToString("000"); // YYddd where ddd is the day number for the year - string setupBuildTimeNumber = DateTime.Now.ToString("HHmm"); // HHmm - string setupBuildPlatform = plat.ToString(); + string setupBuildPlatform = plat.ToString().ToLower(); string setupBuildFullVersionString = setupBuildMajorMinor + "." + setupBuildDateNumber + "." + setupBuildTimeNumber; //string setupBuildFullVersionStringFile = setupBuildFullVersionString.Replace('.', '-'); @@ -659,27 +536,35 @@ Task("BuildInstaller") using (StreamWriter writer = System.IO.File.CreateText(setupBundleInfoIncludeFile)) { writer.WriteLine(""); - writer.WriteLine($" "); + writer.WriteLine($" "); writer.WriteLine($" "); writer.WriteLine(""); } - finalSetupName = $"Windows MIDI Services {setupVersionName} {setupBuildPlatform} {setupBuildFullVersionString}.exe"; + var setupName = $"Windows MIDI Services {setupVersionName} {setupBuildFullVersionString}-{setupBuildPlatform}.exe"; + + if (plat == PlatformTarget.x64) + finalSetupNamex64 = setupName; + else if (plat == PlatformTarget.ARM64) + finalSetupNameArm64 = setupName; + + + string workingDirectory = string.Empty; - var buildSettings = new DotNetBuildSettings + var buildSettings = new MSBuildSettings { - WorkingDirectory = mainBundleProjectDir, - Configuration = configuration, - // Runtime = rid, // rid not supported for solution-level builds + MaxCpuCount = 1, /* Set to 1 to avoid file locking problems */ + Configuration = configuration, + AllowPreviewVersion = allowPreviewVersionOfBuildTools, + PlatformTarget = plat, + Verbosity = Verbosity.Minimal, }; - DotNetClean(setupSolutionFile); + MSBuild(setupSolutionFile, buildSettings); - // build the custom action first - DotNetBuild(setupSolutionFile, buildSettings); - - string releaseStandAloneInstallerFolder = System.IO.Path.Combine(setupReleaseDir, "Component Installs"); + + string releaseStandAloneInstallerFolder = System.IO.Path.Combine(setupReleaseDir, "Component Installs", plat.ToString().ToLower()); if (!DirectoryExists(setupReleaseDir)) CreateDirectory(setupReleaseDir); @@ -694,12 +579,11 @@ Task("BuildInstaller") // rename MoveFile(System.IO.Path.Combine(setupReleaseDir, "WindowsMidiServicesCompleteSetup.exe"), - (System.IO.Path.Combine(setupReleaseDir, finalSetupName))); + (System.IO.Path.Combine(setupReleaseDir, setupName))); CopyFiles(System.IO.Path.Combine(consoleOnlySetupProjectDir, "bin", plat.ToString(), "Release", "*.msi"), releaseStandAloneInstallerFolder); CopyFiles(System.IO.Path.Combine(settingsOnlySetupProjectDir, "bin", plat.ToString(), "Release", "*.msi"), releaseStandAloneInstallerFolder); - - + CopyFiles(System.IO.Path.Combine(apiSetupProjectDir, "bin", plat.ToString(), "Release", "*.msi"), releaseStandAloneInstallerFolder); }); @@ -707,22 +591,30 @@ Task("BuildInstaller") Task("CopyAPIArtifacts") .IsDependentOn("BuildInstaller") .IsDependentOn("PackAPIProjection") - .IsDependentOn("BuildConsoleApp") .DoesForEach(platformTargets, plat => { - string apiStagingDir = System.IO.Path.Combine(apiAndServiceStagingDir, "bin", plat.ToString()); + Information($"\nCopying API artifacts for {plat.ToString()} {configuration}"); + + string apiStagingDir = System.IO.Path.Combine(apiAndServiceStagingDir, "bin", plat.ToString().ToLower()); + string platReleaseFolder = System.IO.Path.Combine(apiReleaseArtifactsFolder, plat.ToString().ToLower()); + + if (!DirectoryExists(apiStagingDir)) + CreateDirectory(apiStagingDir); + + if (!DirectoryExists(platReleaseFolder)) + CreateDirectory(platReleaseFolder); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "Windows.Devices.Midi2.winmd"), apiReleaseArtifactsFolder); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "Windows.Devices.Midi2.dll"), apiReleaseArtifactsFolder); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "Windows.Devices.Midi2.pri"), apiReleaseArtifactsFolder); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "Windows.Devices.Midi2.h"), apiReleaseArtifactsFolder); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/base.h"), System.IO.Path.Combine(apiReleaseArtifactsFolder, "winrt/")); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/Windows.Devices.h"), System.IO.Path.Combine(apiReleaseArtifactsFolder, "winrt/")); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Devices.Enumeration.2.h"), System.IO.Path.Combine(apiReleaseArtifactsFolder, "winrt/impl/")); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Devices.Midi.2.h"), System.IO.Path.Combine(apiReleaseArtifactsFolder, "winrt/impl/")); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Foundation.2.h"), System.IO.Path.Combine(apiReleaseArtifactsFolder, "winrt/impl/")); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Foundation.Collections.2.h"), System.IO.Path.Combine(apiReleaseArtifactsFolder, "winrt/impl/")); - CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Devices.Midi2.2.h"), System.IO.Path.Combine(apiReleaseArtifactsFolder, "winrt/impl/")); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "Windows.Devices.Midi2.winmd"), platReleaseFolder); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "Windows.Devices.Midi2.dll"), platReleaseFolder); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "Windows.Devices.Midi2.pri"), platReleaseFolder); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "Windows.Devices.Midi2.h"), platReleaseFolder); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/base.h"), System.IO.Path.Combine(platReleaseFolder, "winrt/")); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/Windows.Devices.h"), System.IO.Path.Combine(platReleaseFolder, "winrt/")); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Devices.Enumeration.2.h"), System.IO.Path.Combine(platReleaseFolder, "winrt/impl/")); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Devices.Midi.2.h"), System.IO.Path.Combine(platReleaseFolder, "winrt/impl/")); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Foundation.2.h"), System.IO.Path.Combine(platReleaseFolder, "winrt/impl/")); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Foundation.Collections.2.h"), System.IO.Path.Combine(platReleaseFolder, "winrt/impl/")); + CopyFiles(System.IO.Path.Combine(apiStagingDir, "winrt/impl/Windows.Devices.Midi2.2.h"), System.IO.Path.Combine(platReleaseFolder, "winrt/impl/")); }); @@ -739,7 +631,10 @@ Task("Default") .Does(() => { - Information("\n\nInstaller >> \"" + finalSetupName + "\"\n\n"); + Information("\n\n"); + Information($"Installer x64 >> \"{finalSetupNamex64}\" \n"); + Information($"Installer Arm64 >> \"{finalSetupNameArm64}\"\n"); + Information("\n\n"); }); diff --git a/build/dependencies/dotnet8-windowsdesktop-runtime-latest-win-x64.exe b/build/dependencies/dotnet8-windowsdesktop-runtime-latest-win-x64.exe deleted file mode 100644 index bcf2b45b..00000000 Binary files a/build/dependencies/dotnet8-windowsdesktop-runtime-latest-win-x64.exe and /dev/null differ diff --git a/build/dependencies/wasdk/windowsappruntimeinstall-arm64.exe b/build/dependencies/wasdk/windowsappruntimeinstall-arm64.exe deleted file mode 100644 index 8c07c2a2..00000000 Binary files a/build/dependencies/wasdk/windowsappruntimeinstall-arm64.exe and /dev/null differ diff --git a/build/dependencies/wasdk/windowsappruntimeinstall-x64.exe b/build/dependencies/wasdk/windowsappruntimeinstall-x64.exe deleted file mode 100644 index 0410e5e2..00000000 Binary files a/build/dependencies/wasdk/windowsappruntimeinstall-x64.exe and /dev/null differ diff --git a/build/dependencies/windowsdesktop-runtime-8.0.0-win-arm64.exe b/build/dependencies/windowsdesktop-runtime-8.0.0-win-arm64.exe deleted file mode 100644 index faef508b..00000000 Binary files a/build/dependencies/windowsdesktop-runtime-8.0.0-win-arm64.exe and /dev/null differ diff --git a/build/dependencies/windowsdesktop-runtime-8.0.0-win-x64.exe b/build/dependencies/windowsdesktop-runtime-8.0.0-win-x64.exe deleted file mode 100644 index 19e4c0a8..00000000 Binary files a/build/dependencies/windowsdesktop-runtime-8.0.0-win-x64.exe and /dev/null differ diff --git a/build/dependencies/windowsdesktop-runtime-8.0.2-win-arm64.exe b/build/dependencies/windowsdesktop-runtime-8.0.2-win-arm64.exe deleted file mode 100644 index 6a78ff43..00000000 Binary files a/build/dependencies/windowsdesktop-runtime-8.0.2-win-arm64.exe and /dev/null differ diff --git a/build/dependencies/windowsdesktop-runtime-8.0.2-win-x64.exe b/build/dependencies/windowsdesktop-runtime-8.0.2-win-x64.exe deleted file mode 100644 index bcf2b45b..00000000 Binary files a/build/dependencies/windowsdesktop-runtime-8.0.2-win-x64.exe and /dev/null differ diff --git a/build/dependencies/x64 Package is for x64 and Arm64.txt b/build/dependencies/x64 Package is for x64 and Arm64.txt new file mode 100644 index 00000000..e69de29b diff --git a/build/replace_running_service.bat b/build/replace_running_service_x64.bat similarity index 100% rename from build/replace_running_service.bat rename to build/replace_running_service_x64.bat diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi index f6c1a335..2a7ecc0b 100644 --- a/build/staging/version/BundleInfo.wxi +++ b/build/staging/version/BundleInfo.wxi @@ -1,4 +1,4 @@ - - + + diff --git a/docs/data-translation.md b/docs/data-translation.md new file mode 100644 index 00000000..1db20dc3 --- /dev/null +++ b/docs/data-translation.md @@ -0,0 +1,34 @@ +--- +layout: page +title: Data Translation +parent: Windows Midi Services +--- + +# Data Translation + +In general, Windows MIDI Services translates MIDI messages only when it has to. This translation happens in two different places, depending upon the driver in use. + +Internally, the MIDI service moves messages around in the UMP format. This enables a standard format for message scheduling and processing. In addition, the `Windows.Devices.Midi2` API treats all messages as UMP, including ones to/from devices which were bytestream MIDI 1.0 format. + +## Translation Scenarios involving data format changes + +Windows MIDI Services supports MIDI 1.0 and MIDI 2.0 devices. + +| Device | Driver | Windows.Devices.Midi2 | Older MIDI 1.0 APIs | +| ------------------- | --------------------- | -------------------------- | ------------------------ | +| USB MIDI 1.0 Device | MIDI 2.0 Class Driver | To/From UMP by driver | To/from byte stream by service | +| USB MIDI 1.0 Device | Older MIDI 1.0 Class Driver | To/From UMP by service | To/from byte stream by service | +| USB MIDI 1.0 Device | Vendor MIDI 1.0 driver | To/From UMP by service | To/From byte stream by service | +| USB MIDI 2.0 Device | MIDI 2.0 Class Driver | No translation required | To/from byte stream by service | +| Any other MIDI 2.0 Device | (no driver. ex Virtual, Network 2.0) | No translation required | To/from byte stream by service | +| Any other MIDI 1.0 Device | (no driver. ex BLE) | To/From UMP by service | To/From byte stream by service | + +## Translation between Message type 2 (MIDI 1.0 Channel Voice) and Message type 4 (MIDI 2.0 Channel Voice) + +Currently, Windows MIDI Services does not translate messages based on negotiated protocol or Function Block declared protocol. Instead, for native UMP endpoints, applications should send the correct protocol messages (message type 2 for MIDI 1.0-compatible and message type 4 for MIDI 2.0-compatible messages) based upon the information provided by the `EndpointDeviceInformation` [and related enumeration types](developer-docs\Windows.Devices.Midi2\enumeration\README.md). In addition, for native bytestream endpoints, applications should send the appropriate MIDI 1.0 messages in UMP. + +## Resources for translation + +Windows MIDI Services makes use of publicly-available open source libraries for protocol and data format translation. + +* [midi2.dev](https://midi2.dev) contains a number of libraries which include translation. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index e4b99d1f..211fa9df 100644 --- a/docs/index.md +++ b/docs/index.md @@ -43,5 +43,5 @@ While we're in developer preview, get started by downloading and installing the These are the updated MIDI 2.0 specifications which apply to this project today. -* [MIDI 2.0 UMP Specifications](https://midi.org/specifications) +* [MIDI 2.0 UMP (and other) Specifications](https://midi.org/specs) 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 becf0f8f..efdc9e88 100644 --- a/samples/cpp-winrt/api-client-basics/api-client-basics.vcxproj +++ b/samples/cpp-winrt/api-client-basics/api-client-basics.vcxproj @@ -1,7 +1,7 @@ - - + + true true @@ -149,16 +149,16 @@ - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + \ No newline at end of file diff --git a/samples/cpp-winrt/api-client-basics/packages.config b/samples/cpp-winrt/api-client-basics/packages.config index 4773d392..9fca0dc2 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-send-speed/api-client-send-speed.vcxproj b/samples/cpp-winrt/api-client-send-speed/api-client-send-speed.vcxproj index f48a4bea..3fd49fa4 100644 --- a/samples/cpp-winrt/api-client-send-speed/api-client-send-speed.vcxproj +++ b/samples/cpp-winrt/api-client-send-speed/api-client-send-speed.vcxproj @@ -1,7 +1,7 @@ - - + + true true @@ -149,16 +149,16 @@ - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + \ No newline at end of file diff --git a/samples/cpp-winrt/api-client-send-speed/packages.config b/samples/cpp-winrt/api-client-send-speed/packages.config index 4773d392..9fca0dc2 100644 --- a/samples/cpp-winrt/api-client-send-speed/packages.config +++ b/samples/cpp-winrt/api-client-send-speed/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/samples/cpp-winrt/api-enum-endpoints/api-enum-endpoints-cpp.vcxproj b/samples/cpp-winrt/api-enum-endpoints/api-enum-endpoints-cpp.vcxproj index 3cb00f56..ff6edb65 100644 --- a/samples/cpp-winrt/api-enum-endpoints/api-enum-endpoints-cpp.vcxproj +++ b/samples/cpp-winrt/api-enum-endpoints/api-enum-endpoints-cpp.vcxproj @@ -1,7 +1,7 @@ - - + + true true @@ -149,16 +149,16 @@ - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + \ No newline at end of file diff --git a/samples/cpp-winrt/api-enum-endpoints/packages.config b/samples/cpp-winrt/api-enum-endpoints/packages.config index 4773d392..9fca0dc2 100644 --- a/samples/cpp-winrt/api-enum-endpoints/packages.config +++ b/samples/cpp-winrt/api-enum-endpoints/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/samples/cpp-winrt/api-loopback-endpoints/api-loopback-endpoints.vcxproj b/samples/cpp-winrt/api-loopback-endpoints/api-loopback-endpoints.vcxproj index 872ea4ca..325e5e67 100644 --- a/samples/cpp-winrt/api-loopback-endpoints/api-loopback-endpoints.vcxproj +++ b/samples/cpp-winrt/api-loopback-endpoints/api-loopback-endpoints.vcxproj @@ -1,7 +1,7 @@ - - + + true true @@ -111,16 +111,16 @@ - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + \ No newline at end of file diff --git a/samples/cpp-winrt/api-loopback-endpoints/main.cpp b/samples/cpp-winrt/api-loopback-endpoints/main.cpp index fe0312cd..4c9916a7 100644 --- a/samples/cpp-winrt/api-loopback-endpoints/main.cpp +++ b/samples/cpp-winrt/api-loopback-endpoints/main.cpp @@ -31,11 +31,11 @@ bool CreateLoopbackEndpoints() MidiServiceLoopbackEndpointDefinition definitionA; MidiServiceLoopbackEndpointDefinition definitionB; - definitionA.Name(L"My App Loopback A"); + definitionA.Name(L"Sample App Loopback A"); definitionA.Description(L"The first description is optional, but is displayed to users. This becomes the transport-defined description."); definitionA.UniqueId(L"8675309-OU812-5150"); - definitionB.Name(L"My App Loopback B"); + definitionB.Name(L"Sample App Loopback B"); definitionB.Description(L"The second description is optional, but is displayed to users. This becomes the transport-defined description."); definitionB.UniqueId(L"3263827-OU812-5150"); // can be the same as the first one, but doesn't need to be. @@ -45,20 +45,31 @@ bool CreateLoopbackEndpoints() definitionB ); - if (response.Success()) { - std::wcout << L"Endpoint A: " << response.EndpointDeviceIdA().c_str() << std::endl; - std::wcout << L"Endpoint B: " << response.EndpointDeviceIdB().c_str() << std::endl; + std::wcout << L"Endpoints created successfully" << std::endl << std::endl; + + std::cout + << "Loopback Endpoint A: " << std::endl + << " - " << winrt::to_string(definitionA.Name()) << std::endl + << " - " << winrt::to_string(response.EndpointDeviceIdA()) << std::endl << std::endl; + + std::cout + << "Loopback Endpoint B: " << std::endl + << " - " << winrt::to_string(definitionB.Name()) << std::endl + << " - " << winrt::to_string(response.EndpointDeviceIdB()) << std::endl << std::endl; m_endpointAId = response.EndpointDeviceIdA(); m_endpointBId = response.EndpointDeviceIdB(); } else { - std::cout << "Failed to create loopback endpoints." << std::endl; + std::cout << "Failed to create loopback endpoints." << std::endl; + std::cout << "This can happen if you control-C or crash out of the sample before the loopbacks are removed." << std::endl; + std::cout << "If that's the case, restart MidiSrv, or change the unique Ids above." << std::endl; } + // Success here is a boolean for success/fail return response.Success(); } @@ -73,9 +84,7 @@ int main() std::cout << std::endl << "Creating session..." << std::endl; - auto session = MidiSession::CreateSession(L"Sample Session"); - - + auto session = MidiSession::CreateSession(L"Loopback Sample Session"); if (CreateLoopbackEndpoints()) { @@ -178,14 +187,16 @@ int main() // remove the loopback endpoints + // If you don't do this, they will stay active, and the next attempt + // to create them will fail because the unique Ids are already in use if (MidiService::RemoveTemporaryLoopbackEndpoints(m_associationId)) { - std::cout << "Endpoints removed." << std::endl; + std::cout << "Loopback endpoints removed." << std::endl; } else { - std::cout << "There was a problem removing the endpoints." << std::endl; + std::cout << "There was a problem removing the endpoints. You may want to restart the service." << std::endl; } } } diff --git a/samples/cpp-winrt/api-loopback-endpoints/packages.config b/samples/cpp-winrt/api-loopback-endpoints/packages.config index 4773d392..9fca0dc2 100644 --- a/samples/cpp-winrt/api-loopback-endpoints/packages.config +++ b/samples/cpp-winrt/api-loopback-endpoints/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file diff --git a/samples/cpp-winrt/api-watch-endpoints/api-watch-endpoints-cpp.vcxproj b/samples/cpp-winrt/api-watch-endpoints/api-watch-endpoints-cpp.vcxproj index 0868a2fb..d7c3a42d 100644 --- a/samples/cpp-winrt/api-watch-endpoints/api-watch-endpoints-cpp.vcxproj +++ b/samples/cpp-winrt/api-watch-endpoints/api-watch-endpoints-cpp.vcxproj @@ -1,7 +1,7 @@ - - + + true true @@ -149,16 +149,16 @@ - - + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + \ No newline at end of file diff --git a/samples/cpp-winrt/api-watch-endpoints/main.cpp b/samples/cpp-winrt/api-watch-endpoints/main.cpp index 4ef10b39..90667ad4 100644 --- a/samples/cpp-winrt/api-watch-endpoints/main.cpp +++ b/samples/cpp-winrt/api-watch-endpoints/main.cpp @@ -30,13 +30,13 @@ int main() std::cout << "Enumerating endpoints..." << std::endl; - auto filter = MidiEndpointDeviceInformationFilter::IncludeClientByteStreamNative | - MidiEndpointDeviceInformationFilter::IncludeClientUmpNative | - MidiEndpointDeviceInformationFilter::IncludeVirtualDeviceResponder; + auto filter = MidiEndpointDeviceInformationFilters::IncludeClientByteStreamNative | + MidiEndpointDeviceInformationFilters::IncludeClientUmpNative | + MidiEndpointDeviceInformationFilters::IncludeVirtualDeviceResponder; if (includeDiagnosticsEndpoints) { - filter |= MidiEndpointDeviceInformationFilter::IncludeDiagnosticLoopback; + filter |= MidiEndpointDeviceInformationFilters::IncludeDiagnosticLoopback; } auto watcher = MidiEndpointDeviceWatcher::CreateWatcher(filter); @@ -55,25 +55,34 @@ int main() std::cout << "Initial enumeration completed." << std::endl; }; - auto OnWatcherDeviceRemoved = [&](MidiEndpointDeviceWatcher const& /*sender*/, winrt::Windows::Devices::Enumeration::DeviceInformationUpdate const& args) + auto OnWatcherDeviceRemoved = [&](MidiEndpointDeviceWatcher const& /*sender*/, MidiEndpointDeviceInformationRemovedEventArgs const& args) { std::cout << std::endl; std::cout << "Removed: " << winrt::to_string(args.Id()) << std::endl; }; - auto OnWatcherDeviceUpdated = [&](MidiEndpointDeviceWatcher const& /*sender*/, MidiEndpointDeviceInformationUpdateEventArgs const& args) + auto OnWatcherDeviceUpdated = [&](MidiEndpointDeviceWatcher const& /*sender*/, MidiEndpointDeviceInformationUpdatedEventArgs const& args) { std::cout << std::endl; std::cout << "Updated: " << winrt::to_string(args.Id()) << std::endl; - // TODO: Show how to use the various data update flags here + // Show how to use the various data update flags here + + if (args.UpdatedName()) std::cout << "- Name" << std::endl; + if (args.UpdatedUserMetadata()) std::cout << "- User Metadata" << std::endl; + if (args.UpdatedEndpointInformation()) std::cout << "- Endpoint Information" << std::endl; + if (args.UpdatedStreamConfiguration()) std::cout << "- Stream Configuration" << std::endl; + if (args.UpdatedFunctionBlocks()) std::cout << "- Function Blocks" << std::endl; + if (args.UpdatedDeviceIdentity()) std::cout << "- Device Identity" << std::endl; + if (args.UpdatedAdditionalCapabilities()) std::cout << "- Additional Capabilities" << std::endl; + }; - auto OnWatcherDeviceAdded = [&](MidiEndpointDeviceWatcher const& /*sender*/, MidiEndpointDeviceInformation const& args) + auto OnWatcherDeviceAdded = [&](MidiEndpointDeviceWatcher const& /*sender*/, MidiEndpointDeviceInformationAddedEventArgs const& args) { std::cout << std::endl; - std::cout << "Added : " << winrt::to_string(args.Name()) << std::endl; - std::cout << " " << winrt::to_string(args.Id()) << std::endl ; + std::cout << "Added : " << winrt::to_string(args.AddedDevice().Name()) << std::endl; + std::cout << " " << winrt::to_string(args.AddedDevice().Id()) << std::endl ; }; diff --git a/samples/cpp-winrt/api-watch-endpoints/packages.config b/samples/cpp-winrt/api-watch-endpoints/packages.config index 4ec483f0..9fca0dc2 100644 --- a/samples/cpp-winrt/api-watch-endpoints/packages.config +++ b/samples/cpp-winrt/api-watch-endpoints/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file 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 36132b22..64f73a3e 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 @@ -2,7 +2,7 @@ Exe - net7.0-windows10.0.20348.0 + net8.0-windows10.0.22621.0 sdk_client_basics_cs enable enable @@ -11,7 +11,7 @@ - + diff --git a/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi.csproj b/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi.csproj index 86555c4d..6a3d2b1b 100644 --- a/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi.csproj +++ b/samples/csharp-net/app-to-app-midi-cs/MidiSample.AppToAppMidi.csproj @@ -1,7 +1,7 @@ WinExe - net8.0-windows10.0.20348.0 + net8.0-windows10.0.22621.0 10.0.20348.0 MidiSample.AppToAppMidi app.manifest @@ -27,9 +27,9 @@ - + - + diff --git a/src/api/Abstraction/BleMidiAbstraction/AbstractionState.cpp b/src/api/Abstraction/BleMidiAbstraction/AbstractionState.cpp new file mode 100644 index 00000000..3815b3c2 --- /dev/null +++ b/src/api/Abstraction/BleMidiAbstraction/AbstractionState.cpp @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#include "pch.h" + + +AbstractionState::AbstractionState() = default; +AbstractionState::~AbstractionState() = default; + +AbstractionState& AbstractionState::Current() +{ + // explanation: http://www.modernescpp.com/index.php/thread-safe-initialization-of-data/ + + static AbstractionState current; + + return current; +} + + + +HRESULT +AbstractionState::ConstructEndpointManager() +{ + RETURN_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&m_endpointManager)); + + return S_OK; +} + + +HRESULT +AbstractionState::ConstructConfigurationManager() +{ + RETURN_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&m_configurationManager)); + + return S_OK; +} diff --git a/src/api/Abstraction/BleMidiAbstraction/AbstractionState.h b/src/api/Abstraction/BleMidiAbstraction/AbstractionState.h new file mode 100644 index 00000000..09d2fa72 --- /dev/null +++ b/src/api/Abstraction/BleMidiAbstraction/AbstractionState.h @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + + +#pragma once + +// singleton +class AbstractionState +{ + +public: + static AbstractionState& Current(); + + // no copying + AbstractionState(_In_ const AbstractionState&) = delete; + AbstractionState& operator=(_In_ const AbstractionState&) = delete; + + + wil::com_ptr GetEndpointManager() + { + return m_endpointManager; + } + + wil::com_ptr GetConfigurationManager() + { + return m_configurationManager; + } + + HRESULT Cleanup() + { + m_endpointManager.reset(); + m_configurationManager.reset(); + + return S_OK; + } + + + HRESULT ConstructEndpointManager(); + HRESULT ConstructConfigurationManager(); + + +private: + AbstractionState(); + ~AbstractionState(); + + + wil::com_ptr m_endpointManager; + wil::com_ptr m_configurationManager; + + //std::shared_ptr m_endpointTable = std::make_shared(); +}; \ No newline at end of file diff --git a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.cpp b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.cpp index bf59609f..9e3c363a 100644 --- a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.cpp +++ b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.cpp @@ -20,14 +20,12 @@ CMidi2BluetoothMidiAbstraction::Activate( if (__uuidof(IMidiBiDi) == Riid) { - OutputDebugString(L"" __FUNCTION__ " Activating IMidiBiDi"); - TraceLoggingWrite( MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), - __FUNCTION__ "- IMidiBiDi", + __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingValue(__FUNCTION__), - TraceLoggingPointer(this, "this") + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"IMidiBiDi", "interface") ); wil::com_ptr_nothrow midiBiDi; @@ -35,37 +33,52 @@ CMidi2BluetoothMidiAbstraction::Activate( *Interface = midiBiDi.detach(); } - - // IMidiEndpointManager and IMidiApiEndpointManagerExtension are interfaces implemented by the same class - // We want to make sure we're always returning the same instance for these calls else if (__uuidof(IMidiEndpointManager) == Riid) { TraceLoggingWrite( MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), - __FUNCTION__ "- IMidiEndpointManager", + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"IMidiEndpointManager", "interface") + ); + + // check to see if this is the first time we're creating the endpoint manager. If so, create it. + if (AbstractionState::Current().GetEndpointManager() == nullptr) + { + AbstractionState::Current().ConstructEndpointManager(); + } + + RETURN_IF_FAILED(AbstractionState::Current().GetEndpointManager()->QueryInterface(Riid, Interface)); + } + + else if (__uuidof(IMidiAbstractionConfigurationManager) == Riid) + { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingValue(__FUNCTION__), - TraceLoggingPointer(this, "this") + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"IMidiAbstractionConfigurationManager", "interface") ); // check to see if this is the first time we're creating the endpoint manager. If so, create it. - if (m_EndpointManager == nullptr) + if (AbstractionState::Current().GetConfigurationManager() == nullptr) { - RETURN_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&m_EndpointManager)); + AbstractionState::Current().ConstructConfigurationManager(); } - // TODO: Not sure if this is the right pattern for this or not. There's no detach call here, so does this leak? - RETURN_IF_FAILED(m_EndpointManager->QueryInterface(Riid, Interface)); + RETURN_IF_FAILED(AbstractionState::Current().GetConfigurationManager()->QueryInterface(Riid, Interface)); } else if (__uuidof(IMidiServiceAbstractionPluginMetadataProvider) == Riid) { TraceLoggingWrite( MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), - __FUNCTION__ "- IMidiServiceAbstractionPluginMetadataProvider", + __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingValue(__FUNCTION__), - TraceLoggingPointer(this, "this") + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"IMidiServiceAbstractionPluginMetadataProvider", "interface") ); wil::com_ptr_nothrow metadataProvider; @@ -73,10 +86,15 @@ CMidi2BluetoothMidiAbstraction::Activate( *Interface = metadataProvider.detach(); } - else { - OutputDebugString(L"" __FUNCTION__ " Returning E_NOINTERFACE. Was an interface added that isn't handled in the Abstraction?"); + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Returning E_NOINTERFACE. Was an interface added that isn't handled in the Abstraction?", "message") + ); return E_NOINTERFACE; } diff --git a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.h b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.h index 64076c4e..d68b0620 100644 --- a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.h +++ b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.h @@ -41,7 +41,6 @@ class ATL_NO_VTABLE CMidi2BluetoothMidiAbstraction : STDMETHOD(Activate)(_In_ REFIID, _Out_ void**); private: - wil::com_ptr_nothrow m_EndpointManager; }; diff --git a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.rc b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.rc index 8062828a..a2da001d 100644 --- a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.rc +++ b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.rc @@ -100,11 +100,11 @@ END STRINGTABLE BEGIN - IDS_PROJNAME "Midi2.BluetoothMidiAbstraction" + IDS_PROJNAME "Midi2.BluetoothMidiAbstraction" IDS_PLUGIN_METADATA_VERSION "1.0.0.0" IDS_PLUGIN_METADATA_NAME "Bluetooth MIDI 1.0" - IDS_PLUGIN_METADATA_DESCRIPTION "(Currently in Development) Provides Bluetooth MIDI 1.0 (BLE) device support for Windows MIDI Services" + IDS_PLUGIN_METADATA_DESCRIPTION "Provides Bluetooth MIDI 1.0 (BLE) device support for Windows MIDI Services" IDS_PLUGIN_METADATA_AUTHOR "Microsoft" END diff --git a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.vcxproj b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.vcxproj index aed6a053..dff05548 100644 --- a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.vcxproj +++ b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.vcxproj @@ -274,6 +274,7 @@ + @@ -293,15 +294,16 @@ + + - diff --git a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.vcxproj.filters b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.vcxproj.filters index 470c4153..f4935452 100644 --- a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.vcxproj.filters +++ b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiAbstraction.vcxproj.filters @@ -42,6 +42,9 @@ Source Files + + Source Files + @@ -88,9 +91,6 @@ Header Files - - Header Files - Header Files @@ -100,6 +100,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiEndpointManager.cpp b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiEndpointManager.cpp index 06b73baf..d3acfb1b 100644 --- a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiEndpointManager.cpp +++ b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiEndpointManager.cpp @@ -20,16 +20,6 @@ GUID AbstractionLayerGUID = ABSTRACTION_LAYER_GUID; -#define MIDI_BLE_SERVICE_CHARACTERISTIC L"{03B80E5A-EDE8-4B33-A751-6CE34EC4C700}" -#define MIDI_BLE_DATA_IO_CHARACTERISTIC L"{7772E5DB-3868-4112-A1A9-F2669D106BF3}" -// Notes: -// Write (encryption recommended, write without response is required) -// Read (encryption recommended, respond with no payload) -// Notify (encryption recommended) -// Max connection interval is 15ms. Lower is better. - - - _Use_decl_annotations_ HRESULT CMidi2BluetoothMidiEndpointManager::Initialize( @@ -37,65 +27,107 @@ CMidi2BluetoothMidiEndpointManager::Initialize( IUnknown* /*midiEndpointProtocolManager*/ ) { - TraceLoggingWrite( - MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), - __FUNCTION__, - TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingPointer(this, "this") - ); - RETURN_HR_IF(E_INVALIDARG, nullptr == MidiDeviceManager); + // temporary because this may be crashing the service on start + return S_OK; - RETURN_IF_FAILED(MidiDeviceManager->QueryInterface(__uuidof(IMidiDeviceManagerInterface), (void**)&m_MidiDeviceManager)); - m_TransportAbstractionId = AbstractionLayerGUID; // this is needed so MidiSrv can instantiate the correct transport - m_ContainerId = m_TransportAbstractionId; // we use the transport ID as the container ID for convenience - winrt::hstring deviceSelector = bt::BluetoothLEDevice::GetDeviceSelector(); - auto requestedProperties = winrt::single_threaded_vector( - { - L"System.DeviceInterface.Bluetooth.DeviceAddress", - L"System.DeviceInterface.Bluetooth.Flags", - L"System.DeviceInterface.Bluetooth.LastConnectedTime", - L"System.DeviceInterface.Bluetooth.Manufacturer", - L"System.DeviceInterface.Bluetooth.ModelNumber", - L"System.DeviceInterface.Bluetooth.ProductId", - L"System.DeviceInterface.Bluetooth.ProductVersion", - L"System.DeviceInterface.Bluetooth.ServiceGuid", - L"System.DeviceInterface.Bluetooth.VendorId", - L"System.DeviceInterface.Bluetooth.VendorIdSource", - L"System.Devices.Connected", - L"System.Devices.DeviceCapabilities", - L"System.Devices.DeviceCharacteristics" - } - ); + try + { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + RETURN_HR_IF_NULL(E_INVALIDARG, MidiDeviceManager); - m_Watcher = enumeration::DeviceInformation::CreateWatcher(deviceSelector, requestedProperties); + RETURN_IF_FAILED(MidiDeviceManager->QueryInterface(__uuidof(IMidiDeviceManagerInterface), (void**)&m_MidiDeviceManager)); - auto deviceAddedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnDeviceAdded); - auto deviceRemovedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnDeviceRemoved); - auto deviceUpdatedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnDeviceUpdated); - auto deviceStoppedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnDeviceStopped); - auto deviceEnumerationCompletedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnEnumerationCompleted); + m_TransportAbstractionId = AbstractionLayerGUID; // this is needed so MidiSrv can instantiate the correct transport + m_ContainerId = m_TransportAbstractionId; // we use the transport ID as the container ID for convenience - m_DeviceAdded = m_Watcher.Added(winrt::auto_revoke, deviceAddedHandler); - m_DeviceRemoved = m_Watcher.Removed(winrt::auto_revoke, deviceRemovedHandler); - m_DeviceUpdated = m_Watcher.Updated(winrt::auto_revoke, deviceUpdatedHandler); - m_DeviceStopped = m_Watcher.Stopped(winrt::auto_revoke, deviceStoppedHandler); - m_DeviceEnumerationCompleted = m_Watcher.EnumerationCompleted(winrt::auto_revoke, deviceEnumerationCompletedHandler); + RETURN_IF_FAILED(CreateParentDevice()); - m_Watcher.Start(); + RETURN_IF_FAILED(StartAdvertisementWatcher()); + //RETURN_IF_FAILED(StartDeviceWatcher()); - return S_OK; + return S_OK; + } + catch (std::exception ex) + { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingString(ex.what(), "exception"), + TraceLoggingWideString(L"Exception initializing BLE MIDI Endpoint Manager", "message") + ); + + return E_FAIL; + } + catch (...) + { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Unknown exception initializing BLE MIDI Endpoint Manager", "message") + ); + + return E_FAIL; + } } HRESULT CMidi2BluetoothMidiEndpointManager::CreateParentDevice() { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + // the parent device parameters are set by the transport (this) + std::wstring parentDeviceName{ TRANSPORT_PARENT_DEVICE_NAME }; + std::wstring parentDeviceId{ internal::NormalizeDeviceInstanceIdWStringCopy(TRANSPORT_PARENT_ID) }; + + SW_DEVICE_CREATE_INFO createInfo = {}; + createInfo.cbSize = sizeof(createInfo); + createInfo.pszInstanceId = parentDeviceId.c_str(); + createInfo.CapabilityFlags = SWDeviceCapabilitiesNone; + createInfo.pszDeviceDescription = parentDeviceName.c_str(); + createInfo.pContainerId = &m_ContainerId; + + const ULONG deviceIdMaxSize = 255; + wchar_t newDeviceId[deviceIdMaxSize]{ 0 }; + + RETURN_IF_FAILED(m_MidiDeviceManager->ActivateVirtualParentDevice( + 0, + nullptr, + &createInfo, + (PWSTR)newDeviceId, + deviceIdMaxSize + )); + + m_parentDeviceId = internal::NormalizeDeviceInstanceIdWStringCopy(newDeviceId); + + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(newDeviceId, "New parent device instance id") + ); return S_OK; } @@ -104,12 +136,122 @@ CMidi2BluetoothMidiEndpointManager::CreateParentDevice() _Use_decl_annotations_ HRESULT CMidi2BluetoothMidiEndpointManager::CreateEndpoint( - MidiBluetoothDeviceDefinition& definition + MidiBluetoothDeviceDefinition* definition ) { - UNREFERENCED_PARAMETER(definition); + RETURN_HR_IF_NULL_MSG(E_INVALIDARG, definition, "Empty endpoint definition"); + + //RETURN_HR_IF_MSG(E_INVALIDARG, definition->EndpointName.empty(), "Empty endpoint name"); + //RETURN_HR_IF_MSG(E_INVALIDARG, definition->InstanceIdPrefix.empty(), "Empty endpoint prefix"); + //RETURN_HR_IF_MSG(E_INVALIDARG, definition->EndpointUniqueIdentifier.empty(), "Empty endpoint unique id"); + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(definition->BluetoothAddress, "bluetooth address") + ); + + std::wstring mnemonic(TRANSPORT_MNEMONIC); + + // TODO: Need to fold in user data here + std::wstring endpointName = definition->TransportSuppliedName.c_str(); + std::wstring endpointDescription = definition->TransportSuppliedDescription.c_str(); + + std::vector interfaceDeviceProperties{}; + + // no user or in-protocol data in this case + std::wstring friendlyName = internal::CalculateEndpointDevicePrimaryName(endpointName, L"", L""); + + + bool requiresMetadataHandler = false; + bool multiClient = true; + bool generateIncomingTimestamps = true; + std::wstring uniqueIdentifier = std::to_wstring(definition->BluetoothAddress); + // Device properties + + + SW_DEVICE_CREATE_INFO createInfo = {}; + createInfo.cbSize = sizeof(createInfo); + + // build the instance id, which becomes the middle of the SWD id + std::wstring instanceId = internal::NormalizeDeviceInstanceIdWStringCopy( + MIDI_BLE_INSTANCE_ID_PREFIX + + uniqueIdentifier); + + createInfo.pszInstanceId = instanceId.c_str(); + createInfo.CapabilityFlags = SWDeviceCapabilitiesNone; + createInfo.pszDeviceDescription = friendlyName.c_str(); + + const ULONG deviceInterfaceIdMaxSize = 255; + wchar_t newDeviceInterfaceId[deviceInterfaceIdMaxSize]{ 0 }; + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(instanceId.c_str(), "instance id"), + TraceLoggingWideString(L"Activating endpoint", "message") + ); + + MIDIENDPOINTCOMMONPROPERTIES commonProperties{}; + commonProperties.AbstractionLayerGuid = m_TransportAbstractionId; + commonProperties.EndpointPurpose = MidiEndpointDevicePurposePropertyValue::NormalMessageEndpoint; + commonProperties.FriendlyName = friendlyName.c_str(); + commonProperties.TransportMnemonic = mnemonic.c_str(); + commonProperties.TransportSuppliedEndpointName = endpointName.c_str(); + commonProperties.TransportSuppliedEndpointDescription = endpointDescription.c_str(); + commonProperties.UserSuppliedEndpointName = nullptr; + commonProperties.UserSuppliedEndpointDescription = nullptr; + commonProperties.UniqueIdentifier = uniqueIdentifier.c_str(); + commonProperties.SupportedDataFormats = MidiDataFormat::MidiDataFormat_ByteStream; + commonProperties.NativeDataFormat = MIDI_PROP_NATIVEDATAFORMAT_BYTESTREAM; + commonProperties.SupportsMultiClient = multiClient; + commonProperties.RequiresMetadataHandler = requiresMetadataHandler; + commonProperties.GenerateIncomingTimestamps = generateIncomingTimestamps; + commonProperties.SupportsMidi1ProtocolDefaultValue = true; + commonProperties.SupportsMidi2ProtocolDefaultValue = false; + + if (definition->ManufacturerName.empty()) + { + commonProperties.ManufacturerName = nullptr; + } + else + { + commonProperties.ManufacturerName = definition->ManufacturerName.c_str(); + } + + RETURN_IF_FAILED(m_MidiDeviceManager->ActivateEndpoint( + (PCWSTR)m_parentDeviceId.c_str(), // parent instance Id + true, // UMP-only + MidiFlow::MidiFlowBidirectional, // MIDI Flow is bidirectional for a BLE MIDI 1.0 device + &commonProperties, + (ULONG)interfaceDeviceProperties.size(), + (ULONG)0, + (PVOID)interfaceDeviceProperties.data(), + (PVOID)nullptr, + (PVOID)&createInfo, + (LPWSTR)&newDeviceInterfaceId, + deviceInterfaceIdMaxSize)); + + // we need this for removal later + definition->CreatedMidiDeviceInstanceId = instanceId; + definition->CreatedEndpointInterfaceId = internal::NormalizeEndpointInterfaceIdWStringCopy(newDeviceInterfaceId); + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(newDeviceInterfaceId, "new device interface id"), + TraceLoggingWideString(instanceId.c_str(), "instance id"), + TraceLoggingWideString(uniqueIdentifier.c_str(), "unique identifier"), + TraceLoggingWideString(L"Endpoint activated", "message") + ); return S_OK; } @@ -127,6 +269,22 @@ CMidi2BluetoothMidiEndpointManager::Cleanup() TraceLoggingPointer(this, "this") ); + if (m_bleAdWatcher != nullptr) + { + m_bleAdWatcher.Stop(); + m_bleAdWatcher = nullptr; + + m_AdvertisementReceived.revoke(); + m_AdvertisementWatcherStopped.revoke(); + } + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Cleanup complete", "message") + ); return S_OK; } @@ -138,24 +296,60 @@ CMidi2BluetoothMidiEndpointManager::Cleanup() _Use_decl_annotations_ HRESULT -CMidi2BluetoothMidiEndpointManager::OnDeviceAdded(enumeration::DeviceWatcher, enumeration::DeviceInformation) +CMidi2BluetoothMidiEndpointManager::OnDeviceAdded(enumeration::DeviceWatcher /*source*/, enumeration::DeviceInformation /*args*/) { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); return S_OK; } _Use_decl_annotations_ HRESULT -CMidi2BluetoothMidiEndpointManager::OnDeviceRemoved(enumeration::DeviceWatcher, enumeration::DeviceInformationUpdate) +CMidi2BluetoothMidiEndpointManager::OnDeviceRemoved(enumeration::DeviceWatcher /*source*/, enumeration::DeviceInformationUpdate /*args*/) { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + + return S_OK; +} + +_Use_decl_annotations_ +HRESULT +CMidi2BluetoothMidiEndpointManager::OnDeviceUpdated(enumeration::DeviceWatcher /*source*/, enumeration::DeviceInformationUpdate /*args*/) +{ + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + return S_OK; } + _Use_decl_annotations_ HRESULT -CMidi2BluetoothMidiEndpointManager::OnDeviceUpdated(enumeration::DeviceWatcher, enumeration::DeviceInformationUpdate) +CMidi2BluetoothMidiEndpointManager::OnDeviceStopped(enumeration::DeviceWatcher /*source*/, foundation::IInspectable /*args*/) { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + return S_OK; } @@ -163,17 +357,402 @@ CMidi2BluetoothMidiEndpointManager::OnDeviceUpdated(enumeration::DeviceWatcher, _Use_decl_annotations_ HRESULT -CMidi2BluetoothMidiEndpointManager::OnDeviceStopped(enumeration::DeviceWatcher, foundation::IInspectable) +CMidi2BluetoothMidiEndpointManager::OnEnumerationCompleted(enumeration::DeviceWatcher /*source*/, foundation::IInspectable /*args*/) +{ + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + + return S_OK; +} + + + + +// TODO: This likely isn't the correct event signature + +_Use_decl_annotations_ +HRESULT +CMidi2BluetoothMidiEndpointManager::OnBleDeviceNameChanged(bt::BluetoothLEDevice device, foundation::IInspectable /*args*/) { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + return S_OK; } _Use_decl_annotations_ +HRESULT +CMidi2BluetoothMidiEndpointManager::OnBleDeviceConnectionStatusChanged(bt::BluetoothLEDevice device, foundation::IInspectable /*args*/) +{ + bool connected = (device.ConnectionStatus() == bt::BluetoothConnectionStatus::Connected); + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(device.BluetoothAddress(), "address"), + TraceLoggingWideString(device.DeviceId().c_str(), "device id"), + TraceLoggingWideString(device.Name().c_str(), "name"), + TraceLoggingBool(connected, "is connected") + ); + + // TODO: When the device was first seen by the service, it may have already been connected, so + // we need to handle that even though we won't have a connection status changed event + + auto entry = MidiEndpointTable::Current().GetEndpointEntryForBluetoothAddress(device.BluetoothAddress()); + + if (entry == nullptr) + { + // this device is new to us. This should never happen + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(device.BluetoothAddress(), "address"), + TraceLoggingWideString(device.DeviceId().c_str(), "device id"), + TraceLoggingWideString(device.Name().c_str(), "name"), + TraceLoggingBool(connected, "is connected"), + TraceLoggingWideString(L"Received a connection changed notification for a device we didn't have recorded.", "message") + ); + + return E_FAIL; + } + + if (connected) + { + // Connected. check to see if we've already created the SWD. If not, create it. + if (entry->Definition.CreatedEndpointInterfaceId.empty()) + { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(device.BluetoothAddress(), "address"), + TraceLoggingWideString(device.DeviceId().c_str(), "device id"), + TraceLoggingWideString(device.Name().c_str(), "name"), + TraceLoggingBool(connected, "is connected"), + TraceLoggingWideString(L"Creating new SWD / endpoint", "message") + ); + + RETURN_IF_FAILED(CreateEndpoint(&(entry->Definition))); + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(device.BluetoothAddress(), "address"), + TraceLoggingWideString(device.DeviceId().c_str(), "device id"), + TraceLoggingWideString(device.Name().c_str(), "name"), + TraceLoggingBool(connected, "is connected"), + TraceLoggingWideString(L"Endpoint created", "message") + ); + } + else + { + // we've already created this device and it is currently connected. This shouldn't happen. + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_WARNING), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(device.BluetoothAddress(), "address"), + TraceLoggingWideString(device.DeviceId().c_str(), "device id"), + TraceLoggingWideString(device.Name().c_str(), "name"), + TraceLoggingBool(connected, "is connected"), + TraceLoggingWideString(L"The last state we captured was that this device was connected. Shouldn't have received a change notification.", "message") + ); + } + } + else + { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(device.BluetoothAddress(), "address"), + TraceLoggingWideString(device.DeviceId().c_str(), "device id"), + TraceLoggingWideString(device.Name().c_str(), "name"), + TraceLoggingBool(connected, "is connected"), + TraceLoggingWideString(L"Deactivating SWD", "message") + ); + + // Disconnected. Remove the SWD. + + std::wstring instanceId = entry->Definition.CreatedMidiDeviceInstanceId; + + if (!instanceId.empty()) + { + RETURN_IF_FAILED(m_MidiDeviceManager->DeactivateEndpoint(instanceId.c_str())); + + entry->Definition.SetDeactivated(); + entry->MidiDeviceBiDi = nullptr; + } + else + { + // TODO Log + } + } + + return S_OK; +} + + + + +_Use_decl_annotations_ +HRESULT +CMidi2BluetoothMidiEndpointManager::OnBleAdvertisementReceived( + ad::BluetoothLEAdvertisementWatcher /*source*/, + ad::BluetoothLEAdvertisementReceivedEventArgs const& args) +{ + // Check to see if we already know about this device. If so, ignore + + if (MidiEndpointTable::Current().GetEndpointEntryForBluetoothAddress(args.BluetoothAddress()) != nullptr) + { + // we already know about this device. Exit + + return S_OK; + } + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(args.BluetoothAddress(), "address"), + TraceLoggingWideString(L"New BLE device found via advertisement", "message") + ); + + // TODO + // If we don't know about it, check to see if it's a device on our allowed list + // If so, we can create a device entry for it. If not, ignore. + // We may want a ble-wide policy option to allow any MIDI devices we see, vs deny by default. Default is deny all. + + + auto device = bt::BluetoothLEDevice::FromBluetoothAddressAsync(args.BluetoothAddress()).get(); + RETURN_HR_IF_NULL_MSG(E_UNEXPECTED, device, "Bluetooth device address is invalid."); + + // get the GATT MIDI service + auto service = GetBleMidiServiceFromDevice(device); + + if (service != nullptr) + { + auto connectionStatusChangedHandler = foundation::TypedEventHandler + (this, &CMidi2BluetoothMidiEndpointManager::OnBleDeviceConnectionStatusChanged); + + // create the device definition + MidiBluetoothDeviceDefinition definition; + + definition.BluetoothAddress = device.BluetoothAddress(); + definition.TransportSuppliedName = device.Name(); // need to ensure we also wire up the name changed event handler for this + + std::shared_ptr entry{ nullptr }; + + // todo: Should lock the entry list during this + + + + + // add the new endpoint to the device table + entry = MidiEndpointTable::Current().CreateAndAddNewEndpointEntry(definition, device, service); + + if (entry != nullptr) + { + // create the MIDI endpoint if this is connected. + if (entry->Device.ConnectionStatus() == bt::BluetoothConnectionStatus::Connected) + { + RETURN_IF_FAILED(CreateEndpoint(&(entry->Definition))); + } + + // wire up connection status changed event and store the token for revocation +// entry->ConnectionStatusChangedToken = device.ConnectionStatusChanged(winrt::auto_revoke, connectionStatusChangedHandler); + //entry->ConnectionStatusChangedRevoker = entry->Device.ConnectionStatusChanged(winrt::auto_revoke, connectionStatusChangedHandler); + entry->ConnectionStatusChangedRevoker = entry->Device.ConnectionStatusChanged(connectionStatusChangedHandler); + } + else + { + // failed to just add a new entry. Log + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(device.BluetoothAddress(), "address"), + TraceLoggingWideString(device.DeviceId().c_str(), "device id"), + TraceLoggingWideString(device.Name().c_str(), "name"), + TraceLoggingWideString(L"Failed to add entry to endpoint table", "message") + ); + } + } + else + { + // no BLE MIDI service found. Log this because the filter should have prevented this. + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(device.BluetoothAddress(), "address"), + TraceLoggingWideString(device.DeviceId().c_str(), "device id"), + TraceLoggingWideString(device.Name().c_str(), "name"), + TraceLoggingWideString(L"BLE device does not have MIDI service", "message") + ); + } + + return S_OK; +} + +_Use_decl_annotations_ +HRESULT +CMidi2BluetoothMidiEndpointManager::OnBleAdvertisementWatcherStopped(ad::BluetoothLEAdvertisementWatcher, ad::BluetoothLEAdvertisementWatcherStoppedEventArgs args) +{ + // error enum vals: https://learn.microsoft.com/uwp/api/windows.devices.bluetooth.bluetootherror?view=winrt-22621 + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt32((uint32_t)args.Error(), "BluetoothError enum value") + ); + + // todo: consider if we should restart the watcher or not + + return S_OK; +} + + HRESULT -CMidi2BluetoothMidiEndpointManager::OnEnumerationCompleted(enumeration::DeviceWatcher, foundation::IInspectable) +CMidi2BluetoothMidiEndpointManager::StartAdvertisementWatcher() { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + auto adReceivedHandler = foundation::TypedEventHandler + (this, &CMidi2BluetoothMidiEndpointManager::OnBleAdvertisementReceived); + + auto adWatcherStoppedHandler = foundation::TypedEventHandler + (this, &CMidi2BluetoothMidiEndpointManager::OnBleAdvertisementWatcherStopped); + + + // wire up the event handler so we're notified when advertising messages are received. + // This will fire for every message received, even if we already know about the device. + m_AdvertisementReceived = m_bleAdWatcher.Received(winrt::auto_revoke, adReceivedHandler); + m_AdvertisementWatcherStopped = m_bleAdWatcher.Stopped(winrt::auto_revoke, adWatcherStoppedHandler); + + winrt::guid serviceUuid{ MIDI_BLE_SERVICE_UUID }; + + m_bleAdWatcher.AdvertisementFilter().Advertisement().ServiceUuids().Append(serviceUuid); + m_bleAdWatcher.Start(); + + if (m_bleAdWatcher.Status() == ad::BluetoothLEAdvertisementWatcherStatus::Aborted) + { + // if the status is Aborted, it means there was an error + + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Error starting BLE ad watcher", "message") + ); + + return E_FAIL; + } + else + { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"BLE Ad watcher started", "message") + ); + + return S_OK; + } +} + +HRESULT +CMidi2BluetoothMidiEndpointManager::StartDeviceWatcher() +{ + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + winrt::hstring deviceSelector = bt::BluetoothLEDevice::GetDeviceSelector(); + + + auto requestedProperties = winrt::single_threaded_vector( + { + L"System.DeviceInterface.Bluetooth.DeviceAddress", + L"System.DeviceInterface.Bluetooth.Flags", + L"System.DeviceInterface.Bluetooth.LastConnectedTime", + L"System.DeviceInterface.Bluetooth.Manufacturer", + L"System.DeviceInterface.Bluetooth.ModelNumber", + L"System.DeviceInterface.Bluetooth.ProductId", + L"System.DeviceInterface.Bluetooth.ProductVersion", + L"System.DeviceInterface.Bluetooth.ServiceGuid", + L"System.DeviceInterface.Bluetooth.VendorId", + L"System.DeviceInterface.Bluetooth.VendorIdSource", + L"System.Devices.Connected", + L"System.Devices.DeviceCapabilities", + L"System.Devices.DeviceCharacteristics" + } + ); + + + m_Watcher = enumeration::DeviceInformation::CreateWatcher(deviceSelector, requestedProperties); + + auto deviceAddedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnDeviceAdded); + auto deviceRemovedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnDeviceRemoved); + auto deviceUpdatedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnDeviceUpdated); + auto deviceStoppedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnDeviceStopped); + auto deviceEnumerationCompletedHandler = foundation::TypedEventHandler(this, &CMidi2BluetoothMidiEndpointManager::OnEnumerationCompleted); + + m_DeviceAdded = m_Watcher.Added(winrt::auto_revoke, deviceAddedHandler); + m_DeviceRemoved = m_Watcher.Removed(winrt::auto_revoke, deviceRemovedHandler); + m_DeviceUpdated = m_Watcher.Updated(winrt::auto_revoke, deviceUpdatedHandler); + m_DeviceStopped = m_Watcher.Stopped(winrt::auto_revoke, deviceStoppedHandler); + m_DeviceEnumerationCompleted = m_Watcher.EnumerationCompleted(winrt::auto_revoke, deviceEnumerationCompletedHandler); + + + m_Watcher.Start(); return S_OK; } + +HRESULT +CMidi2BluetoothMidiEndpointManager::CreateSelfPeripheralEndpoint() +{ + + + + return S_OK; +} \ No newline at end of file diff --git a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiEndpointManager.h b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiEndpointManager.h index ec6a1396..436052f3 100644 --- a/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiEndpointManager.h +++ b/src/api/Abstraction/BleMidiAbstraction/Midi2.BluetoothMidiEndpointManager.h @@ -8,6 +8,12 @@ #pragma once +// Notes: +// Write (encryption recommended, write without response is required) +// Read (encryption recommended, respond with no payload) +// Notify (encryption recommended) +// Max connection interval is 15ms. Lower is better. + class CMidi2BluetoothMidiEndpointManager : public Microsoft::WRL::RuntimeClass< @@ -21,7 +27,15 @@ class CMidi2BluetoothMidiEndpointManager : private: - enumeration::DeviceWatcher m_Watcher{ nullptr }; +// winrt::guid m_midiBleServiceUuid{ MIDI_BLE_SERVICE_UUID }; + wil::com_ptr_nothrow m_MidiDeviceManager; + + GUID m_ContainerId{}; + GUID m_TransportAbstractionId{}; + std::wstring m_parentDeviceId{}; + + enumeration::DeviceWatcher m_Watcher{ nullptr }; // for non-advertising but paired devices + ad::BluetoothLEAdvertisementWatcher m_bleAdWatcher; // for advertised devices, which is common for BLE MIDI winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Added_revoker m_DeviceAdded; winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Removed_revoker m_DeviceRemoved; @@ -29,23 +43,32 @@ class CMidi2BluetoothMidiEndpointManager : winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Stopped_revoker m_DeviceStopped; winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::EnumerationCompleted_revoker m_DeviceEnumerationCompleted; + winrt::impl::consume_Windows_Devices_Bluetooth_Advertisement_IBluetoothLEAdvertisementWatcher::Received_revoker m_AdvertisementReceived; + winrt::impl::consume_Windows_Devices_Bluetooth_Advertisement_IBluetoothLEAdvertisementWatcher::Stopped_revoker m_AdvertisementWatcherStopped; + + HRESULT OnDeviceAdded(_In_ enumeration::DeviceWatcher, _In_ enumeration::DeviceInformation); HRESULT OnDeviceRemoved(_In_ enumeration::DeviceWatcher, _In_ enumeration::DeviceInformationUpdate); HRESULT OnDeviceUpdated(_In_ enumeration::DeviceWatcher, _In_ enumeration::DeviceInformationUpdate); HRESULT OnDeviceStopped(_In_ enumeration::DeviceWatcher, _In_ foundation::IInspectable); HRESULT OnEnumerationCompleted(_In_ enumeration::DeviceWatcher, _In_ foundation::IInspectable); + HRESULT OnBleAdvertisementReceived(_In_ ad::BluetoothLEAdvertisementWatcher, _In_ ad::BluetoothLEAdvertisementReceivedEventArgs const&); + HRESULT OnBleAdvertisementWatcherStopped(_In_ ad::BluetoothLEAdvertisementWatcher, _In_ ad::BluetoothLEAdvertisementWatcherStoppedEventArgs); - GUID m_ContainerId{}; - GUID m_TransportAbstractionId{}; + HRESULT OnBleDeviceConnectionStatusChanged(_In_ bt::BluetoothLEDevice /*device*/, _In_ foundation::IInspectable /*args*/); + HRESULT OnBleDeviceNameChanged(_In_ bt::BluetoothLEDevice /*device*/, _In_ foundation::IInspectable /*args*/); HRESULT CreateEndpoint( - _In_ MidiBluetoothDeviceDefinition& definition + _In_ MidiBluetoothDeviceDefinition* definition ); - HRESULT EnumCompatibleBluetoothDevices(); +// HRESULT EnumCompatibleBluetoothDevices(); - HRESULT CreateParentDevice(); + HRESULT StartAdvertisementWatcher(); + HRESULT StartDeviceWatcher(); - wil::com_ptr_nothrow m_MidiDeviceManager; + HRESULT CreateSelfPeripheralEndpoint(); + + HRESULT CreateParentDevice(); }; diff --git a/src/api/Abstraction/BleMidiAbstraction/MidiBluetoothDeviceDefinition.h b/src/api/Abstraction/BleMidiAbstraction/MidiBluetoothDeviceDefinition.h index 8b78d4c3..6c31fd47 100644 --- a/src/api/Abstraction/BleMidiAbstraction/MidiBluetoothDeviceDefinition.h +++ b/src/api/Abstraction/BleMidiAbstraction/MidiBluetoothDeviceDefinition.h @@ -11,11 +11,24 @@ struct MidiBluetoothDeviceDefinition { - std::wstring HardwareBluetoothDeviceId{}; + uint64_t BluetoothAddress{}; + winrt::hstring TransportSuppliedName{}; + winrt::hstring TransportSuppliedDescription{}; - std::wstring TransportSuppliedName{}; - std::wstring TransportSuppliedDescription{}; + winrt::hstring ManufacturerName{}; + +// winrt::hstring UserSuppliedName{}; +// winrt::hstring UserSuppliedDescription{}; std::wstring CreatedEndpointInterfaceId{}; + std::wstring CreatedMidiDeviceInstanceId{}; + + + void SetDeactivated() + { + CreatedEndpointInterfaceId = L""; + CreatedMidiDeviceInstanceId = L""; + } + }; \ No newline at end of file diff --git a/src/api/Abstraction/BleMidiAbstraction/MidiEndpointTable.cpp b/src/api/Abstraction/BleMidiAbstraction/MidiEndpointTable.cpp index 23ef796a..6869b39c 100644 --- a/src/api/Abstraction/BleMidiAbstraction/MidiEndpointTable.cpp +++ b/src/api/Abstraction/BleMidiAbstraction/MidiEndpointTable.cpp @@ -22,20 +22,66 @@ MidiEndpointTable& MidiEndpointTable::Current() } + + _Use_decl_annotations_ -wil::com_ptr_nothrow -MidiEndpointTable::GetEndpointInterfaceForId( - std::wstring const EndpointDeviceId -) const noexcept +std::shared_ptr +MidiEndpointTable::GetEndpointEntryForBluetoothAddress(uint64_t const bluetoothAddress) const noexcept { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(bluetoothAddress, "address") + ); + try { - auto result = m_Endpoints.find(EndpointDeviceId); - - if (result != m_Endpoints.end()) - return result->second.MidiDeviceBiDi; + if (auto it = m_endpoints.find(bluetoothAddress); it != m_endpoints.end()) + { + return it->second; + } else + { return nullptr; + } + } + catch (...) + { + return nullptr; + } +} + + +_Use_decl_annotations_ +std::shared_ptr +MidiEndpointTable::CreateAndAddNewEndpointEntry( + MidiBluetoothDeviceDefinition definition, + bt::BluetoothLEDevice device, + gatt::GattDeviceService service +) noexcept +{ + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(definition.BluetoothAddress, "address") + ); + + try + { + MidiBluetoothEndpointEntry entry; + + entry.Definition = definition; + entry.MidiDeviceBiDi = nullptr; + entry.Device = device; + entry.Service = service; + + m_endpoints[definition.BluetoothAddress] = std::make_shared(entry); + + return m_endpoints[definition.BluetoothAddress]; } catch (...) { @@ -44,19 +90,29 @@ MidiEndpointTable::GetEndpointInterfaceForId( } + + _Use_decl_annotations_ void MidiEndpointTable::RemoveEndpointEntry( - std::wstring EndpointDeviceId + uint64_t bluetoothAddress ) noexcept { + TraceLoggingWrite( + MidiBluetoothMidiAbstractionTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingUInt64(bluetoothAddress, "address") + ); + try { - auto result = m_Endpoints.find(EndpointDeviceId); + auto result = m_endpoints.find(bluetoothAddress); - if (result != m_Endpoints.end()) + if (result != m_endpoints.end()) { - m_Endpoints.erase(result); + m_endpoints.erase(result); } } catch (...) diff --git a/src/api/Abstraction/BleMidiAbstraction/MidiEndpointTable.h b/src/api/Abstraction/BleMidiAbstraction/MidiEndpointTable.h index 66034dc7..58f687af 100644 --- a/src/api/Abstraction/BleMidiAbstraction/MidiEndpointTable.h +++ b/src/api/Abstraction/BleMidiAbstraction/MidiEndpointTable.h @@ -13,22 +13,36 @@ // thread-safe meyers singleton for storing the devices we'll use -struct MidiVirtualEndpointEntry +struct MidiBluetoothEndpointEntry { - GUID JsonKey; // this is the key that is used in config before there's any SWD to refer to + MidiBluetoothDeviceDefinition Definition{}; - std::wstring InstanceId; // the part of the instance ID we (mostly) control + bt::BluetoothLEDevice Device{ nullptr }; + gatt::GattDeviceService Service{ nullptr }; - std::wstring EndpointDeviceId; // the device interface id + wil::com_ptr_nothrow MidiDeviceBiDi{ nullptr }; - wil::com_ptr_nothrow MidiDeviceBiDi; + //winrt::event_token ConnectionStatusChangedRevoker; - ~MidiVirtualEndpointEntry() + //bt::BluetoothLEDevice::ConnectionParametersChanged_revoker ConnectionStatusChangedRevoker{ nullptr }; + //winrt::impl::consume_Windows_Devices_Bluetooth_IBluetoothLEDevice::ConnectionStatusChanged_revoker ConnectionStatusChangedRevoker{ nullptr }; +// winrt::impl::consume_Windows_Devices_Bluetooth_IBluetoothLEDevice::ConnectionStatusChanged_revoker ConnectionStatusChangedRevoker; + + winrt::event_token ConnectionStatusChangedRevoker; + + MidiBluetoothEndpointEntry() = default; + ~MidiBluetoothEndpointEntry() { if (MidiDeviceBiDi) { + MidiDeviceBiDi->Cleanup(); MidiDeviceBiDi.reset(); } + + if (Device != nullptr) + { + Device.ConnectionStatusChanged(ConnectionStatusChangedRevoker); + } } }; @@ -36,28 +50,24 @@ struct MidiVirtualEndpointEntry class MidiEndpointTable { public: - //std::shared_ptr GetDevice(std::wstring deviceId); - //MidiLoopbackDevice* GetBidiDevice(); - //MidiLoopbackDevice* GetInOutDevice(); - static MidiEndpointTable& Current(); // no copying MidiEndpointTable(_In_ const MidiEndpointTable&) = delete; MidiEndpointTable& operator=(_In_ const MidiEndpointTable&) = delete; +// wil::com_ptr_nothrow GetEndpointInterfaceForId(_In_ std::wstring const EndpointDeviceId) const noexcept; + + std::shared_ptr GetEndpointEntryForBluetoothAddress(_In_ uint64_t const bluetoothAddress) const noexcept; - wil::com_ptr_nothrow GetEndpointInterfaceForId(_In_ std::wstring EndpointDeviceId) const noexcept; - void RemoveEndpointEntry(_In_ std::wstring EndpointDeviceId) noexcept; + std::shared_ptr CreateAndAddNewEndpointEntry(_In_ MidiBluetoothDeviceDefinition definition, _In_ bt::BluetoothLEDevice device, _In_ gatt::GattDeviceService service) noexcept; + + void RemoveEndpointEntry(_In_ uint64_t bluetoothAddress) noexcept; private: MidiEndpointTable(); ~MidiEndpointTable(); - // key is EndpointDeviceId (the device interface id) - std::map m_Endpoints; - - - //MidiLoopbackDevice m_BidiDevice; - //MidiLoopbackDevice m_InOutDevice; + std::map> m_endpoints; + //std::vector m_endpoints; }; \ No newline at end of file diff --git a/src/api/Abstraction/BleMidiAbstraction/abstraction_defs.h b/src/api/Abstraction/BleMidiAbstraction/abstraction_defs.h index 61290a64..f88f8d05 100644 --- a/src/api/Abstraction/BleMidiAbstraction/abstraction_defs.h +++ b/src/api/Abstraction/BleMidiAbstraction/abstraction_defs.h @@ -15,10 +15,11 @@ // The full Id comes back from the swdevicecreate callback #define TRANSPORT_MNEMONIC L"BLE1" - -// TODO: Names should be moved to .rc for localization +#define MIDI_BLE_INSTANCE_ID_PREFIX L"MIDIU_BLE1_" #define TRANSPORT_PARENT_ID L"MIDIU_BLE1_TRANSPORT" + +// TODO: Names should be moved to .rc for localization #define TRANSPORT_PARENT_DEVICE_NAME L"MIDI 1.0 Bluetooth Devices" diff --git a/src/api/Abstraction/BleMidiAbstraction/ble_utilities.h b/src/api/Abstraction/BleMidiAbstraction/ble_utilities.h new file mode 100644 index 00000000..a6bdf3b5 --- /dev/null +++ b/src/api/Abstraction/BleMidiAbstraction/ble_utilities.h @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#pragma once + +#define MIDI_BLE_SERVICE_UUID L"03B80E5A-EDE8-4B33-A751-6CE34EC4C700" +#define MIDI_BLE_DATA_IO_CHARACTERISTIC_UUID L"7772E5DB-3868-4112-A1A9-F2669D106BF3" + + +inline bt::BluetoothLEDevice GetBleDeviceFromEnumerationDeviceId(_In_ std::wstring deviceId) +{ + // std::cout << __FUNCTION__ << std::endl; + + auto device = bt::BluetoothLEDevice::FromIdAsync(winrt::to_hstring(deviceId.c_str())).get(); + + return device; +} + + +inline gatt::GattDeviceService GetBleMidiServiceFromDevice(_In_ bt::BluetoothLEDevice bleDevice) +{ + // std::cout << __FUNCTION__ << std::endl; + + winrt::guid serviceUuid{ MIDI_BLE_SERVICE_UUID }; + + auto gattServicesResult = bleDevice.GetGattServicesAsync(bt::BluetoothCacheMode::Uncached).get(); + + if (gattServicesResult.Status() == gatt::GattCommunicationStatus::Success) + { + auto services = gattServicesResult.Services(); + + // find the MIDI BLE service + if (auto it = std::find_if(services.begin(), services.end(), [&](gatt::GattDeviceService const& x) { return x.Uuid() == serviceUuid; }); it != services.end()) + { + // just grab the first one. Shouldn't be more than one match. + auto midiService = *it; + + return midiService; + } + } + + return nullptr; +} + + + +inline gatt::GattCharacteristic GetBleMidiDataIOCharacteristicFromService(_In_ gatt::GattDeviceService midiService) +{ + // std::cout << __FUNCTION__ << std::endl; + + winrt::guid dataIOCharacteristicUuid{ MIDI_BLE_DATA_IO_CHARACTERISTIC_UUID }; + + if (midiService != nullptr) + { + auto characteristicsResult = midiService.GetCharacteristicsForUuidAsync(dataIOCharacteristicUuid).get(); + + if (characteristicsResult.Status() == gatt::GattCommunicationStatus::Success && characteristicsResult.Characteristics().Size() > 0) + { + // we're just getting the first one + auto characteristic = characteristicsResult.Characteristics().GetAt(0); + + return characteristic; + } + } + + return nullptr; +} diff --git a/src/api/Abstraction/BleMidiAbstraction/pch.h b/src/api/Abstraction/BleMidiAbstraction/pch.h index 33202444..ee7c79a4 100644 --- a/src/api/Abstraction/BleMidiAbstraction/pch.h +++ b/src/api/Abstraction/BleMidiAbstraction/pch.h @@ -21,14 +21,18 @@ #include #include #include -#include +#include +#include #include namespace json = ::winrt::Windows::Data::Json; namespace enumeration = ::winrt::Windows::Devices::Enumeration; namespace foundation = ::winrt::Windows::Foundation; namespace collections = ::winrt::Windows::Foundation::Collections; + namespace bt = ::winrt::Windows::Devices::Bluetooth; +namespace gatt = ::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile; +namespace ad = ::winrt::Windows::Devices::Bluetooth::Advertisement; #include #include @@ -94,6 +98,9 @@ class CMidi2BluetoothMidiPluginMetadataManager; #include "abstraction_defs.h" #include "midi_timestamp.h" #include "ble_timestamp.h" +#include "ble_utilities.h" + +#include "AbstractionState.h" #include "MidiServicePlugin.h" #include "MidiServicePlugin_i.c" @@ -109,7 +116,6 @@ class CMidi2BluetoothMidiPluginMetadataManager; #include "MidiBluetoothPacket.h" #include "MidiBluetoothDeviceDefinition.h" -#include "MidiBluetoothDevice.h" #include "MidiEndpointTable.h" #include "Midi2.BluetoothMidiAbstraction.h" diff --git a/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.cpp b/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.cpp index d517817e..19339d84 100644 --- a/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.cpp +++ b/src/api/Abstraction/KSAbstraction/Midi2.KSMidiEndpointManager.cpp @@ -601,13 +601,22 @@ HRESULT CMidi2KSMidiEndpointManager::OnDeviceAdded(DeviceWatcher watcher, Device BYTE preferredProtocol{ MIDI_PROP_CONFIGURED_PROTOCOL_MIDI2 }; WORD negotiationTimeoutMS{ 5000 }; // this should be much shorter + PENDPOINTPROTOCOLNEGOTIATIONRESULTS negotiationResults; + LOG_IF_FAILED(m_MidiProtocolManager->NegotiateAndRequestMetadata( newDeviceInterfaceId, preferToSendJRToEndpoint, preferToReceiveJRFromEndpoint, preferredProtocol, - negotiationTimeoutMS + negotiationTimeoutMS, + &negotiationResults )); + + + // TODO: The negotiationResults structure contains the function blocks which + // should be used to create MIDI 1.0 legacy ports for this MIDI 2.0 device. + + } } diff --git a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.cpp b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.cpp index bc022794..82f9eb0e 100644 --- a/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.cpp +++ b/src/api/Abstraction/VirtualMidiAbstraction/Midi2.VirtualMidiEndpointManager.cpp @@ -142,7 +142,7 @@ CMidi2VirtualMidiEndpointManager::CreateParentDevice() { TraceLoggingWrite( MidiVirtualMidiAbstractionTelemetryProvider::Provider(), - __func__, + __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), TraceLoggingPointer(this, "this") ); @@ -202,12 +202,15 @@ CMidi2VirtualMidiEndpointManager::NegotiateAndRequestMetadata(std::wstring endpo BYTE preferredProtocol{ MIDI_PROP_CONFIGURED_PROTOCOL_MIDI2 }; WORD negotiationTimeoutMS{ 2000 }; + PENDPOINTPROTOCOLNEGOTIATIONRESULTS negotiationResults; + RETURN_IF_FAILED(m_MidiProtocolManager->NegotiateAndRequestMetadata( endpointId.c_str(), preferToSendJRToEndpoint, preferToReceiveJRFromEndpoint, preferredProtocol, - negotiationTimeoutMS + negotiationTimeoutMS, + &negotiationResults )); return S_OK; diff --git a/src/api/Client/Midi2Client-Projection/Windows.Devices.Midi2.NET.csproj b/src/api/Client/Midi2Client-Projection/Windows.Devices.Midi2.NET.csproj index 534e21d2..0721cac0 100644 --- a/src/api/Client/Midi2Client-Projection/Windows.Devices.Midi2.NET.csproj +++ b/src/api/Client/Midi2Client-Projection/Windows.Devices.Midi2.NET.csproj @@ -1,7 +1,7 @@  - net7.0-windows10.0.20348.0 + net8.0-windows10.0.20348.0 enable enable AnyCPU diff --git a/src/api/Client/Midi2Client-Projection/nuget/Windows.Devices.Midi2.nuspec b/src/api/Client/Midi2Client-Projection/nuget/Windows.Devices.Midi2.nuspec index 6503deec..5de74484 100644 --- a/src/api/Client/Midi2Client-Projection/nuget/Windows.Devices.Midi2.nuspec +++ b/src/api/Client/Midi2Client-Projection/nuget/Windows.Devices.Midi2.nuspec @@ -2,7 +2,7 @@ Windows.Devices.Midi2 - 1.0.0-preview.5-0185 + 1.0.0-preview.6-0190 Microsoft Corporation Windows MIDI Services API. Minimum package necessary to use Windows MIDI Services from an app on a PC that has Windows MIDI Services installed. MIT @@ -12,7 +12,6 @@ false midi windows music audio native - @@ -20,9 +19,6 @@ - - - @@ -39,9 +35,7 @@ - - - - @@ -68,8 +60,6 @@ - - diff --git a/src/api/Client/Midi2Client/IMidiEndpointConnectionSettings.idl b/src/api/Client/Midi2Client/IMidiEndpointConnectionSettings.idl index d6681ce6..1df7befb 100644 --- a/src/api/Client/Midi2Client/IMidiEndpointConnectionSettings.idl +++ b/src/api/Client/Midi2Client/IMidiEndpointConnectionSettings.idl @@ -15,7 +15,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [MIDI_INTERFACE_UUID("65736736-35f3-421c-a683-3a034ad0dcc2",1.0)] interface IMidiEndpointConnectionSettings { diff --git a/src/api/Client/Midi2Client/IMidiEndpointConnectionSource.idl b/src/api/Client/Midi2Client/IMidiEndpointConnectionSource.idl index 6b6a1870..2ece14ee 100644 --- a/src/api/Client/Midi2Client/IMidiEndpointConnectionSource.idl +++ b/src/api/Client/Midi2Client/IMidiEndpointConnectionSource.idl @@ -17,7 +17,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [MIDI_INTERFACE_UUID("44385cdd-b64c-4195-8faa-8a61fc952a23",1.0)] interface IMidiEndpointConnectionSource { diff --git a/src/api/Client/Midi2Client/IMidiEndpointMessageProcessingPlugin.idl b/src/api/Client/Midi2Client/IMidiEndpointMessageProcessingPlugin.idl index ef17b4e7..c6504d11 100644 --- a/src/api/Client/Midi2Client/IMidiEndpointMessageProcessingPlugin.idl +++ b/src/api/Client/Midi2Client/IMidiEndpointMessageProcessingPlugin.idl @@ -16,7 +16,7 @@ import "IMidiEndpointConnectionSource.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [MIDI_INTERFACE_UUID("198d5ef2-313f-41af-9259-a42997e060f0",1.0)] interface IMidiEndpointMessageProcessingPlugin { diff --git a/src/api/Client/Midi2Client/IMidiMessageReceivedEventSource.idl b/src/api/Client/Midi2Client/IMidiMessageReceivedEventSource.idl index 7b8eab73..115cd1b6 100644 --- a/src/api/Client/Midi2Client/IMidiMessageReceivedEventSource.idl +++ b/src/api/Client/Midi2Client/IMidiMessageReceivedEventSource.idl @@ -13,7 +13,7 @@ import "MidiMessageReceivedEventArgs.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [MIDI_INTERFACE_UUID("101ceb4b-cca4-48a1-b91e-6002b928613c",1.0)] interface IMidiMessageReceivedEventSource { diff --git a/src/api/Client/Midi2Client/IMidiServiceMessageProcessingPluginConfiguration.idl b/src/api/Client/Midi2Client/IMidiServiceMessageProcessingPluginConfiguration.idl index d0d86b51..c314601a 100644 --- a/src/api/Client/Midi2Client/IMidiServiceMessageProcessingPluginConfiguration.idl +++ b/src/api/Client/Midi2Client/IMidiServiceMessageProcessingPluginConfiguration.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [MIDI_INTERFACE_UUID("2ebcfa13-585a-4376-8fe1-635784fa7fd4",1.0)] interface IMidiServiceMessageProcessingPluginConfiguration { diff --git a/src/api/Client/Midi2Client/IMidiServiceTransportPluginConfiguration.idl b/src/api/Client/Midi2Client/IMidiServiceTransportPluginConfiguration.idl index 5f264a7b..45838aba 100644 --- a/src/api/Client/Midi2Client/IMidiServiceTransportPluginConfiguration.idl +++ b/src/api/Client/Midi2Client/IMidiServiceTransportPluginConfiguration.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [MIDI_INTERFACE_UUID("b2417dde-ef35-499b-a89b-0a4c32cc699a",1.0)] interface IMidiServiceTransportPluginConfiguration { diff --git a/src/api/Client/Midi2Client/IMidiUniversalPacket.idl b/src/api/Client/Midi2Client/IMidiUniversalPacket.idl index 9c8ede65..5a5e0432 100644 --- a/src/api/Client/Midi2Client/IMidiUniversalPacket.idl +++ b/src/api/Client/Midi2Client/IMidiUniversalPacket.idl @@ -16,7 +16,7 @@ import "MidiMessageTypeEnum.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [MIDI_INTERFACE_UUID("2eb5df8a-d751-4997-bf96-ba9a531fd5ff",1.0)] interface IMidiUniversalPacket { diff --git a/src/api/Client/Midi2Client/Midi1ChannelVoiceMessageStatusEnum.idl b/src/api/Client/Midi2Client/Midi1ChannelVoiceMessageStatusEnum.idl index e4a52da9..c9595d53 100644 --- a/src/api/Client/Midi2Client/Midi1ChannelVoiceMessageStatusEnum.idl +++ b/src/api/Client/Midi2Client/Midi1ChannelVoiceMessageStatusEnum.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum Midi1ChannelVoiceMessageStatus { NoteOff = 0x8, diff --git a/src/api/Client/Midi2Client/Midi2ChannelVoiceMessageStatusEnum.idl b/src/api/Client/Midi2Client/Midi2ChannelVoiceMessageStatusEnum.idl index cad5345b..bf7f9298 100644 --- a/src/api/Client/Midi2Client/Midi2ChannelVoiceMessageStatusEnum.idl +++ b/src/api/Client/Midi2Client/Midi2ChannelVoiceMessageStatusEnum.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum Midi2ChannelVoiceMessageStatus { RegisteredPerNoteController = 0x0, diff --git a/src/api/Client/Midi2Client/MidiChannel.idl b/src/api/Client/Midi2Client/MidiChannel.idl index ed92c1d5..562e83db 100644 --- a/src/api/Client/Midi2Client/MidiChannel.idl +++ b/src/api/Client/Midi2Client/MidiChannel.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiChannel { diff --git a/src/api/Client/Midi2Client/MidiChannelEndpointListener.idl b/src/api/Client/Midi2Client/MidiChannelEndpointListener.idl index aaeb4330..58e01372 100644 --- a/src/api/Client/Midi2Client/MidiChannelEndpointListener.idl +++ b/src/api/Client/Midi2Client/MidiChannelEndpointListener.idl @@ -22,7 +22,7 @@ import "IMidiMessageReceivedEventSource.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiChannelEndpointListener : IMidiEndpointMessageProcessingPlugin, IMidiMessageReceivedEventSource diff --git a/src/api/Client/Midi2Client/MidiClock.idl b/src/api/Client/Midi2Client/MidiClock.idl index 0265c409..74f31c50 100644 --- a/src/api/Client/Midi2Client/MidiClock.idl +++ b/src/api/Client/Midi2Client/MidiClock.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiClock { diff --git a/src/api/Client/Midi2Client/MidiEndpointConnection.idl b/src/api/Client/Midi2Client/MidiEndpointConnection.idl index 567e1011..53cbc67b 100644 --- a/src/api/Client/Midi2Client/MidiEndpointConnection.idl +++ b/src/api/Client/Midi2Client/MidiEndpointConnection.idl @@ -22,7 +22,7 @@ import "IMidiEndpointConnectionSource.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiEndpointConnection : Windows.Devices.Midi2.IMidiMessageReceivedEventSource, IMidiEndpointConnectionSource diff --git a/src/api/Client/Midi2Client/MidiEndpointDeviceInformation.idl b/src/api/Client/Midi2Client/MidiEndpointDeviceInformation.idl index baae7538..dbf47199 100644 --- a/src/api/Client/Midi2Client/MidiEndpointDeviceInformation.idl +++ b/src/api/Client/Midi2Client/MidiEndpointDeviceInformation.idl @@ -20,7 +20,7 @@ import "MidiEndpointDeviceInformationFiltersEnum.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiEndpointDeviceInformation { diff --git a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationAddedEventArgs.idl b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationAddedEventArgs.idl index 38a44896..1dbb30ba 100644 --- a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationAddedEventArgs.idl +++ b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationAddedEventArgs.idl @@ -13,7 +13,7 @@ import "MidiEndpointDeviceInformation.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiEndpointDeviceInformationAddedEventArgs { diff --git a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationFiltersEnum.idl b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationFiltersEnum.idl index c201fde0..97e2f891 100644 --- a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationFiltersEnum.idl +++ b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationFiltersEnum.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [flags] enum MidiEndpointDeviceInformationFilters { diff --git a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationRemovedEventArgs.idl b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationRemovedEventArgs.idl index 22a244bd..65c8c997 100644 --- a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationRemovedEventArgs.idl +++ b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationRemovedEventArgs.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiEndpointDeviceInformationRemovedEventArgs { diff --git a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationSortOrderEnum.idl b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationSortOrderEnum.idl index 8959d7c2..d23594a4 100644 --- a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationSortOrderEnum.idl +++ b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationSortOrderEnum.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiEndpointDeviceInformationSortOrder { None = 0, diff --git a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationUpdatedEventArgs.idl b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationUpdatedEventArgs.idl index 6f933bfb..374cf244 100644 --- a/src/api/Client/Midi2Client/MidiEndpointDeviceInformationUpdatedEventArgs.idl +++ b/src/api/Client/Midi2Client/MidiEndpointDeviceInformationUpdatedEventArgs.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiEndpointDeviceInformationUpdatedEventArgs { diff --git a/src/api/Client/Midi2Client/MidiEndpointDevicePurposeEnum.idl b/src/api/Client/Midi2Client/MidiEndpointDevicePurposeEnum.idl index 5564d0a8..065feb57 100644 --- a/src/api/Client/Midi2Client/MidiEndpointDevicePurposeEnum.idl +++ b/src/api/Client/Midi2Client/MidiEndpointDevicePurposeEnum.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiEndpointDevicePurpose { NormalMessageEndpoint = 0, diff --git a/src/api/Client/Midi2Client/MidiEndpointDeviceWatcher.idl b/src/api/Client/Midi2Client/MidiEndpointDeviceWatcher.idl index 4c9f7534..13a98274 100644 --- a/src/api/Client/Midi2Client/MidiEndpointDeviceWatcher.idl +++ b/src/api/Client/Midi2Client/MidiEndpointDeviceWatcher.idl @@ -18,7 +18,7 @@ import "MidiEndpointDeviceInformationAddedEventArgs.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiEndpointDeviceWatcher { diff --git a/src/api/Client/Midi2Client/MidiEndpointDiscoveryRequestsEnum.idl b/src/api/Client/Midi2Client/MidiEndpointDiscoveryRequestsEnum.idl index 6cd09af8..35e8994e 100644 --- a/src/api/Client/Midi2Client/MidiEndpointDiscoveryRequestsEnum.idl +++ b/src/api/Client/Midi2Client/MidiEndpointDiscoveryRequestsEnum.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [flags] enum MidiEndpointDiscoveryRequests { diff --git a/src/api/Client/Midi2Client/MidiEndpointNativeDataFormatEnum.idl b/src/api/Client/Midi2Client/MidiEndpointNativeDataFormatEnum.idl index ad6e97a9..a9ea8ec3 100644 --- a/src/api/Client/Midi2Client/MidiEndpointNativeDataFormatEnum.idl +++ b/src/api/Client/Midi2Client/MidiEndpointNativeDataFormatEnum.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiEndpointNativeDataFormat { Unknown = 0, diff --git a/src/api/Client/Midi2Client/MidiFunctionBlock.idl b/src/api/Client/Midi2Client/MidiFunctionBlock.idl index a87b4b86..adc0ced6 100644 --- a/src/api/Client/Midi2Client/MidiFunctionBlock.idl +++ b/src/api/Client/Midi2Client/MidiFunctionBlock.idl @@ -17,7 +17,7 @@ import "MidiGroup.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiFunctionBlock { diff --git a/src/api/Client/Midi2Client/MidiFunctionBlockDirectionEnum.idl b/src/api/Client/Midi2Client/MidiFunctionBlockDirectionEnum.idl index 30dfe5e6..aef4a955 100644 --- a/src/api/Client/Midi2Client/MidiFunctionBlockDirectionEnum.idl +++ b/src/api/Client/Midi2Client/MidiFunctionBlockDirectionEnum.idl @@ -14,7 +14,7 @@ namespace Windows.Devices.Midi2 // NOTE: the values are different for function blocks and group terminal // blocks, so we have to different enumerations - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiFunctionBlockDirection { Undefined = 0x0, diff --git a/src/api/Client/Midi2Client/MidiFunctionBlockDiscoveryRequestsEnum.idl b/src/api/Client/Midi2Client/MidiFunctionBlockDiscoveryRequestsEnum.idl index 8cc817dd..69a8535a 100644 --- a/src/api/Client/Midi2Client/MidiFunctionBlockDiscoveryRequestsEnum.idl +++ b/src/api/Client/Midi2Client/MidiFunctionBlockDiscoveryRequestsEnum.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [flags] enum MidiFunctionBlockDiscoveryRequests { diff --git a/src/api/Client/Midi2Client/MidiFunctionBlockMidi10Enum.idl b/src/api/Client/Midi2Client/MidiFunctionBlockMidi10Enum.idl index 0187a279..3480a813 100644 --- a/src/api/Client/Midi2Client/MidiFunctionBlockMidi10Enum.idl +++ b/src/api/Client/Midi2Client/MidiFunctionBlockMidi10Enum.idl @@ -15,7 +15,7 @@ namespace Windows.Devices.Midi2 // from the POV of the function block, not from the OS. IOW, // Input here means the device FB *receives* messages that // are sent through Windows - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiFunctionBlockMidi10 { Not10 = 0x0, diff --git a/src/api/Client/Midi2Client/MidiFunctionBlockUIHintEnum.idl b/src/api/Client/Midi2Client/MidiFunctionBlockUIHintEnum.idl index ddf3f90e..f32dd771 100644 --- a/src/api/Client/Midi2Client/MidiFunctionBlockUIHintEnum.idl +++ b/src/api/Client/Midi2Client/MidiFunctionBlockUIHintEnum.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiFunctionBlockUIHint { Unknown = 0x0, diff --git a/src/api/Client/Midi2Client/MidiGroup.idl b/src/api/Client/Midi2Client/MidiGroup.idl index c8ecdb77..c52ac730 100644 --- a/src/api/Client/Midi2Client/MidiGroup.idl +++ b/src/api/Client/Midi2Client/MidiGroup.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiGroup { diff --git a/src/api/Client/Midi2Client/MidiGroupEndpointListener.idl b/src/api/Client/Midi2Client/MidiGroupEndpointListener.idl index e7e66f34..5bf230f8 100644 --- a/src/api/Client/Midi2Client/MidiGroupEndpointListener.idl +++ b/src/api/Client/Midi2Client/MidiGroupEndpointListener.idl @@ -21,7 +21,7 @@ import "IMidiMessageReceivedEventSource.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiGroupEndpointListener : IMidiEndpointMessageProcessingPlugin, IMidiMessageReceivedEventSource diff --git a/src/api/Client/Midi2Client/MidiGroupTerminalBlock.idl b/src/api/Client/Midi2Client/MidiGroupTerminalBlock.idl index d82d7646..1535db5f 100644 --- a/src/api/Client/Midi2Client/MidiGroupTerminalBlock.idl +++ b/src/api/Client/Midi2Client/MidiGroupTerminalBlock.idl @@ -23,7 +23,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiGroupTerminalBlock { diff --git a/src/api/Client/Midi2Client/MidiGroupTerminalBlockDirectionEnum.idl b/src/api/Client/Midi2Client/MidiGroupTerminalBlockDirectionEnum.idl index 99832c87..ac6f3078 100644 --- a/src/api/Client/Midi2Client/MidiGroupTerminalBlockDirectionEnum.idl +++ b/src/api/Client/Midi2Client/MidiGroupTerminalBlockDirectionEnum.idl @@ -14,7 +14,7 @@ namespace Windows.Devices.Midi2 // NOTE: the values are different for function blocks and group terminal // blocks, so we have to different enumerations - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiGroupTerminalBlockDirection { Bidirectional = 0x0, diff --git a/src/api/Client/Midi2Client/MidiGroupTerminalBlockProtocolEnum.idl b/src/api/Client/Midi2Client/MidiGroupTerminalBlockProtocolEnum.idl index 9b5603b3..41213c14 100644 --- a/src/api/Client/Midi2Client/MidiGroupTerminalBlockProtocolEnum.idl +++ b/src/api/Client/Midi2Client/MidiGroupTerminalBlockProtocolEnum.idl @@ -18,7 +18,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiGroupTerminalBlockProtocol { Unknown = 0x00, diff --git a/src/api/Client/Midi2Client/MidiMessage128.idl b/src/api/Client/Midi2Client/MidiMessage128.idl index bf123ccf..01dc71a6 100644 --- a/src/api/Client/Midi2Client/MidiMessage128.idl +++ b/src/api/Client/Midi2Client/MidiMessage128.idl @@ -15,7 +15,7 @@ import "IMidiUniversalPacket.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] unsealed runtimeclass MidiMessage128 : IMidiUniversalPacket, Windows.Foundation.IStringable { MidiMessage128(); diff --git a/src/api/Client/Midi2Client/MidiMessage32.idl b/src/api/Client/Midi2Client/MidiMessage32.idl index aed69ca4..2cf37c3f 100644 --- a/src/api/Client/Midi2Client/MidiMessage32.idl +++ b/src/api/Client/Midi2Client/MidiMessage32.idl @@ -15,7 +15,7 @@ import "IMidiUniversalPacket.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] unsealed runtimeclass MidiMessage32 : IMidiUniversalPacket, Windows.Foundation.IStringable { MidiMessage32(); diff --git a/src/api/Client/Midi2Client/MidiMessage64.idl b/src/api/Client/Midi2Client/MidiMessage64.idl index d1f37dd3..2b536f75 100644 --- a/src/api/Client/Midi2Client/MidiMessage64.idl +++ b/src/api/Client/Midi2Client/MidiMessage64.idl @@ -15,7 +15,7 @@ import "IMidiUniversalPacket.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] unsealed runtimeclass MidiMessage64 : IMidiUniversalPacket, Windows.Foundation.IStringable { MidiMessage64(); diff --git a/src/api/Client/Midi2Client/MidiMessage96.idl b/src/api/Client/Midi2Client/MidiMessage96.idl index 2cc16cef..6ac074b8 100644 --- a/src/api/Client/Midi2Client/MidiMessage96.idl +++ b/src/api/Client/Midi2Client/MidiMessage96.idl @@ -15,7 +15,7 @@ import "IMidiUniversalPacket.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] unsealed runtimeclass MidiMessage96 : IMidiUniversalPacket, Windows.Foundation.IStringable { MidiMessage96(); diff --git a/src/api/Client/Midi2Client/MidiMessageBuilder.idl b/src/api/Client/Midi2Client/MidiMessageBuilder.idl index 32a498f0..16df61c8 100644 --- a/src/api/Client/Midi2Client/MidiMessageBuilder.idl +++ b/src/api/Client/Midi2Client/MidiMessageBuilder.idl @@ -29,7 +29,7 @@ import "Midi2ChannelVoiceMessageStatusEnum.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] static runtimeclass MidiMessageBuilder { // parameters are named, where applicable, for the fields as defined in the spec. diff --git a/src/api/Client/Midi2Client/MidiMessageConverter.idl b/src/api/Client/Midi2Client/MidiMessageConverter.idl index 3743ff21..22b5961e 100644 --- a/src/api/Client/Midi2Client/MidiMessageConverter.idl +++ b/src/api/Client/Midi2Client/MidiMessageConverter.idl @@ -24,7 +24,7 @@ import "Midi2ChannelVoiceMessageStatusEnum.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] static runtimeclass MidiMessageConverter { // for migrating from raw bytes diff --git a/src/api/Client/Midi2Client/MidiMessageReceivedEventArgs.idl b/src/api/Client/Midi2Client/MidiMessageReceivedEventArgs.idl index d8eb6568..63d8e022 100644 --- a/src/api/Client/Midi2Client/MidiMessageReceivedEventArgs.idl +++ b/src/api/Client/Midi2Client/MidiMessageReceivedEventArgs.idl @@ -24,7 +24,7 @@ import "MidiMessageStruct.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiMessageReceivedEventArgs { diff --git a/src/api/Client/Midi2Client/MidiMessageStruct.idl b/src/api/Client/Midi2Client/MidiMessageStruct.idl index f0a54727..0db39151 100644 --- a/src/api/Client/Midi2Client/MidiMessageStruct.idl +++ b/src/api/Client/Midi2Client/MidiMessageStruct.idl @@ -15,7 +15,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] struct MidiMessageStruct { UInt32 Word0; diff --git a/src/api/Client/Midi2Client/MidiMessageTypeEndpointListener.idl b/src/api/Client/Midi2Client/MidiMessageTypeEndpointListener.idl index d9b82cc4..f22178df 100644 --- a/src/api/Client/Midi2Client/MidiMessageTypeEndpointListener.idl +++ b/src/api/Client/Midi2Client/MidiMessageTypeEndpointListener.idl @@ -22,7 +22,7 @@ import "IMidiMessageReceivedEventSource.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiMessageTypeEndpointListener : IMidiEndpointMessageProcessingPlugin, IMidiMessageReceivedEventSource diff --git a/src/api/Client/Midi2Client/MidiMessageTypeEnum.idl b/src/api/Client/Midi2Client/MidiMessageTypeEnum.idl index 1fecbcb3..da6a7090 100644 --- a/src/api/Client/Midi2Client/MidiMessageTypeEnum.idl +++ b/src/api/Client/Midi2Client/MidiMessageTypeEnum.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiMessageType { UtilityMessage32 = 0x0, diff --git a/src/api/Client/Midi2Client/MidiMessageUtility.idl b/src/api/Client/Midi2Client/MidiMessageUtility.idl index d619fb62..a1113de3 100644 --- a/src/api/Client/Midi2Client/MidiMessageUtility.idl +++ b/src/api/Client/Midi2Client/MidiMessageUtility.idl @@ -25,7 +25,7 @@ import "Midi2ChannelVoiceMessageStatusEnum.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] static runtimeclass MidiMessageUtility { static Boolean ValidateMessage32MessageType(UInt32 word0); diff --git a/src/api/Client/Midi2Client/MidiPacketTypeEnum.idl b/src/api/Client/Midi2Client/MidiPacketTypeEnum.idl index 1deaf397..eac18663 100644 --- a/src/api/Client/Midi2Client/MidiPacketTypeEnum.idl +++ b/src/api/Client/Midi2Client/MidiPacketTypeEnum.idl @@ -13,7 +13,7 @@ namespace Windows.Devices.Midi2 { // can be cast to the size in MIDI Words. - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiPacketType { UnknownOrInvalid = 0, diff --git a/src/api/Client/Midi2Client/MidiProtocolEnum.idl b/src/api/Client/Midi2Client/MidiProtocolEnum.idl index 85871265..4127deb7 100644 --- a/src/api/Client/Midi2Client/MidiProtocolEnum.idl +++ b/src/api/Client/Midi2Client/MidiProtocolEnum.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiProtocol { Default = 0, diff --git a/src/api/Client/Midi2Client/MidiSendMessageResultsEnum.idl b/src/api/Client/Midi2Client/MidiSendMessageResultsEnum.idl index 16ef76f0..da10a453 100644 --- a/src/api/Client/Midi2Client/MidiSendMessageResultsEnum.idl +++ b/src/api/Client/Midi2Client/MidiSendMessageResultsEnum.idl @@ -14,7 +14,7 @@ namespace Windows.Devices.Midi2 // Yes, this is effectively recreating the HRESULT, but // our API standards do not allow creating and exposing // custom HRESULTs in public APIs - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [flags] enum MidiSendMessageResults { diff --git a/src/api/Client/Midi2Client/MidiService.idl b/src/api/Client/Midi2Client/MidiService.idl index f3d960c9..c8d3444a 100644 --- a/src/api/Client/Midi2Client/MidiService.idl +++ b/src/api/Client/Midi2Client/MidiService.idl @@ -24,7 +24,7 @@ import "MidiServiceConfigurationResponse.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] static runtimeclass MidiService { diff --git a/src/api/Client/Midi2Client/MidiServiceConfigurationResponse.idl b/src/api/Client/Midi2Client/MidiServiceConfigurationResponse.idl index 3e194ce1..95a8aca2 100644 --- a/src/api/Client/Midi2Client/MidiServiceConfigurationResponse.idl +++ b/src/api/Client/Midi2Client/MidiServiceConfigurationResponse.idl @@ -13,7 +13,7 @@ import "MidiServiceConfigurationResponseStatusEnum.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiServiceConfigurationResponse { diff --git a/src/api/Client/Midi2Client/MidiServiceConfigurationResponseStatusEnum.idl b/src/api/Client/Midi2Client/MidiServiceConfigurationResponseStatusEnum.idl index 02503eb0..131655d7 100644 --- a/src/api/Client/Midi2Client/MidiServiceConfigurationResponseStatusEnum.idl +++ b/src/api/Client/Midi2Client/MidiServiceConfigurationResponseStatusEnum.idl @@ -14,7 +14,7 @@ namespace Windows.Devices.Midi2 // NOTE: the values are different for function blocks and group terminal // blocks, so we have to different enumerations - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiServiceConfigurationResponseStatus { Success = 0, diff --git a/src/api/Client/Midi2Client/MidiServiceLoopbackEndpointCreationResult.idl b/src/api/Client/Midi2Client/MidiServiceLoopbackEndpointCreationResult.idl index 5e9e9955..202664d1 100644 --- a/src/api/Client/Midi2Client/MidiServiceLoopbackEndpointCreationResult.idl +++ b/src/api/Client/Midi2Client/MidiServiceLoopbackEndpointCreationResult.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiServiceLoopbackEndpointCreationResult { diff --git a/src/api/Client/Midi2Client/MidiServiceLoopbackEndpointDefinition.idl b/src/api/Client/Midi2Client/MidiServiceLoopbackEndpointDefinition.idl index 9c5c39b2..cbf55204 100644 --- a/src/api/Client/Midi2Client/MidiServiceLoopbackEndpointDefinition.idl +++ b/src/api/Client/Midi2Client/MidiServiceLoopbackEndpointDefinition.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiServiceLoopbackEndpointDefinition { diff --git a/src/api/Client/Midi2Client/MidiServiceMessageProcessingPluginInfo.idl b/src/api/Client/Midi2Client/MidiServiceMessageProcessingPluginInfo.idl index 7903c440..46c27c22 100644 --- a/src/api/Client/Midi2Client/MidiServiceMessageProcessingPluginInfo.idl +++ b/src/api/Client/Midi2Client/MidiServiceMessageProcessingPluginInfo.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiServiceMessageProcessingPluginInfo { diff --git a/src/api/Client/Midi2Client/MidiServicePingResponse.idl b/src/api/Client/Midi2Client/MidiServicePingResponse.idl index 00f1628e..61f1ce48 100644 --- a/src/api/Client/Midi2Client/MidiServicePingResponse.idl +++ b/src/api/Client/Midi2Client/MidiServicePingResponse.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiServicePingResponse { diff --git a/src/api/Client/Midi2Client/MidiServicePingResponseSummary.idl b/src/api/Client/Midi2Client/MidiServicePingResponseSummary.idl index 940c8d6c..b436682e 100644 --- a/src/api/Client/Midi2Client/MidiServicePingResponseSummary.idl +++ b/src/api/Client/Midi2Client/MidiServicePingResponseSummary.idl @@ -13,7 +13,7 @@ import "MidiServicePingResponse.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiServicePingResponseSummary { diff --git a/src/api/Client/Midi2Client/MidiServiceSessionConnectionInfo.idl b/src/api/Client/Midi2Client/MidiServiceSessionConnectionInfo.idl index 51ab3726..e5228964 100644 --- a/src/api/Client/Midi2Client/MidiServiceSessionConnectionInfo.idl +++ b/src/api/Client/Midi2Client/MidiServiceSessionConnectionInfo.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiServiceSessionConnectionInfo { diff --git a/src/api/Client/Midi2Client/MidiServiceSessionInfo.idl b/src/api/Client/Midi2Client/MidiServiceSessionInfo.idl index b8b0dc25..38679613 100644 --- a/src/api/Client/Midi2Client/MidiServiceSessionInfo.idl +++ b/src/api/Client/Midi2Client/MidiServiceSessionInfo.idl @@ -13,7 +13,7 @@ import "MidiServiceSessionConnectionInfo.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiServiceSessionInfo { diff --git a/src/api/Client/Midi2Client/MidiServiceTransportPluginInfo.idl b/src/api/Client/Midi2Client/MidiServiceTransportPluginInfo.idl index 408c1d8e..b1f315e0 100644 --- a/src/api/Client/Midi2Client/MidiServiceTransportPluginInfo.idl +++ b/src/api/Client/Midi2Client/MidiServiceTransportPluginInfo.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiServiceTransportPluginInfo { diff --git a/src/api/Client/Midi2Client/MidiSession.idl b/src/api/Client/Midi2Client/MidiSession.idl index 070f6ce4..36be5ad9 100644 --- a/src/api/Client/Midi2Client/MidiSession.idl +++ b/src/api/Client/Midi2Client/MidiSession.idl @@ -17,7 +17,7 @@ import "MidiVirtualEndpointDeviceDefinition.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiSession : Windows.Foundation.IClosable { diff --git a/src/api/Client/Midi2Client/MidiSessionSettings.idl b/src/api/Client/Midi2Client/MidiSessionSettings.idl index fef04d3c..512e7e86 100644 --- a/src/api/Client/Midi2Client/MidiSessionSettings.idl +++ b/src/api/Client/Midi2Client/MidiSessionSettings.idl @@ -12,7 +12,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiSessionSettings { diff --git a/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.cpp b/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.cpp new file mode 100644 index 00000000..63886a1f --- /dev/null +++ b/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.cpp @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + + +#include "pch.h" +#include "MidiStreamConfigurationRequestReceivedEventArgs.h" +#include "MidiStreamConfigurationRequestReceivedEventArgs.g.cpp" + +namespace winrt::Windows::Devices::Midi2::implementation +{ + _Use_decl_annotations_ + void MidiStreamConfigurationRequestReceivedEventArgs::InternalInitialize( + internal::MidiTimestamp timestamp, + midi2::MidiProtocol protocol, + bool requestReceiveJRTimestamps, + bool requestTransmitJRTimestamps + ) + { + m_timestamp = timestamp; + m_protocol = protocol; + m_requestReceiveJRTimestamps = requestReceiveJRTimestamps; + m_requestTransmitJRTimestamps = requestTransmitJRTimestamps; + } + +} diff --git a/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.h b/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.h new file mode 100644 index 00000000..2807cb62 --- /dev/null +++ b/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.h @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#pragma once +#include "MidiStreamConfigurationRequestReceivedEventArgs.g.h" + +namespace winrt::Windows::Devices::Midi2::implementation +{ + struct MidiStreamConfigurationRequestReceivedEventArgs : MidiStreamConfigurationRequestReceivedEventArgsT + { + MidiStreamConfigurationRequestReceivedEventArgs() = default; + + internal::MidiTimestamp Timestamp() { return m_timestamp; } + midi2::MidiProtocol PreferredMidiProtocol() { return m_protocol; } + bool RequestEndpointTransmitJitterReductionTimestamps() { return m_requestTransmitJRTimestamps; } + bool RequestEndpointReceiveJitterReductionTimestamps() { return m_requestReceiveJRTimestamps; } + + void InternalInitialize( + _In_ internal::MidiTimestamp timestamp, + _In_ midi2::MidiProtocol protocol, + _In_ bool requestReceiveJRTimestamps, + _In_ bool requestTransmitJRTimestamps + ); + + private: + internal::MidiTimestamp m_timestamp{ 0 }; + midi2::MidiProtocol m_protocol{ midi2::MidiProtocol::Default }; + bool m_requestTransmitJRTimestamps{ false }; + bool m_requestReceiveJRTimestamps{ false }; + + }; +} diff --git a/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.idl b/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.idl index bde8da9a..6bc63816 100644 --- a/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.idl +++ b/src/api/Client/Midi2Client/MidiStreamConfigurationRequestReceivedEventArgs.idl @@ -13,7 +13,7 @@ import "MidiProtocolEnum.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiStreamConfigurationRequestReceivedEventArgs { diff --git a/src/api/Client/Midi2Client/MidiStreamConfigurationRequestedSettings.idl b/src/api/Client/Midi2Client/MidiStreamConfigurationRequestedSettings.idl index a9adf5b2..b3987df4 100644 --- a/src/api/Client/Midi2Client/MidiStreamConfigurationRequestedSettings.idl +++ b/src/api/Client/Midi2Client/MidiStreamConfigurationRequestedSettings.idl @@ -13,7 +13,7 @@ import "MidiProtocolEnum.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiStreamConfigurationRequestedSettings { diff --git a/src/api/Client/Midi2Client/MidiStreamMessageBuilder.idl b/src/api/Client/Midi2Client/MidiStreamMessageBuilder.idl index 82c31608..ceb616ce 100644 --- a/src/api/Client/Midi2Client/MidiStreamMessageBuilder.idl +++ b/src/api/Client/Midi2Client/MidiStreamMessageBuilder.idl @@ -21,7 +21,7 @@ import "MidiFunctionBlockDiscoveryRequestsEnum.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] static runtimeclass MidiStreamMessageBuilder { static IMidiUniversalPacket BuildEndpointDiscoveryMessage( diff --git a/src/api/Client/Midi2Client/MidiSystemExclusive8StatusEnum.idl b/src/api/Client/Midi2Client/MidiSystemExclusive8StatusEnum.idl index 371c0ff5..c5db819b 100644 --- a/src/api/Client/Midi2Client/MidiSystemExclusive8StatusEnum.idl +++ b/src/api/Client/Midi2Client/MidiSystemExclusive8StatusEnum.idl @@ -11,7 +11,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] enum MidiSystemExclusive8Status { CompleteMessageInSingleMessagePacket = 0x0, diff --git a/src/api/Client/Midi2Client/MidiUniqueId.idl b/src/api/Client/Midi2Client/MidiUniqueId.idl index ffd4c843..cfd5b9ac 100644 --- a/src/api/Client/Midi2Client/MidiUniqueId.idl +++ b/src/api/Client/Midi2Client/MidiUniqueId.idl @@ -19,7 +19,7 @@ MIDI_IDL_IMPORT namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] runtimeclass MidiUniqueId { // byte 1 is LSB according to spec diff --git a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.cpp b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.cpp index b046843f..0dad0384 100644 --- a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.cpp +++ b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.cpp @@ -14,26 +14,73 @@ namespace winrt::Windows::Devices::Midi2::implementation { - - _Use_decl_annotations_ - void MidiVirtualEndpointDevice::UpdateFunctionBlock(midi2::MidiFunctionBlock const& block) noexcept + bool MidiVirtualEndpointDevice::UpdateFunctionBlock(midi2::MidiFunctionBlock const& block) noexcept { internal::LogInfo(__FUNCTION__, L"Enter"); - UNREFERENCED_PARAMETER(block); + // If blocks are static, return false. By spec, they are not allowed to be updated. + if (m_virtualEndpointDeviceDefinition.AreFunctionBlocksStatic()) + { + internal::LogGeneralError(__FUNCTION__, L"Attempt to update static function blocks in a virtual device."); + return false; + } + + // check to see if this is an existing block number. If not, fail. Devices are + // not allowed to change the number of function blocks they have, per the MIDI 2 spec + if (!m_functionBlocks.HasKey(block.Number())) + { + internal::LogGeneralError(__FUNCTION__, L"Attempt to update a function block which wasn't declared up-front."); + return false; + } + + // Update the block and send the notification messages + if (SendFunctionBlockInfoNotificationMessage(block)) + { + // this ignores that the name may not be set. It's an oversight in the block object design. + m_functionBlocks.Insert(block.Number(), block); + } + else + { + internal::LogGeneralError(__FUNCTION__, L"Error sending function block info notification message."); + return false; + } + + // check to see if the function block name changed. If so, send out a name notification + + auto existingBlock = m_functionBlocks.Lookup(block.Number()); + + if (existingBlock.Name() != block.Name()) + { + if (!SendFunctionBlockNameNotificationMessages(block)) + { + internal::LogGeneralError(__FUNCTION__, L"Error sending function block name notification messages."); + return false; + } + } - // TODO + return true; } _Use_decl_annotations_ - void MidiVirtualEndpointDevice::UpdateEndpointName(winrt::hstring const& name) noexcept + bool MidiVirtualEndpointDevice::UpdateEndpointName(winrt::hstring const& name) noexcept { internal::LogInfo(__FUNCTION__, L"Enter"); - UNREFERENCED_PARAMETER(name); + auto cleanedName = internal::TrimmedHStringCopy(name); + + // TODO: update the name and send the notification message - // TODO + if (m_endpointName != cleanedName) + { + if (!SendEndpointNameNotificationMessages(cleanedName)) + { + internal::LogGeneralError(__FUNCTION__, L"Error sending endpoint name notification messages."); + return false; + } + } + + return true; } @@ -60,7 +107,7 @@ namespace winrt::Windows::Devices::Midi2::implementation } _Use_decl_annotations_ - void MidiVirtualEndpointDevice::SendFunctionBlockInfoNotificationMessage(midi2::MidiFunctionBlock const& fb) noexcept + bool MidiVirtualEndpointDevice::SendFunctionBlockInfoNotificationMessage(midi2::MidiFunctionBlock const& fb) noexcept { internal::LogInfo(__FUNCTION__, L"Enter"); @@ -77,19 +124,21 @@ namespace winrt::Windows::Devices::Midi2::implementation fb.MaxSystemExclusive8Streams() ); - // TODO: Log if failed if (midi2::MidiEndpointConnection::SendMessageFailed(m_endpointConnection.SendSingleMessagePacket(functionBlockNotification))) { internal::LogGeneralError(__FUNCTION__, L"SendMessagePacket failed"); + return false; } + + return true; } _Use_decl_annotations_ - void MidiVirtualEndpointDevice::SendFunctionBlockNameNotificationMessages(midi2::MidiFunctionBlock const& fb) noexcept + bool MidiVirtualEndpointDevice::SendFunctionBlockNameNotificationMessages(midi2::MidiFunctionBlock const& fb) noexcept { internal::LogInfo(__FUNCTION__, L"Enter"); - if (fb.Name() == L"") return; + if (fb.Name().empty()) return false; auto nameMessages = midi2::MidiStreamMessageBuilder::BuildFunctionBlockNameNotificationMessages( MidiClock::TimestampConstantSendImmediately(), @@ -99,8 +148,32 @@ namespace winrt::Windows::Devices::Midi2::implementation if (midi2::MidiEndpointConnection::SendMessageFailed(m_endpointConnection.SendMultipleMessagesPacketList(nameMessages.GetView()))) { - internal::LogGeneralError(__FUNCTION__, L"SendMessagePacketList failed"); + internal::LogGeneralError(__FUNCTION__, L"SendMultipleMessagesPacketList failed"); + return false; + } + + return true; + } + + _Use_decl_annotations_ + bool MidiVirtualEndpointDevice::SendEndpointNameNotificationMessages(winrt::hstring const& name) noexcept + { + internal::LogInfo(__FUNCTION__, L"Enter"); + + if (name.empty()) return false; + + auto nameMessages = midi2::MidiStreamMessageBuilder::BuildEndpointNameNotificationMessages( + MidiClock::TimestampConstantSendImmediately(), + name + ); + + if (midi2::MidiEndpointConnection::SendMessageFailed(m_endpointConnection.SendMultipleMessagesPacketList(nameMessages.GetView()))) + { + internal::LogGeneralError(__FUNCTION__, L"SendMultipleMessagesPacketList failed"); + return false; } + + return true; } @@ -126,7 +199,7 @@ namespace winrt::Windows::Devices::Midi2::implementation if (internal::EndpointDiscoveryFilterRequestsEndpointInfoNotification(filterFlags)) { - internal::LogInfo(__FUNCTION__, L"Sending Endpoint Info Notification"); + internal::LogInfo(__FUNCTION__, L"Building/Sending Endpoint Info Notification"); // send endpoint info notification @@ -136,26 +209,51 @@ namespace winrt::Windows::Devices::Midi2::implementation MIDI_PREFERRED_UMP_VERSION_MINOR, m_areFunctionBlocksStatic, (uint8_t)m_functionBlocks.Size(), - true, // TODO: Pull from properties supports midi 2.0 - true, // TODO: pull from properties supports midi 1.0 - false, // todo: pull from default JR timestamp handling - false // todo: pull from jr timestamp handling + m_virtualEndpointDeviceDefinition.SupportsMidi2ProtocolMessages(), + m_virtualEndpointDeviceDefinition.SupportsMidi1ProtocolMessages(), + m_virtualEndpointDeviceDefinition.SupportsReceivingJRTimestamps(), + m_virtualEndpointDeviceDefinition.SupportsSendingJRTimestamps() ); if (midi2::MidiEndpointConnection::SendMessageFailed(m_endpointConnection.SendSingleMessagePacket(notification))) { - internal::LogGeneralError(__FUNCTION__, L"SendMessagePacket failed - sending endpoint info notification"); + internal::LogGeneralError(__FUNCTION__, L"SendSingleMessagePacket failed - sending endpoint info notification"); } } if (internal::EndpointDiscoveryFilterRequestsDeviceIdentityNotification(filterFlags)) { + internal::LogInfo(__FUNCTION__, L"Building/Sending Device Identity Notification"); + // send device identity notification + + // TODO: Need to validate no indexes out of bounds here + + auto identityNotification = midi2::MidiStreamMessageBuilder::BuildDeviceIdentityNotificationMessage( + MidiClock::TimestampConstantSendImmediately(), + m_virtualEndpointDeviceDefinition.DeviceManufacturerSystemExclusiveId().GetAt(0), // byte 1 + m_virtualEndpointDeviceDefinition.DeviceManufacturerSystemExclusiveId().GetAt(1), // byte 2 + m_virtualEndpointDeviceDefinition.DeviceManufacturerSystemExclusiveId().GetAt(2), // byte 3 + m_virtualEndpointDeviceDefinition.DeviceFamilyLsb(), + m_virtualEndpointDeviceDefinition.DeviceFamilyMsb(), + m_virtualEndpointDeviceDefinition.DeviceFamilyModelLsb(), + m_virtualEndpointDeviceDefinition.DeviceFamilyModelMsb(), + m_virtualEndpointDeviceDefinition.SoftwareRevisionLevel().GetAt(0), // byte 1 + m_virtualEndpointDeviceDefinition.SoftwareRevisionLevel().GetAt(1), // byte 2 + m_virtualEndpointDeviceDefinition.SoftwareRevisionLevel().GetAt(2), // byte 3 + m_virtualEndpointDeviceDefinition.SoftwareRevisionLevel().GetAt(3) // byte 4 + ); + + if (midi2::MidiEndpointConnection::SendMessageFailed(m_endpointConnection.SendSingleMessagePacket(identityNotification))) + { + internal::LogGeneralError(__FUNCTION__, L"SendSingleMessagePacket failed - sending device identity notification"); + } + } if (internal::EndpointDiscoveryFilterRequestsEndpointNameNotification(filterFlags)) { - internal::LogInfo(__FUNCTION__, L"Sending Endpoint Name Notification"); + internal::LogInfo(__FUNCTION__, L"Building/Sending Endpoint Name Notification"); // send endpoint name notification messages @@ -168,14 +266,14 @@ namespace winrt::Windows::Devices::Midi2::implementation if (midi2::MidiEndpointConnection::SendMessageFailed(m_endpointConnection.SendMultipleMessagesPacketList(nameMessages.GetView()))) { - internal::LogGeneralError(__FUNCTION__, L"SendMessagePacketList failed - sending endpoint name notification list"); + internal::LogGeneralError(__FUNCTION__, L"SendMultipleMessagesPacketList failed - sending endpoint name notification list"); } } } if (internal::EndpointDiscoveryFilterRequestsProductInstanceIdNotification(filterFlags)) { - internal::LogInfo(__FUNCTION__, L"Sending Endpoint Product Instance Id Notification"); + internal::LogInfo(__FUNCTION__, L"Building/Sending Endpoint Product Instance Id Notification"); // send product instance id notification messages @@ -188,14 +286,34 @@ namespace winrt::Windows::Devices::Midi2::implementation if (midi2::MidiEndpointConnection::SendMessageFailed(m_endpointConnection.SendMultipleMessagesPacketList(instanceIdMessages.GetView()))) { - internal::LogGeneralError(__FUNCTION__, L"SendMessagePacketList failed - sending product instance id messages"); + internal::LogGeneralError(__FUNCTION__, L"SendMultipleMessagesPacketList failed - sending product instance id messages"); } } } if (internal::EndpointDiscoveryFilterRequestsStreamConfigurationNotification(filterFlags)) { - // send stream configuration message + internal::LogInfo(__FUNCTION__, L"Building/Sending Stream Configuration Notification"); + + uint8_t protocol{ (uint8_t)midi2::MidiProtocol::Midi1 }; + + if (m_virtualEndpointDeviceDefinition.SupportsMidi2ProtocolMessages()) + { + protocol = (uint8_t)midi2::MidiProtocol::Midi2; + } + + auto streamConfigurationNotification = midi2::MidiStreamMessageBuilder::BuildStreamConfigurationNotificationMessage( + MidiClock::TimestampConstantSendImmediately(), + protocol, + m_virtualEndpointDeviceDefinition.SupportsReceivingJRTimestamps(), + m_virtualEndpointDeviceDefinition.SupportsSendingJRTimestamps() + ); + + if (midi2::MidiEndpointConnection::SendMessageFailed(m_endpointConnection.SendSingleMessagePacket(streamConfigurationNotification))) + { + internal::LogGeneralError(__FUNCTION__, L"SendSingleMessagePacket failed - sending device identity notification"); + } + } } else if (internal::MessageIsFunctionBlockDiscoveryRequest(message.Word0())) @@ -245,13 +363,26 @@ namespace winrt::Windows::Devices::Midi2::implementation } } - else + else if (internal::MessageIsStreamConfigurationRequest(message.Word0())) { - // something else - } + // raise stream configuration request message + auto reqArgs = winrt::make_self(); + auto protocol = (midi2::MidiProtocol)MIDIWORDBYTE3(message.Word0()); + bool rxjr = (bool)((message.Word0() & 0x00000002) == 0x00000002); + bool txjr = (bool)((message.Word0() & 0x00000001) == 0x00000001); + reqArgs->InternalInitialize(message.Timestamp(), protocol, rxjr, txjr); + if (m_streamConfigurationRequestReceivedEvent) + { + m_streamConfigurationRequestReceivedEvent(*this, *reqArgs); + } + } + else + { + // something else + } } else { diff --git a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.h b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.h index a354901c..a04543e7 100644 --- a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.h +++ b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.h @@ -51,8 +51,8 @@ namespace winrt::Windows::Devices::Midi2::implementation bool AreFunctionBlocksStatic() { return m_areFunctionBlocksStatic; } collections::IMapView FunctionBlocks() noexcept { return m_functionBlocks.GetView(); } - void UpdateFunctionBlock(_In_ midi2::MidiFunctionBlock const& block) noexcept; - void UpdateEndpointName(_In_ winrt::hstring const& name) noexcept; + bool UpdateFunctionBlock(_In_ midi2::MidiFunctionBlock const& block) noexcept; + bool UpdateEndpointName(_In_ winrt::hstring const& name) noexcept; midi2::MidiVirtualEndpointDeviceDefinition DeviceDefinition() { return m_virtualEndpointDeviceDefinition; } @@ -72,9 +72,10 @@ namespace winrt::Windows::Devices::Midi2::implementation private: - void SendFunctionBlockInfoNotificationMessage(_In_ midi2::MidiFunctionBlock const& fb) noexcept; - void SendFunctionBlockNameNotificationMessages(_In_ midi2::MidiFunctionBlock const& fb) noexcept; - + bool SendFunctionBlockInfoNotificationMessage(_In_ midi2::MidiFunctionBlock const& fb) noexcept; + bool SendFunctionBlockNameNotificationMessages(_In_ midi2::MidiFunctionBlock const& fb) noexcept; + bool SendEndpointNameNotificationMessages(_In_ winrt::hstring const& name) noexcept; + //void MidiVirtualEndpointDevice::QueueWorker(); //void EnqueueOutgoingMessage(_In_ internal::PackedUmp128 const& message); diff --git a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.idl b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.idl index 586c8040..3924d5ee 100644 --- a/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.idl +++ b/src/api/Client/Midi2Client/MidiVirtualEndpointDevice.idl @@ -21,7 +21,7 @@ import "MidiVirtualEndpointDeviceDefinition.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiVirtualEndpointDevice : IMidiEndpointMessageProcessingPlugin { @@ -32,9 +32,9 @@ namespace Windows.Devices.Midi2 IMapView FunctionBlocks{ get; }; // only available if blocks are not static - void UpdateFunctionBlock(MidiFunctionBlock block); + Boolean UpdateFunctionBlock(MidiFunctionBlock block); - void UpdateEndpointName(String name); + Boolean UpdateEndpointName(String name); // true if you want the stream / function block etc. messages automatically handled // and then removed from the stream. False if the endpoint should still raise diff --git a/src/api/Client/Midi2Client/MidiVirtualEndpointDeviceDefinition.idl b/src/api/Client/Midi2Client/MidiVirtualEndpointDeviceDefinition.idl index 7a713355..93a6321e 100644 --- a/src/api/Client/Midi2Client/MidiVirtualEndpointDeviceDefinition.idl +++ b/src/api/Client/Midi2Client/MidiVirtualEndpointDeviceDefinition.idl @@ -15,7 +15,7 @@ import "MidiFunctionBlock.idl"; namespace Windows.Devices.Midi2 { - [MIDI_API_CONTRACT(1)] + [MIDI_API_CONTRACT(19)] [default_interface] runtimeclass MidiVirtualEndpointDeviceDefinition { diff --git a/src/api/Client/Midi2Client/Windows.Devices.Midi2.vcxproj b/src/api/Client/Midi2Client/Windows.Devices.Midi2.vcxproj index 2633b312..55f08a12 100644 --- a/src/api/Client/Midi2Client/Windows.Devices.Midi2.vcxproj +++ b/src/api/Client/Midi2Client/Windows.Devices.Midi2.vcxproj @@ -344,6 +344,7 @@ MidiStreamConfigurationRequestedSettings.idl + MidiStreamMessageBuilder.idl @@ -486,6 +487,7 @@ MidiStreamConfigurationRequestedSettings.idl + MidiStreamMessageBuilder.idl diff --git a/src/api/Client/Midi2Client/Windows.Devices.Midi2.vcxproj.filters b/src/api/Client/Midi2Client/Windows.Devices.Midi2.vcxproj.filters index 6135e66d..772ca9a3 100644 --- a/src/api/Client/Midi2Client/Windows.Devices.Midi2.vcxproj.filters +++ b/src/api/Client/Midi2Client/Windows.Devices.Midi2.vcxproj.filters @@ -201,6 +201,9 @@ API\Enumeration\DeviceInformation + + API\Endpoints\Message Processing Plugins\Stock Plugins\Virtual Device + @@ -297,6 +300,9 @@ API\Enumeration\DeviceInformation + + API\Endpoints\Message Processing Plugins\Stock Plugins\Virtual Device + diff --git a/src/api/Client/Midi2Client/pch.h b/src/api/Client/Midi2Client/pch.h index e7918737..78cf393a 100644 --- a/src/api/Client/Midi2Client/pch.h +++ b/src/api/Client/Midi2Client/pch.h @@ -115,6 +115,7 @@ namespace midi2 = ::winrt::Windows::Devices::Midi2; #include "MidiEndpointDeviceInformationRemovedEventArgs.h" #include "MidiEndpointDeviceWatcher.h" +#include "MidiStreamConfigurationRequestReceivedEventArgs.h" #include "MidiVirtualEndpointDevice.h" #include "MidiVirtualEndpointDeviceDefinition.h" diff --git a/src/api/Client/Midi2Client/trace_logging.cpp b/src/api/Client/Midi2Client/trace_logging.cpp index d5834efa..17ff21a5 100644 --- a/src/api/Client/Midi2Client/trace_logging.cpp +++ b/src/api/Client/Midi2Client/trace_logging.cpp @@ -77,7 +77,7 @@ namespace Windows::Devices::Midi2::Internal const wchar_t* message, winrt::hresult_error const& ex) noexcept { - OutputDebugString(L"" __FUNCTION__ L"API HRESULT Error. Use tracing provider for details."); + //OutputDebugString(L"" __FUNCTION__ L"API HRESULT Error. Use tracing provider for details."); if (!g_traceLoggingRegistered) RegisterTraceLogging(); @@ -98,13 +98,14 @@ namespace Windows::Devices::Midi2::Internal 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(); TraceLoggingWrite( g_hLoggingProvider, "MIDI.GeneralError", + TraceLoggingOpcode(ERROR), TraceLoggingLevel(WINEVENT_LEVEL_ERROR), TraceLoggingKeyword(TRACE_KEYWORD_API_GENERAL), TraceLoggingString(location, "Location"), diff --git a/src/api/InBoxApps/mididmp/main.cpp b/src/api/InBoxApps/mididmp/main.cpp index 4878248c..c4374b43 100644 --- a/src/api/InBoxApps/mididmp/main.cpp +++ b/src/api/InBoxApps/mididmp/main.cpp @@ -135,19 +135,10 @@ void OutputError(_In_ std::wstring errorMessage) #define RETURN_SUCCESS return 0 #define RETURN_FAIL return 1 -int __cdecl main() -{ - winrt::init_apartment(); - - bool verbose = true; - bool pingTest = true; - bool midiClock = true; - - OutputHeader(L"Microsoft Windows MIDI Services"); - - OutputSectionHeader(L"header"); - OutputCurrentTime(); +bool DoSectionTransports(_In_ bool verbose) +{ + UNREFERENCED_PARAMETER(verbose); try { @@ -169,112 +160,239 @@ int __cdecl main() else { OutputError(L"Enumerating transports returned no matches. This is not expected and indicates an installation problem or that the service is not running."); - RETURN_FAIL; + return false; } + } + catch (...) + { + OutputError(L"Exception enumerating transports."); + return false; + } + + return true; +} - OutputSectionHeader(L"enum_endpoints"); +bool DoSectionMidi2ApiEndpoints(_In_ bool verbose) +{ + OutputSectionHeader(L"enum_ump_api_endpoints"); - // list devices + // list devices - collections::IVectorView devices{ nullptr }; + collections::IVectorView devices{ nullptr }; - try + try + { + // list all devices + devices = midi2::MidiEndpointDeviceInformation::FindAll( + midi2::MidiEndpointDeviceInformationSortOrder::Name, + midi2::MidiEndpointDeviceInformationFilters::IncludeClientByteStreamNative | + midi2::MidiEndpointDeviceInformationFilters::IncludeClientUmpNative | + midi2::MidiEndpointDeviceInformationFilters::IncludeDiagnosticLoopback | + midi2::MidiEndpointDeviceInformationFilters::IncludeDiagnosticPing | + midi2::MidiEndpointDeviceInformationFilters::IncludeVirtualDeviceResponder + ); + } + catch (...) + { + OutputError(L"Unable to access Windows MIDI Services and enumerate devices."); + return false; + } + + if (devices != nullptr && devices.Size() > 0) + { + for (uint32_t i = 0; i < devices.Size(); i++) { - // list all devices - devices = midi2::MidiEndpointDeviceInformation::FindAll( - midi2::MidiEndpointDeviceInformationSortOrder::Name, - midi2::MidiEndpointDeviceInformationFilters::IncludeClientByteStreamNative | - midi2::MidiEndpointDeviceInformationFilters::IncludeClientUmpNative | - midi2::MidiEndpointDeviceInformationFilters::IncludeDiagnosticLoopback | - midi2::MidiEndpointDeviceInformationFilters::IncludeDiagnosticPing | - midi2::MidiEndpointDeviceInformationFilters::IncludeVirtualDeviceResponder - ); + auto device = devices.GetAt(i); + + // These names should not be localized because customers may parse these output fields + + OutputStringField(L"endpoint_device_id", device.Id()); + OutputStringField(L"name", device.Name()); + OutputStringField(L"transport_mnemonic", device.TransportMnemonic()); + + if (verbose) + { + OutputStringField(L"name_user_supplied", device.UserSuppliedName()); + OutputStringField(L"name_endpoint_supplied", device.EndpointSuppliedName()); + OutputStringField(L"name_transport_supplied", device.TransportSuppliedName()); + OutputStringField(L"description_transport_supplied", device.TransportSuppliedDescription()); + OutputStringField(L"description_user_supplied", device.UserSuppliedDescription()); + } + + auto parent = device.GetParentDeviceInformation(); + + if (parent != nullptr) + { + OutputStringField(L"parent_id", parent.Id()); + OutputStringField(L"parent_name", parent.Name()); + } + else + { + OutputError(L"Unable to find endpoint parent"); + } + + if (i != devices.Size() - 1) + { + OutputItemSeparator(); + } } - catch (...) + } + else + { + OutputError(L"Enumerating devices returned no matches. This is not expected and indicates an installation problem or that the service is not running."); + return false; + } + + return true; +} + +bool DoSectionMidi1ApiEndpoints(_In_ bool verbose) +{ + UNREFERENCED_PARAMETER(verbose); + + OutputSectionHeader(L"enum_midi1_api_input_endpoints"); + + try + { + // inputs + auto midi1Inputs = winrt::Windows::Devices::Enumeration::DeviceInformation::FindAllAsync( + winrt::Windows::Devices::Midi::MidiInPort::GetDeviceSelector()).get(); + + for (uint32_t i = 0; i < midi1Inputs.Size(); i++) { - OutputError(L"Unable to access Windows MIDI Services and enumerate devices."); + auto device = midi1Inputs.GetAt(i); + + OutputStringField(L"endpoint_device_id", device.Id()); + OutputStringField(L"name", device.Name()); - RETURN_FAIL; + if (i != midi1Inputs.Size() - 1) + { + OutputItemSeparator(); + } } + } + catch (...) + { + OutputError(L"Enumerating MIDI 1.0 devices encountered an exception."); - if (devices != nullptr && devices.Size() > 0) + return false; + } + + OutputSectionHeader(L"enum_midi1_api_output_endpoints"); + + try + {// outputs + auto midi1Outputs = winrt::Windows::Devices::Enumeration::DeviceInformation::FindAllAsync( + winrt::Windows::Devices::Midi::MidiOutPort::GetDeviceSelector()).get(); + + for (uint32_t i = 0; i < midi1Outputs.Size(); i++) { - for (uint32_t i = 0; i < devices.Size(); i++) + auto device = midi1Outputs.GetAt(i); + + OutputStringField(L"endpoint_device_id", device.Id()); + OutputStringField(L"name", device.Name()); + + if (i != midi1Outputs.Size() - 1) { - auto device = devices.GetAt(i); - - // These names should not be localized because customers may parse these output fields - - OutputStringField(L"endpoint_device_id", device.Id()); - OutputStringField(L"name", device.Name()); - OutputStringField(L"transport_mnemonic", device.TransportMnemonic()); - - if (verbose) - { - OutputStringField(L"name_user_supplied", device.UserSuppliedName()); - OutputStringField(L"name_endpoint_supplied", device.EndpointSuppliedName()); - OutputStringField(L"name_transport_supplied", device.TransportSuppliedName()); - OutputStringField(L"description_transport_supplied", device.TransportSuppliedDescription()); - OutputStringField(L"description_user_supplied", device.UserSuppliedDescription()); - } - - auto parent = device.GetParentDeviceInformation(); - - if (parent != nullptr) - { - OutputStringField(L"parent_id", parent.Id()); - OutputStringField(L"parent_name", parent.Name()); - } - else - { - OutputError(L"Unable to find endpoint parent"); - } - - if (i != devices.Size() - 1) - { - OutputItemSeparator(); - } + OutputItemSeparator(); } } + } + catch (...) + { + OutputError(L"Enumerating MIDI 1.0 devices encountered an exception."); + + return false; + } +} + +bool DoSectionPingTest(_In_ bool verbose, _In_ uint8_t pingCount) +{ + try + { + UNREFERENCED_PARAMETER(verbose); + + OutputSectionHeader(L"ping_test"); + + OutputTimestampField(L"ping_attempt_count", pingCount); + + auto pingResult = midi2::MidiService::PingService(pingCount); + + OutputNumericField(L"ping_return_count", pingResult.Responses().Size()); + + if (pingResult.Success()) + { + OutputTimestampField(L"ping_time_round_trip_total_ticks", pingResult.TotalPingRoundTripMidiClock()); + OutputTimestampField(L"ping_time_round_trip_average_ticks", pingResult.AveragePingRoundTripMidiClock()); + + } else { - OutputError(L"Enumerating devices returned no matches. This is not expected and indicates an installation problem or that the service is not running."); - RETURN_FAIL; + OutputError(L"Ping test failed"); + OutputStringField(L"ping_failure_reason", pingResult.FailureReason()); + + return false; } + } + catch (...) + { + OutputError(L"Ping test failed with exception"); + return false; + } - const uint8_t pingCount = 10; + return true; +} - // ping the service - if (pingTest) - { - OutputSectionHeader(L"ping_test"); +bool DoSectionClock(_In_ bool verbose) +{ + OutputSectionHeader(L"midi_clock"); - OutputTimestampField(L"ping_attempt_count", pingCount); + OutputTimestampField(L"clock_frequency", midi2::MidiClock::TimestampFrequency()); + OutputTimestampField(L"clock_now", midi2::MidiClock::Now()); - auto pingResult = midi2::MidiService::PingService(pingCount); + return true; +} - OutputNumericField(L"ping_return_count", pingResult.Responses().Size()); +int __cdecl main() +{ + winrt::init_apartment(); - if (pingResult.Success()) - { - OutputTimestampField(L"ping_time_round_trip_total_ticks", pingResult.TotalPingRoundTripMidiClock()); - OutputTimestampField(L"ping_time_round_trip_average_ticks", pingResult.AveragePingRoundTripMidiClock()); + bool verbose = true; + bool pingTest = true; + bool midiClock = true; - } - else + OutputHeader(L"Microsoft Windows MIDI Services"); + + OutputSectionHeader(L"header"); + + OutputCurrentTime(); + + try + { + auto transportsWorked = DoSectionTransports(verbose); + + if (transportsWorked) + { + if (!DoSectionMidi2ApiEndpoints(verbose)) RETURN_FAIL; + } + + DoSectionMidi1ApiEndpoints(verbose); // we don't bail if this fails + + if (transportsWorked) + { + // ping the service + if (pingTest) { - OutputError(L"Ping test failed"); - OutputStringField(L"ping_failure_reason", pingResult.FailureReason()); + const uint8_t pingCount = 10; + + if (!DoSectionPingTest(verbose, pingCount)) RETURN_FAIL; } } if (midiClock) { - OutputSectionHeader(L"midi_clock"); - - OutputTimestampField(L"clock_frequency", midi2::MidiClock::TimestampFrequency()); - OutputTimestampField(L"clock_now", midi2::MidiClock::Now()); + if (!DoSectionClock(verbose)) RETURN_FAIL; } } @@ -285,9 +403,7 @@ int __cdecl main() RETURN_FAIL; } - OutputSectionHeader(L"end_of_file"); - RETURN_SUCCESS; } diff --git a/src/api/InBoxApps/mididmp/mididmp.vcxproj b/src/api/InBoxApps/mididmp/mididmp.vcxproj index 9c0ac0f1..73659d27 100644 --- a/src/api/InBoxApps/mididmp/mididmp.vcxproj +++ b/src/api/InBoxApps/mididmp/mididmp.vcxproj @@ -15,10 +15,18 @@ + + Debug + ARM64 + Debug Win32 + + Release + ARM64 + Release Win32 @@ -73,10 +81,18 @@ $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + Use @@ -92,6 +108,7 @@ Disabled _DEBUG;%(PreprocessorDefinitions) stdcpp20 + stdcpp20 Console @@ -112,8 +129,10 @@ NDEBUG;%(PreprocessorDefinitions) stdcpp20 stdcpp20 + stdcpp20 %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(IntDir); %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(IntDir); + %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(IntDir); Console diff --git a/src/api/InBoxApps/mididmp/pch.h b/src/api/InBoxApps/mididmp/pch.h index db747f2d..a4c871af 100644 --- a/src/api/InBoxApps/mididmp/pch.h +++ b/src/api/InBoxApps/mididmp/pch.h @@ -15,6 +15,7 @@ #include #include #include +#include #include diff --git a/src/api/Inc/ump_helpers.h b/src/api/Inc/ump_helpers.h index 422e6ec2..d45af137 100644 --- a/src/api/Inc/ump_helpers.h +++ b/src/api/Inc/ump_helpers.h @@ -49,6 +49,8 @@ #define MIDIWORDBYTE4LOWCRUMB4(x) (uint8_t)((x & 0x000000C0) >> 6) + + #define UMP32_WORD_COUNT 1 #define UMP64_WORD_COUNT 2 #define UMP96_WORD_COUNT 3 @@ -70,6 +72,60 @@ #define MIDI_MESSAGE_CHANNEL_BITSHIFT 16 +#define MIDI_UMP_MESSAGE_TYPE_UTILITY_MESSAGE_32 0x0 +#define MIDI_UMP_MESSAGE_TYPE_SYSTEM_COMMON_32 0x1 +#define MIDI_UMP_MESSAGE_TYPE_MIDI1_CHANNEL_VOICE_32 0x2 +#define MIDI_UMP_MESSAGE_TYPE_DATA_MESSAGE_64 0x3 +#define MIDI_UMP_MESSAGE_TYPE_MIDI2_CHANNEL_VOICE_64 0x4 +#define MIDI_UMP_MESSAGE_TYPE_DATA_MESSAGE_128 0x5 +#define MIDI_UMP_MESSAGE_TYPE_FLEX_DATA_128 0xD +#define MIDI_UMP_MESSAGE_TYPE_STREAM_128 0xF + +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_REG_PER_NOTE_CONTROLLER 0x0 +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_ASSIGN_PER_NOTE_CONTROLLER 0x1 +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_REG_CONTROLLER 0x2 +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_ASSIGN_CONTROLLER 0x3 +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_REL_REG_CONTROLLER 0x4 +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_REL_ASSIGN_CONTROLLER 0x5 +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_PER_NOTE_PITCH_BEND 0x6 +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_NOTE_OFF 0x8 +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_NOTE_ON 0x9 +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_POLY_PRESSURE 0xA +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_CONTROL_CHANGE 0xB +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_PROGRAM_CHANGE 0xC +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_CHANNEL_PRESSURE 0xD +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_PITCH_BEND 0xE +#define MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_PER_NOTE_MANAGEMENT 0xF + + +#define MIDI_UMP_MIDI1_CHANNEL_VOICE_STATUS_NOTE_OFF 0x8 +#define MIDI_UMP_MIDI1_CHANNEL_VOICE_STATUS_NOTE_ON 0x9 +#define MIDI_UMP_MIDI1_CHANNEL_VOICE_STATUS_POLY_PRESSURE 0xA +#define MIDI_UMP_MIDI1_CHANNEL_VOICE_STATUS_CONTROL_CHANGE 0xB +#define MIDI_UMP_MIDI1_CHANNEL_VOICE_STATUS_PROGRAM_CHANGE 0xC +#define MIDI_UMP_MIDI1_CHANNEL_VOICE_STATUS_CHANNEL_PRESSURE 0xD +#define MIDI_UMP_MIDI1_CHANNEL_VOICE_STATUS_PITCH_BEND 0xE + +#define MIDI_UMP_MIDI1_BANK_SELECT_MSB_CC_INDEX 0x00 +#define MIDI_UMP_MIDI1_BANK_SELECT_LSB_CC_INDEX 0x20 + + +//#define MIDI_RPN_PITCH_BEND_RANGE 0x0000 +//#define MIDI_RPN_COARSE_TUNING 0x0002 +//#define MIDI_RPN_TUNING_PROGRAM_CHANGE 0x0003 +//#define MIDI_RPN_TUNING_BANK_SELECT 0x0004 +//#define MIDI_RPN_MPE_MCM 0x0006 + +#define MIDI_RPN_CC_NUMBER_BANK 0x65 +#define MIDI_RPN_CC_NUMBER_INDEX 0x64 +#define MIDI_RPN_CC_NUMBER_DATA_COARSE 0x06 +#define MIDI_RPN_CC_NUMBER_DATA_FINE 0x26 + +#define MIDI_NRPN_CC_NUMBER_BANK 0x63 +#define MIDI_NRPN_CC_NUMBER_INDEX 0x62 +#define MIDI_NRPN_CC_NUMBER_DATA_COARSE 0x06 +#define MIDI_NRPN_CC_NUMBER_DATA_FINE 0x26 + namespace Windows::Devices::Midi2::Internal { @@ -312,6 +368,11 @@ namespace Windows::Devices::Midi2::Internal return (uint32_t)(byte0 << 24 | byte1 << 16 | byte2 << 8 | byte3); } + inline uint16_t Combine7BitMsbLsbTo14BitValue(_In_ uint8_t msb, _In_ uint8_t lsb) + { + return ((uint16_t)(CleanupByte7(msb)) << 7) | CleanupByte7(lsb); + } + inline void SetMidiWordMostSignificantByte1(_Inout_ uint32_t& word, _In_ uint8_t value) @@ -807,4 +868,6 @@ namespace Windows::Devices::Midi2::Internal + + } diff --git a/src/api/Midi2.sln b/src/api/Midi2.sln index f79b6c8b..8a863925 100644 --- a/src/api/Midi2.sln +++ b/src/api/Midi2.sln @@ -77,6 +77,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MidiSrv", "Service\Exe\Midi {7427BC7A-4247-42B0-AC9B-7DA10418AA9D} = {7427BC7A-4247-42B0-AC9B-7DA10418AA9D} {7E618284-6AA0-4FCE-9E4A-D895A5EE8E3C} = {7E618284-6AA0-4FCE-9E4A-D895A5EE8E3C} {8795821B-541D-4B9B-BF7F-50CA976FC54E} = {8795821B-541D-4B9B-BF7F-50CA976FC54E} + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB} = {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB} {EFB7CF90-7DEF-44CF-868A-191CA30E0FCF} = {EFB7CF90-7DEF-44CF-868A-191CA30E0FCF} EndProjectSection EndProject @@ -230,6 +231,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Midi2.LoopbackMidiAbstracti {EFB7CF90-7DEF-44CF-868A-191CA30E0FCF} = {EFB7CF90-7DEF-44CF-868A-191CA30E0FCF} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Midi2.UmpProtocolDownscalerTransform", "Transform\UmpProtocolDownscaler\Midi2.UmpProtocolDownscalerTransform.vcxproj", "{C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -958,8 +961,8 @@ Global {5EC6D5EB-53D4-4731-891E-F746F0201429}.Debug|x86.Build.0 = Debug|Win32 {5EC6D5EB-53D4-4731-891E-F746F0201429}.Release|Any CPU.ActiveCfg = Release|x64 {5EC6D5EB-53D4-4731-891E-F746F0201429}.Release|Any CPU.Build.0 = Release|x64 - {5EC6D5EB-53D4-4731-891E-F746F0201429}.Release|ARM64.ActiveCfg = Release|x64 - {5EC6D5EB-53D4-4731-891E-F746F0201429}.Release|ARM64.Build.0 = Release|x64 + {5EC6D5EB-53D4-4731-891E-F746F0201429}.Release|ARM64.ActiveCfg = Release|ARM64 + {5EC6D5EB-53D4-4731-891E-F746F0201429}.Release|ARM64.Build.0 = Release|ARM64 {5EC6D5EB-53D4-4731-891E-F746F0201429}.Release|ARM64EC.ActiveCfg = Release|x64 {5EC6D5EB-53D4-4731-891E-F746F0201429}.Release|ARM64EC.Build.0 = Release|x64 {5EC6D5EB-53D4-4731-891E-F746F0201429}.Release|x64.ActiveCfg = Release|x64 @@ -986,6 +989,26 @@ Global {0F94A751-9159-4A88-8A71-347151124548}.Release|x64.Build.0 = Release|x64 {0F94A751-9159-4A88-8A71-347151124548}.Release|x86.ActiveCfg = Release|x64 {0F94A751-9159-4A88-8A71-347151124548}.Release|x86.Build.0 = Release|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|Any CPU.ActiveCfg = Debug|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|Any CPU.Build.0 = Debug|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|ARM64.Build.0 = Debug|ARM64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|ARM64EC.Build.0 = Debug|ARM64EC + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|x64.ActiveCfg = Debug|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|x64.Build.0 = Debug|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|x86.ActiveCfg = Debug|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Debug|x86.Build.0 = Debug|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|Any CPU.ActiveCfg = Release|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|Any CPU.Build.0 = Release|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|ARM64.ActiveCfg = Release|ARM64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|ARM64.Build.0 = Release|ARM64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|ARM64EC.ActiveCfg = Release|ARM64EC + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|ARM64EC.Build.0 = Release|ARM64EC + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|x64.ActiveCfg = Release|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|x64.Build.0 = Release|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|x86.ActiveCfg = Release|x64 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}.Release|x86.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1031,6 +1054,7 @@ Global {16F34756-0269-4CEB-8F1C-32D9C6441990} = {6DB1D424-53D4-488F-8053-EBBD7D1F2E49} {5EC6D5EB-53D4-4731-891E-F746F0201429} = {67A42126-8502-4681-9ACC-B1417C527620} {0F94A751-9159-4A88-8A71-347151124548} = {69CC5CD9-47DC-4118-A3C7-E0D071407185} + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB} = {23C1D0EF-10F0-464B-86C3-1FCD3F6BA20E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7F5A157F-9894-451D-B091-848921952166} diff --git a/src/api/Midi2Setup/Midi2Setup.vdproj b/src/api/Midi2Setup/Midi2Setup.vdproj index 98940aaf..3fcd11a8 100644 --- a/src/api/Midi2Setup/Midi2Setup.vdproj +++ b/src/api/Midi2Setup/Midi2Setup.vdproj @@ -682,6 +682,12 @@ "Entry" { "MsmKey" = "8:_233E143DE3B73E4453F74EDD5B3ABEEC" + "OwnerKey" = "8:_52D7284C08974CD7BCE0BD610635A9EF" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_233E143DE3B73E4453F74EDD5B3ABEEC" "OwnerKey" = "8:_EF73BDE238574EA1B9C0A604958811F1" "MsmSig" = "8:_UNDEFINED" } @@ -796,6 +802,12 @@ "Entry" { "MsmKey" = "8:_276951EB6BDD2CD6AB8AE7556C7D8689" + "OwnerKey" = "8:_52D7284C08974CD7BCE0BD610635A9EF" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_276951EB6BDD2CD6AB8AE7556C7D8689" "OwnerKey" = "8:_EF73BDE238574EA1B9C0A604958811F1" "MsmSig" = "8:_UNDEFINED" } @@ -910,6 +922,12 @@ "Entry" { "MsmKey" = "8:_2C0A596B30D1A4DE8AC76BEFC0BF3415" + "OwnerKey" = "8:_52D7284C08974CD7BCE0BD610635A9EF" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_2C0A596B30D1A4DE8AC76BEFC0BF3415" "OwnerKey" = "8:_EF73BDE238574EA1B9C0A604958811F1" "MsmSig" = "8:_UNDEFINED" } @@ -2794,6 +2812,12 @@ "Entry" { "MsmKey" = "8:_4F91EF9506038C6EEBA69C5DDE51BF29" + "OwnerKey" = "8:_52D7284C08974CD7BCE0BD610635A9EF" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_4F91EF9506038C6EEBA69C5DDE51BF29" "OwnerKey" = "8:_EF73BDE238574EA1B9C0A604958811F1" "MsmSig" = "8:_UNDEFINED" } @@ -3454,6 +3478,12 @@ "Entry" { "MsmKey" = "8:_50D875E42CEF6E0475EAB4384E4C79E1" + "OwnerKey" = "8:_52D7284C08974CD7BCE0BD610635A9EF" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_50D875E42CEF6E0475EAB4384E4C79E1" "OwnerKey" = "8:_EF73BDE238574EA1B9C0A604958811F1" "MsmSig" = "8:_UNDEFINED" } @@ -3549,6 +3579,12 @@ } "Entry" { + "MsmKey" = "8:_52D7284C08974CD7BCE0BD610635A9EF" + "OwnerKey" = "8:_UNDEFINED" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { "MsmKey" = "8:_53F57B074B84A66BFAC43DAE75C45C7C" "OwnerKey" = "8:_0AA6EC5B722D4FDBA7D4F9D05F8643E3" "MsmSig" = "8:_UNDEFINED" @@ -3808,6 +3844,12 @@ "Entry" { "MsmKey" = "8:_95A010555EE939A8528D490FA44410C9" + "OwnerKey" = "8:_52D7284C08974CD7BCE0BD610635A9EF" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_95A010555EE939A8528D490FA44410C9" "OwnerKey" = "8:_EF73BDE238574EA1B9C0A604958811F1" "MsmSig" = "8:_UNDEFINED" } @@ -5134,6 +5176,12 @@ "Entry" { "MsmKey" = "8:_E386CA0AE72FBD122562EAE2A9B23CC9" + "OwnerKey" = "8:_52D7284C08974CD7BCE0BD610635A9EF" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_E386CA0AE72FBD122562EAE2A9B23CC9" "OwnerKey" = "8:_EF73BDE238574EA1B9C0A604958811F1" "MsmSig" = "8:_UNDEFINED" } @@ -10561,6 +10609,34 @@ { } } + "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_52D7284C08974CD7BCE0BD610635A9EF" + { + "SourcePath" = "8:..\\VSFiles\\x64\\Release\\Midi2.UmpProtocolDownscalerTransform.dll" + "TargetName" = "8:" + "Tag" = "8:" + "Folder" = "8:_1DC960FE630C406E9A1F93C99C1E099A" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Vital" = "11:TRUE" + "ReadOnly" = "11:FALSE" + "Hidden" = "11:FALSE" + "System" = "11:FALSE" + "Permanent" = "11:FALSE" + "SharedLegacy" = "11:FALSE" + "PackageAs" = "3:1" + "Register" = "3:1" + "Exclude" = "11:FALSE" + "IsDependency" = "11:FALSE" + "IsolateTo" = "8:" + "ProjectOutputGroupRegister" = "3:4" + "OutputConfiguration" = "8:" + "OutputGroupCanonicalName" = "8:Built" + "OutputProjectGuid" = "8:{C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB}" + "ShowKeyOutput" = "11:TRUE" + "ExcludeFilters" + { + } + } "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_5B95BDBA834E45B6BA7B73AC8260C86C" { "SourcePath" = "8:..\\VSFiles\\x64\\Release\\Midi2.BluetoothMidiAbstraction.dll" diff --git a/src/api/Service/Exe/MidiClientManager.cpp b/src/api/Service/Exe/MidiClientManager.cpp index 6e4b0eea..f904813d 100644 --- a/src/api/Service/Exe/MidiClientManager.cpp +++ b/src/api/Service/Exe/MidiClientManager.cpp @@ -48,8 +48,6 @@ CMidiClientManager::Cleanup() ); - OutputDebugString(L"" __FUNCTION__ " enter"); - auto lock = m_ClientManagerLock.lock(); m_PerformanceManager.reset(); @@ -76,51 +74,116 @@ CMidiClientManager::Cleanup() } m_DevicePipes.clear(); - OutputDebugString(L"" __FUNCTION__ " exit"); - return S_OK; } - HRESULT -GetEndpointShouldHaveMetadataHandler(_In_ std::wstring MidiDevice, _Inout_ bool& AddMetadataListener, _In_ MidiFlow& Flow) +GetEndpointRequiresOutboundProtocolDownscaling( + _In_ std::wstring const& MidiDevice, + _In_ MidiFlow const Flow, + _In_ MidiDataFormat const DeviceFormat, + _Inout_ bool& AddProtocolDownscaler) { - if (Flow == MidiFlow::MidiFlowBidirectional || Flow == MidiFlow::MidiFlowIn) + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingWideString(MidiDevice.c_str(), "Device Id") + ); + + // default to false + AddProtocolDownscaler = false; + + if (DeviceFormat == MidiDataFormat::MidiDataFormat_UMP && (Flow == MidiFlow::MidiFlowBidirectional || Flow == MidiFlow::MidiFlowOut)) { auto additionalProperties = winrt::single_threaded_vector(); - additionalProperties.Append(winrt::to_hstring(STRING_PKEY_MIDI_EndpointRequiresMetadataHandler)); - auto deviceInfo = DeviceInformation::CreateFromIdAsync(MidiDevice, additionalProperties, winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get(); + additionalProperties.Append(winrt::to_hstring(STRING_PKEY_MIDI_NativeDataFormat)); + + auto deviceInfo = DeviceInformation::CreateFromIdAsync( + MidiDevice, additionalProperties, + winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get(); - auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_EndpointRequiresMetadataHandler)); + auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_NativeDataFormat)); if (prop) { - OutputDebugString(__FUNCTION__ L" found property"); - // this interface is pointing to a UMP interface, so use that instance id. - AddMetadataListener = winrt::unbox_value(prop); - } - else - { - OutputDebugString(__FUNCTION__ L" did not find property"); + auto nativeFormat = winrt::unbox_value(prop); - // default to true - AddMetadataListener = true; + if (nativeFormat == MIDI_PROP_NATIVEDATAFORMAT_BYTESTREAM) + { + // Native bytestream behind a UMP driver, so yes, we need to downscale because the driver will just discard MT4 + + AddProtocolDownscaler = true; + } } } - else - { - // output-only flow - AddMetadataListener = false; - } + + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingWideString(MidiDevice.c_str(), "Device Id"), + TraceLoggingBool(AddProtocolDownscaler, "Requires downscaler") + ); return S_OK; } +//HRESULT +//GetEndpointShouldHaveMetadataHandler(_In_ std::wstring MidiDevice, _Inout_ bool& AddMetadataListener, _In_ MidiFlow Flow) +//{ +// TraceLoggingWrite( +// MidiSrvTelemetryProvider::Provider(), +// __FUNCTION__, +// TraceLoggingLevel(WINEVENT_LEVEL_INFO), +// TraceLoggingWideString(MidiDevice.c_str(), "Device Id") +// ); +// +// if (Flow == MidiFlow::MidiFlowBidirectional || Flow == MidiFlow::MidiFlowIn) +// { +// auto additionalProperties = winrt::single_threaded_vector(); +// additionalProperties.Append(winrt::to_hstring(STRING_PKEY_MIDI_EndpointRequiresMetadataHandler)); +// auto deviceInfo = DeviceInformation::CreateFromIdAsync(MidiDevice, additionalProperties, winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get(); +// +// auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_EndpointRequiresMetadataHandler)); +// if (prop) +// { +// // this interface is pointing to a UMP interface, so use that instance id. +// AddMetadataListener = winrt::unbox_value(prop); +// } +// else +// { +// // default to true +// AddMetadataListener = true; +// } +// } +// else +// { +// // output-only flow +// AddMetadataListener = false; +// } +// +// TraceLoggingWrite( +// MidiSrvTelemetryProvider::Provider(), +// __FUNCTION__, +// TraceLoggingLevel(WINEVENT_LEVEL_INFO), +// TraceLoggingWideString(MidiDevice.c_str(), "Device Id"), +// TraceLoggingBool(AddMetadataListener, "Add Metadata Listener") +// ); +// +// return S_OK; +//} + HRESULT GetDeviceSupportedDataFormat(_In_ std::wstring MidiDevice, _Inout_ MidiDataFormat& DataFormat) { - OutputDebugString(__FUNCTION__ L" enter"); + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingWideString(MidiDevice.c_str(), "Device Id") + ); auto additionalProperties = winrt::single_threaded_vector(); additionalProperties.Append(winrt::to_hstring(STRING_PKEY_MIDI_SupportedDataFormats)); @@ -129,9 +192,6 @@ GetDeviceSupportedDataFormat(_In_ std::wstring MidiDevice, _Inout_ MidiDataForma auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_SupportedDataFormats)); if (prop) { - - OutputDebugString(__FUNCTION__ L" found property"); - DataFormat = MidiDataFormat::MidiDataFormat_Any; try { @@ -142,12 +202,17 @@ GetDeviceSupportedDataFormat(_In_ std::wstring MidiDevice, _Inout_ MidiDataForma } else { - OutputDebugString(__FUNCTION__ L" didn't find property"); // default to any DataFormat = MidiDataFormat::MidiDataFormat_Any; } - OutputDebugString(__FUNCTION__ L" exiting OK"); + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingWideString(MidiDevice.c_str(), "Device Id"), + TraceLoggingBool(DataFormat, "MIDI Data Format") + ); return S_OK; } @@ -155,6 +220,13 @@ GetDeviceSupportedDataFormat(_In_ std::wstring MidiDevice, _Inout_ MidiDataForma HRESULT GetEndpointGenerateIncomingTimestamp(_In_ std::wstring MidiDevice, _Inout_ bool& GenerateIncomingTimestamp, _In_ MidiFlow& Flow) { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingWideString(MidiDevice.c_str(), "Device Id") + ); + // we only generate timestamps for incoming messages FROM the device if (Flow == MidiFlow::MidiFlowBidirectional || Flow == MidiFlow::MidiFlowIn) { @@ -180,14 +252,26 @@ GetEndpointGenerateIncomingTimestamp(_In_ std::wstring MidiDevice, _Inout_ bool& GenerateIncomingTimestamp = false; } + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingWideString(MidiDevice.c_str(), "Device Id"), + TraceLoggingBool(GenerateIncomingTimestamp, "Generate Incoming Timestamp") + ); + return S_OK; } HRESULT GetEndpointAlias(_In_ LPCWSTR MidiDevice, _In_ std::wstring& Alias, _In_ MidiFlow& AliasFlow) { - OutputDebugString(__FUNCTION__ L" enter. Device:"); - OutputDebugString(MidiDevice); + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingWideString(MidiDevice, "Device Id") + ); Alias = MidiDevice; @@ -195,15 +279,11 @@ GetEndpointAlias(_In_ LPCWSTR MidiDevice, _In_ std::wstring& Alias, _In_ MidiFlo additionalProperties.Append(winrt::to_hstring(STRING_PKEY_MIDI_AssociatedUMP)); auto deviceInfo = DeviceInformation::CreateFromIdAsync(MidiDevice, additionalProperties, winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get(); - OutputDebugString(__FUNCTION__ L" looking up prop"); auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_AssociatedUMP)); - OutputDebugString(__FUNCTION__ L" got prop. About to check for null"); if (prop) { - OutputDebugString(L"" __FUNCTION__ " STRING_PKEY_MIDI_AssociatedUMP property present"); - // this interface is pointing to a UMP interface, so use that instance id. Alias = winrt::unbox_value(prop).c_str(); @@ -226,9 +306,6 @@ GetEndpointAlias(_In_ LPCWSTR MidiDevice, _In_ std::wstring& Alias, _In_ MidiFlo std::transform(Alias.begin(), Alias.end(), Alias.begin(), ::towlower); - OutputDebugString(__FUNCTION__ L" exit. Alias:"); - OutputDebugString(Alias.c_str()); - return S_OK; } @@ -250,8 +327,8 @@ CMidiClientManager::GetMidiClient( __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), TraceLoggingPointer(this, "this"), - TraceLoggingWideString(MidiDevice), - TraceLoggingGuid(SessionId) + TraceLoggingWideString(MidiDevice, "Device Id"), + TraceLoggingGuid(SessionId, "Session Id") ); wil::com_ptr_nothrow clientPipe; @@ -285,7 +362,7 @@ CMidiClientManager::GetMidiDevice( __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), TraceLoggingPointer(this, "this"), - TraceLoggingWideString(MidiDevice) + TraceLoggingWideString(MidiDevice, "Device Id") ); // Get an existing device pipe if one exists, otherwise create a new pipe @@ -322,6 +399,79 @@ CMidiClientManager::GetMidiDevice( return S_OK; } + +_Use_decl_annotations_ +HRESULT +CMidiClientManager::GetMidiProtocolDownscalerTransform( + _In_ handle_t BindingHandle, + _In_ MidiFlow Flow, + _In_ wil::com_ptr_nothrow& DevicePipe, + _In_ wil::com_ptr_nothrow& NextDeviceSidePipe, + _In_ wil::com_ptr_nothrow& ClientConnectionPipe) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, DevicePipe); + + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(DevicePipe->MidiDevice().c_str(), "Device Id") + ); + + RETURN_HR_IF(E_UNEXPECTED, Flow != MidiFlow::MidiFlowOut); + + wil::com_ptr_nothrow transformPipe{ nullptr }; + + auto transformGuid = _uuidof(Midi2UmpProtocolDownscalerTransform); + + // search existing transforms for this device for an output that supports + // the requested flow and data format. + auto transforms = m_TransformPipes.equal_range(DevicePipe->MidiDevice()); + for (auto& transform = transforms.first; transform != transforms.second; ++transform) + { + if (Flow == transform->second->Flow() && + transform->second->TransformGuid() == transformGuid) + { + RETURN_HR_IF(E_UNEXPECTED, transformPipe); + transformPipe = transform->second; + } + } + + // connect transform to device because we're transforming for the case where + // we have a MIDI 1.0 device connected to the new driver, and we need to + // transform MT4 messages to MT2 messages + + if (!transformPipe) + { + MIDISRV_TRANSFORMCREATION_PARAMS creationParams{ }; + + creationParams.Flow = Flow; + creationParams.DataFormatIn = MidiDataFormat::MidiDataFormat_UMP; + creationParams.DataFormatOut = MidiDataFormat::MidiDataFormat_UMP; + creationParams.TransformGuid = transformGuid; + + // create the transform + wil::com_ptr_nothrow transform; + RETURN_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&transform)); + + RETURN_IF_FAILED(transform->Initialize(BindingHandle, DevicePipe->MidiDevice().c_str(), &creationParams, &m_MmcssTaskId, (IUnknown*)&m_DeviceManager)); + transformPipe = transform.get(); + + // connect the transform to the device + RETURN_IF_FAILED(transformPipe->AddConnectedPipe(NextDeviceSidePipe)); + + m_TransformPipes.emplace(DevicePipe->MidiDevice(), transform); + } + + ClientConnectionPipe = transformPipe; + + return S_OK; +} + + + + // This function handles data format translation only _Use_decl_annotations_ HRESULT @@ -334,11 +484,14 @@ CMidiClientManager::GetMidiTransform( wil::com_ptr_nothrow& ClientConnectionPipe ) { + RETURN_HR_IF_NULL(E_INVALIDARG, DevicePipe); + TraceLoggingWrite( MidiSrvTelemetryProvider::Provider(), __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingPointer(this, "this") + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(DevicePipe->MidiDevice().c_str(), "Device Id") ); @@ -364,7 +517,7 @@ CMidiClientManager::GetMidiTransform( // not found, instantiate the transform that is needed. if (!transformPipe) { - MIDISRV_TRANSFORMCREATION_PARAMS creationParams {0}; + MIDISRV_TRANSFORMCREATION_PARAMS creationParams { }; creationParams.Flow = Flow; creationParams.DataFormatIn = DataFormatFrom; @@ -449,11 +602,14 @@ CMidiClientManager::GetMidiScheduler( wil::com_ptr_nothrow& ClientConnectionPipe ) { + RETURN_HR_IF_NULL(E_INVALIDARG, DevicePipe); + TraceLoggingWrite( MidiSrvTelemetryProvider::Provider(), __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingPointer(this, "this") + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(DevicePipe->MidiDevice().c_str(), "Device Id") ); @@ -487,7 +643,14 @@ CMidiClientManager::GetMidiScheduler( // not found, instantiate the transform that is needed. if (!transformPipe) { -// OutputDebugString(L"" __FUNCTION__ " scheduler transform pipe not found. Creating one."); + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(DevicePipe->MidiDevice().c_str(), "Device Id"), + TraceLoggingWideString(L"Creating new scheduler", "message") + ); MIDISRV_TRANSFORMCREATION_PARAMS creationParams{ 0 }; @@ -534,11 +697,14 @@ CMidiClientManager::GetMidiEndpointMetadataHandler( wil::com_ptr_nothrow& ClientConnectionPipe ) { + RETURN_HR_IF_NULL(E_INVALIDARG, DevicePipe); + TraceLoggingWrite( MidiSrvTelemetryProvider::Provider(), __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingPointer(this, "this") + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(DevicePipe->MidiDevice().c_str(), "Device Id") ); wil::com_ptr_nothrow transformPipe{ nullptr }; @@ -598,6 +764,7 @@ CMidiClientManager::GetMidiEndpointMetadataHandler( } + _Use_decl_annotations_ HRESULT CMidiClientManager::CreateMidiClient( @@ -605,7 +772,8 @@ CMidiClientManager::CreateMidiClient( LPCWSTR MidiDevice, GUID SessionId, PMIDISRV_CLIENTCREATION_PARAMS CreationParams, - PMIDISRV_CLIENT Client + PMIDISRV_CLIENT Client, + BOOL InternalProtocolNegotiationUseOnly ) { TraceLoggingWrite( @@ -617,6 +785,52 @@ CMidiClientManager::CreateMidiClient( TraceLoggingGuid(SessionId) ); + if (InternalProtocolNegotiationUseOnly && Client->DataFormat != MidiDataFormat::MidiDataFormat_UMP) + { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(MidiDevice), + TraceLoggingGuid(SessionId), + TraceLoggingWideString(L"Called for internal protocol negotiation, but client dataformat is not UMP", "message") + ); + + return E_UNEXPECTED; + } + + if (InternalProtocolNegotiationUseOnly && CreationParams->DataFormat != MidiDataFormat::MidiDataFormat_UMP) + { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(MidiDevice), + TraceLoggingGuid(SessionId), + TraceLoggingWideString(L"Called for internal protocol negotiation, but creation params dataformat is not UMP", "message") + ); + + return E_UNEXPECTED; + } + + if (InternalProtocolNegotiationUseOnly && CreationParams->Flow != MidiFlow::MidiFlowBidirectional) + { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(MidiDevice), + TraceLoggingGuid(SessionId), + TraceLoggingWideString(L"Called for internal protocol negotiation, but creation params data flow is not Bidirectional", "message") + ); + + return E_UNEXPECTED; + } + + auto lock = m_ClientManagerLock.lock(); DWORD clientProcessId{0}; @@ -630,45 +844,54 @@ CMidiClientManager::CreateMidiClient( unique_mmcss_handle MmcssHandle; std::wstring midiDevice; - // get the client PID, impersonate the client to get the client process handle, and then - // revert back to self. - RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(I_RpcBindingInqLocalClientPID(BindingHandle, &clientProcessId))); - RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(RpcImpersonateClient(BindingHandle))); - clientProcessHandle.reset(OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION, FALSE, clientProcessId)); - RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(RpcRevertToSelf())); - - RETURN_LAST_ERROR_IF_NULL(clientProcessHandle); - - // pre-populate our mmcss task id so we have a known task id - // to provide and use for all other tasks related to this pipe - // retain the MMCSS configuration until after the client and device - // pipes are initialized, so this TaskId remains valid through - // initialization. Safe to disable mmcss for this rpc thread, - // once we complete initialization and have worker threads - // actively using this task id for the duration of this pipe. - RETURN_IF_FAILED(EnableMmcss(MmcssHandle, m_MmcssTaskId)); - - // The provided SWD instance id provided may be an alias of a UMP device, retrieve the - // PKEY_MIDI_AssociatedUMP property to retrieve the id of the primary device. - // We only create device pipe entries for the primary devices. - RETURN_IF_FAILED(GetEndpointAlias(MidiDevice, midiDevice, CreationParams->Flow)); - - // see if we should create timestamps or not. This option is set - // by the transport at endpoint enumeration time. For most endpoint - // types, this will be true, but for diagnostics loopback endpoints, - // it is false. So, we store the value in the property store to make - // it more flexible + //bool addMetadataListenerToIncomingStream{ true }; bool generateIncomingMessageTimestamps{ true }; - RETURN_IF_FAILED(GetEndpointGenerateIncomingTimestamp(midiDevice, generateIncomingMessageTimestamps, CreationParams->Flow)); - // Some endpoints, like the device-side of an app-to-app MIDI connection, - // should not have metadata listeners. This flag controls whether or not - // we add one if otherwise eligible. - bool addMetadataListenerToIncomingStream{ true }; - RETURN_IF_FAILED(GetEndpointShouldHaveMetadataHandler(midiDevice, addMetadataListenerToIncomingStream, CreationParams->Flow)); - - - RETURN_IF_FAILED(GetMidiClient(BindingHandle, midiDevice.c_str(), SessionId, CreationParams, Client, clientProcessHandle, clientPipe, generateIncomingMessageTimestamps)); + if (!InternalProtocolNegotiationUseOnly) + { + // get the client PID, impersonate the client to get the client process handle, and then + // revert back to self. + RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(I_RpcBindingInqLocalClientPID(BindingHandle, &clientProcessId))); + RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(RpcImpersonateClient(BindingHandle))); + clientProcessHandle.reset(OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION, FALSE, clientProcessId)); + RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(RpcRevertToSelf())); + + RETURN_LAST_ERROR_IF_NULL(clientProcessHandle); + + // pre-populate our mmcss task id so we have a known task id + // to provide and use for all other tasks related to this pipe + // retain the MMCSS configuration until after the client and device + // pipes are initialized, so this TaskId remains valid through + // initialization. Safe to disable mmcss for this rpc thread, + // once we complete initialization and have worker threads + // actively using this task id for the duration of this pipe. + RETURN_IF_FAILED(EnableMmcss(MmcssHandle, m_MmcssTaskId)); + + // The provided SWD instance id provided may be an alias of a UMP device, retrieve the + // PKEY_MIDI_AssociatedUMP property to retrieve the id of the primary device. + // We only create device pipe entries for the primary devices. + RETURN_IF_FAILED(GetEndpointAlias(MidiDevice, midiDevice, CreationParams->Flow)); + + // see if we should create timestamps or not. This option is set + // by the transport at endpoint enumeration time. For most endpoint + // types, this will be true, but for diagnostics loopback endpoints, + // it is false. So, we store the value in the property store to make + // it more flexible + RETURN_IF_FAILED(GetEndpointGenerateIncomingTimestamp(midiDevice, generateIncomingMessageTimestamps, CreationParams->Flow)); + + // Some endpoints, like the device-side of an app-to-app MIDI connection, + // should not have metadata listeners. This flag controls whether or not + // we add one if otherwise eligible. + //RETURN_IF_FAILED(GetEndpointShouldHaveMetadataHandler(midiDevice, addMetadataListenerToIncomingStream, CreationParams->Flow)); + + RETURN_IF_FAILED(GetMidiClient(BindingHandle, midiDevice.c_str(), SessionId, CreationParams, Client, clientProcessHandle, clientPipe, generateIncomingMessageTimestamps)); + } + else + { + // for protocol negotiation / metadata capture only. BindingHandle here is going to be invalid + // NOTE: clientProcessHandle here is invalid + RETURN_IF_FAILED(GetMidiClient(BindingHandle, MidiDevice, SessionId, CreationParams, Client, clientProcessHandle, clientPipe, false)); + } auto cleanupOnFailure = wil::scope_exit([&]() { @@ -709,24 +932,22 @@ CMidiClientManager::CreateMidiClient( newClientConnectionPipe = clientConnectionPipe; - // Metadata Listener ---------------------------------------------------------------- - // We should check protocol, not just data format here because we're putting this on - // MIDI 1.0 devices that use the new driver, and there's no reason to do that. - if (addMetadataListenerToIncomingStream && devicePipe->IsFormatSupportedIn(MidiDataFormat::MidiDataFormat_UMP)) - { - // Our clientConnectionPipe is now the Scheduler - RETURN_IF_FAILED(GetMidiEndpointMetadataHandler( - BindingHandle, - MidiFlowIn, - devicePipe, - newClientConnectionPipe, - clientConnectionPipe)); // clientConnectionPipe is the plugin - - clientConnectionPipe->AddClient((MidiClientHandle)clientPipe.get()); - } - newClientConnectionPipe = clientConnectionPipe; - - + //// Metadata Listener ---------------------------------------------------------------- + //// We should check protocol, not just data format here because we're putting this on + //// MIDI 1.0 devices that use the new driver, and there's no reason to do that. + //if (addMetadataListenerToIncomingStream && devicePipe->IsFormatSupportedIn(MidiDataFormat::MidiDataFormat_UMP)) + //{ + // // Our clientConnectionPipe is now the Scheduler + // RETURN_IF_FAILED(GetMidiEndpointMetadataHandler( + // BindingHandle, + // MidiFlowIn, + // devicePipe, + // newClientConnectionPipe, + // clientConnectionPipe)); // clientConnectionPipe is the plugin + + // clientConnectionPipe->AddClient((MidiClientHandle)clientPipe.get()); + //} + //newClientConnectionPipe = clientConnectionPipe; // no more transforms to add @@ -747,45 +968,82 @@ CMidiClientManager::CreateMidiClient( clientConnectionPipe = devicePipe; newClientConnectionPipe = devicePipe; - // TODO: This needs to work with both bytestream and UMP clients, so this logic needs to change - - // Data Format Translator --------------------------------------------------- - if (!clientPipe->IsFormatSupportedOut(newClientConnectionPipe->DataFormatOut())) + if (!InternalProtocolNegotiationUseOnly) { - // Format is not supported, so we need to transform - // client requires a specific format, retrieve the transform required for that format. - // Our clientConnectionPipe is now the format translator + // TODO: This needs to work with both bytestream and UMP clients, so this logic needs to change - RETURN_IF_FAILED(GetMidiTransform( - BindingHandle, - MidiFlowOut, - clientPipe->DataFormatOut(), - newClientConnectionPipe->DataFormatOut(), - devicePipe, - clientConnectionPipe)); // clientConnectionPipe is the plugin + // Data Format Translator --------------------------------------------------- + if (!clientPipe->IsFormatSupportedOut(newClientConnectionPipe->DataFormatOut())) + { + // Format is not supported, so we need to transform + // client requires a specific format, retrieve the transform required for that format. + // Our clientConnectionPipe is now the format translator - clientConnectionPipe->AddClient((MidiClientHandle)clientPipe.get()); + RETURN_IF_FAILED(GetMidiTransform( + BindingHandle, + MidiFlowOut, + clientPipe->DataFormatOut(), + newClientConnectionPipe->DataFormatOut(), + devicePipe, + clientConnectionPipe)); // clientConnectionPipe is the plugin - newClientConnectionPipe = clientConnectionPipe; - } + clientConnectionPipe->AddClient((MidiClientHandle)clientPipe.get()); - // Scheduler ---------------------------------------------------------------- - // for now, the scheduler is only going to work with UMP, so we hope - // any required translation is done BEFORE we add this. - if (clientConnectionPipe->IsFormatSupportedOut(MidiDataFormat::MidiDataFormat_UMP)) - { - // Our clientConnectionPipe is now the Scheduler - RETURN_IF_FAILED(GetMidiScheduler( - BindingHandle, - MidiFlowOut, - devicePipe, - newClientConnectionPipe, - clientConnectionPipe)); // clientConnectionPipe is the plugin + newClientConnectionPipe = clientConnectionPipe; + } - clientConnectionPipe->AddClient((MidiClientHandle)clientPipe.get()); + // Scheduler ---------------------------------------------------------------- + // for now, the scheduler is only going to work with UMP, so we hope + // any required translation is done BEFORE we add this. + if (clientConnectionPipe->IsFormatSupportedOut(MidiDataFormat::MidiDataFormat_UMP)) + { + // Our clientConnectionPipe is now the Scheduler + RETURN_IF_FAILED(GetMidiScheduler( + BindingHandle, + MidiFlowOut, + devicePipe, + newClientConnectionPipe, + clientConnectionPipe)); // clientConnectionPipe is the plugin + + clientConnectionPipe->AddClient((MidiClientHandle)clientPipe.get()); + + newClientConnectionPipe = clientConnectionPipe; + } - newClientConnectionPipe = clientConnectionPipe; + + // Protocol Translator ---------------------------------------------------------------- + // This translates MT4 to MT2 for MIDI 1.0 devices connected through the new driver. + // For devices connected directly to the service via the old driver, we don't have to + // do any translation or scaling because that's all taken care of in the BS2UMP and + // UMP2BS transforms. (They do format translation but *also* MT4/MT2 translation) + // This outbound translation step should happen prior to the outbound scheduler step. + + bool addProtocolDownscalerForMidi1DeviceWithUmpDriver{ false }; + RETURN_IF_FAILED(GetEndpointRequiresOutboundProtocolDownscaling( + midiDevice, + CreationParams->Flow, + devicePipe->DataFormatOut(), + addProtocolDownscalerForMidi1DeviceWithUmpDriver)); + + if (addProtocolDownscalerForMidi1DeviceWithUmpDriver) + { + RETURN_IF_FAILED(GetMidiProtocolDownscalerTransform( + BindingHandle, + MidiFlowOut, + devicePipe, + newClientConnectionPipe, + clientConnectionPipe)); // clientConnectionPipe is the plugin + + clientConnectionPipe->AddClient((MidiClientHandle)clientPipe.get()); + + newClientConnectionPipe = clientConnectionPipe; + } } + else + { + // for protocol negotiation use only, so no translator, scheduler, or downscaler + } + // no more transforms to add clientConnectionPipe = newClientConnectionPipe; diff --git a/src/api/Service/Exe/MidiClientPipe.cpp b/src/api/Service/Exe/MidiClientPipe.cpp index 5f3eb564..1377b607 100644 --- a/src/api/Service/Exe/MidiClientPipe.cpp +++ b/src/api/Service/Exe/MidiClientPipe.cpp @@ -20,8 +20,8 @@ CMidiClientPipe::AdjustForBufferingRequirements(PMIDISRV_CLIENTCREATION_PARAMS C _Use_decl_annotations_ HRESULT CMidiClientPipe::Initialize( - handle_t /* BindingHandle */, - HANDLE /* clientProcess */, + handle_t /* BindingHandle */, // this isn't used now, but if used in the future, not this will not be set for internal clients like protocol negotiation + HANDLE /* clientProcess */, // this isn't used now, but if used in the future, not this will not be set for internal clients like protocol negotiation LPCWSTR Device, GUID SessionId, PMIDISRV_CLIENTCREATION_PARAMS CreationParams, diff --git a/src/api/Service/Exe/MidiDeviceManager.cpp b/src/api/Service/Exe/MidiDeviceManager.cpp index e7a9ff48..c648bf9a 100644 --- a/src/api/Service/Exe/MidiDeviceManager.cpp +++ b/src/api/Service/Exe/MidiDeviceManager.cpp @@ -46,13 +46,22 @@ CMidiDeviceManager::Initialize( __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_INFO), TraceLoggingPointer(this, "this"), - TraceLoggingWideString(L"Loading abstraction layer", "message"), + TraceLoggingWideString(L"Getting abstraction configuration JSON", "message"), TraceLoggingGuid(AbstractionLayer, "abstraction layer") ); // provide the initial settings for these transports auto transportSettingsJson = m_ConfigurationManager->GetSavedConfigurationForTransportAbstraction(AbstractionLayer); + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"CoCreating abstraction layer", "message"), + TraceLoggingGuid(AbstractionLayer, "abstraction layer") + ); + // changed these from a return-on-fail to just log, so we don't prevent service startup // due to one bad abstraction @@ -60,17 +69,44 @@ CMidiDeviceManager::Initialize( if (midiAbstraction != nullptr) { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Activating abstraction layer IMidiEndpointManager", "message"), + TraceLoggingGuid(AbstractionLayer, "abstraction layer") + ); + LOG_IF_FAILED(midiAbstraction->Activate(__uuidof(IMidiEndpointManager), (void**)&endpointManager)); if (endpointManager != nullptr) { wil::com_ptr_nothrow protocolManager = EndpointProtocolManager.get(); + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Initializing abstraction layer Midi Endpoint Manager", "message"), + TraceLoggingGuid(AbstractionLayer, "abstraction layer") + ); + auto initializeResult = endpointManager->Initialize((IUnknown*)this, (IUnknown*)protocolManager.get()); if (SUCCEEDED(initializeResult)) { m_MidiEndpointManagers.emplace(AbstractionLayer, std::move(endpointManager)); + + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Midi Endpoint Manager initialized", "message"), + TraceLoggingGuid(AbstractionLayer, "abstraction layer") + ); } else { @@ -98,6 +134,15 @@ CMidiDeviceManager::Initialize( } + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Activating abstraction configuration manager.", "message"), + TraceLoggingGuid(AbstractionLayer, "abstraction layer") + ); + // we only log. This interface is optional auto configHR = midiAbstraction->Activate(__uuidof(IMidiAbstractionConfigurationManager), (void**)&abstractionConfigurationManager); if (SUCCEEDED(configHR)) @@ -112,6 +157,8 @@ CMidiDeviceManager::Initialize( if (FAILED(initializeResult)) { + LOG_IF_FAILED(initializeResult); + TraceLoggingWrite( MidiSrvTelemetryProvider::Provider(), __FUNCTION__, @@ -129,6 +176,15 @@ CMidiDeviceManager::Initialize( if (!transportSettingsJson.empty()) { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Updating abstraction configuration", "message"), + TraceLoggingGuid(AbstractionLayer, "abstraction layer") + ); + CComBSTR response{}; response.Empty(); @@ -137,7 +193,6 @@ CMidiDeviceManager::Initialize( // we don't use the response info here. ::SysFreeString(response); - if (FAILED(updateConfigHR)) { if (updateConfigHR == E_NOTIMPL) @@ -223,15 +278,53 @@ CMidiDeviceManager::Initialize( } } - catch (...) + + catch (wil::ResultException rex) + { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Result Exception loading transport abstraction.", "message"), + TraceLoggingString(rex.what(), "error"), + TraceLoggingHResult(rex.GetErrorCode(), "hresult"), + TraceLoggingGuid(AbstractionLayer, "abstraction layer") + ); + + } + catch (std::runtime_error& err) + { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Runtime error loading transport abstraction.", "message"), + TraceLoggingString(err.what(), "error"), + TraceLoggingGuid(AbstractionLayer, "abstraction layer") + ); + } + catch (const std::exception& ex) { - // TODO: Log exception TraceLoggingWrite( MidiSrvTelemetryProvider::Provider(), __FUNCTION__, TraceLoggingLevel(WINEVENT_LEVEL_ERROR), TraceLoggingPointer(this, "this"), TraceLoggingWideString(L"Exception loading transport abstraction.", "message"), + TraceLoggingString(ex.what(), "exception"), + TraceLoggingGuid(AbstractionLayer, "abstraction layer") + ); + } + catch (...) + { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Unknown exception loading transport abstraction.", "message"), TraceLoggingGuid(AbstractionLayer, "abstraction layer") ); diff --git a/src/api/Service/Exe/MidiDevicePipe.cpp b/src/api/Service/Exe/MidiDevicePipe.cpp index 075669a3..668c53df 100644 --- a/src/api/Service/Exe/MidiDevicePipe.cpp +++ b/src/api/Service/Exe/MidiDevicePipe.cpp @@ -26,12 +26,8 @@ CMidiDevicePipe::Initialize( TraceLoggingPointer(this, "this") ); - auto deviceLock = m_DevicePipeLock.lock(); - OutputDebugString(L"" __FUNCTION__ " Initialize."); - OutputDebugString(Device); - ABSTRACTIONCREATIONPARAMS abstractionCreationParams; RETURN_IF_FAILED(CMidiPipe::Initialize(Device, CreationParams->Flow)); diff --git a/src/api/Service/Exe/MidiEndpointProtocolManager.cpp b/src/api/Service/Exe/MidiEndpointProtocolManager.cpp index c99e9c67..590092fc 100644 --- a/src/api/Service/Exe/MidiEndpointProtocolManager.cpp +++ b/src/api/Service/Exe/MidiEndpointProtocolManager.cpp @@ -44,38 +44,16 @@ CMidiEndpointProtocolManager::Initialize( m_deviceManager = DeviceManager; m_sessionTracker = SessionTracker; - m_allMessagesReceived.create(); - m_queueWorkerThreadWakeup.create(); - - // connect to the service. Needing a reference to this abstraction def - // creates a circular reference to the MidiSrv Abstraction. Not sure of - // a good way around that other than fixing up the ClientManager to make - // local connections with local handles reasonable. - RETURN_IF_FAILED(CoCreateInstance(__uuidof(Midi2MidiSrvAbstraction), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_serviceAbstraction))); - + // log a an active session so a user can figure out which + // processes have any given device open. auto pid = GetCurrentProcessId(); - m_sessionTracker->AddClientSession( - m_sessionId, - MIDI_PROTOCOL_MANAGER_SESSION_NAME, - pid, - MIDI_PROTOCOL_MANAGER_PROCESS_NAME); - - try - { - // start background processing thread - - std::thread workerThread( - &CMidiEndpointProtocolManager::ThreadWorker, - this); - - m_queueWorkerThread = std::move(workerThread); - - // start up the worker thread - m_queueWorkerThread.detach(); - - } - CATCH_RETURN(); + LOG_IF_FAILED(m_sessionTracker->AddClientSession( + m_sessionId, + MIDI_PROTOCOL_MANAGER_SESSION_NAME, + pid, + MIDI_PROTOCOL_MANAGER_PROCESS_NAME)); + return S_OK; } @@ -89,7 +67,8 @@ CMidiEndpointProtocolManager::NegotiateAndRequestMetadata( BOOL PreferToSendJRTimestampsToEndpoint, BOOL PreferToReceiveJRTimestampsFromEndpoint, BYTE PreferredMidiProtocol, - WORD TimeoutMS + WORD TimeoutMS, + PENDPOINTPROTOCOLNEGOTIATIONRESULTS* NegotiationResults ) noexcept { TraceLoggingWrite( @@ -100,10 +79,13 @@ CMidiEndpointProtocolManager::NegotiateAndRequestMetadata( TraceLoggingWideString(DeviceInterfaceId) ); + // TEMP! + NegotiationResults = nullptr; + ProtocolManagerWork work; - // DEBUG - //TimeoutMS = 25000; + //// DEBUG + ////TimeoutMS = 25000; work.EndpointInstanceId = DeviceInterfaceId; work.PreferToSendJRTimestampsToEndpoint = PreferToSendJRTimestampsToEndpoint; @@ -111,12 +93,12 @@ CMidiEndpointProtocolManager::NegotiateAndRequestMetadata( work.PreferredMidiProtocol = PreferredMidiProtocol; work.TimeoutMS = TimeoutMS; - m_queueMutex.lock(); - m_workQueue.push(std::move(work)); - m_queueMutex.unlock(); + //m_queueMutex.lock(); + //m_workQueue.push(std::move(work)); + //m_queueMutex.unlock(); - // todo: signal event that there's new work - m_queueWorkerThreadWakeup.SetEvent(); + //// todo: signal event that there's new work + //m_queueWorkerThreadWakeup.SetEvent(); return S_OK; @@ -146,10 +128,10 @@ CMidiEndpointProtocolManager::Cleanup() while (m_workQueue.size() > 0) m_workQueue.pop(); m_shutdown = true; - m_queueWorkerThreadWakeup.SetEvent(); +// m_queueWorkerThreadWakeup.SetEvent(); - if (m_queueWorkerThread.joinable()) - m_queueWorkerThread.join(); +// if (m_queueWorkerThread.joinable()) +// m_queueWorkerThread.join(); return S_OK; } @@ -321,7 +303,7 @@ CMidiEndpointProtocolManager::Callback(PVOID Data, UINT Size, LONGLONG Position, m_currentWorkItem.CountFunctionBlocksReceived == m_currentWorkItem.DeclaredFunctionBlockCount && m_currentWorkItem.TaskFinalStreamNegotiationResponseReceived) { - m_allMessagesReceived.SetEvent(); +// m_allMessagesReceived.SetEvent(); } return S_OK; @@ -491,38 +473,38 @@ CMidiEndpointProtocolManager::ProcessCurrentWorkEntry() HRESULT hr = S_OK; - // Send initial discovery request - // the rest happens in response to messages in the callback - LOG_IF_FAILED(hr = RequestAllEndpointDiscoveryInformation()); - - if (SUCCEEDED(hr)) - { - OutputDebugString(__FUNCTION__ L" - Requested discovery information"); - } - else - { - TraceLoggingWrite( - MidiSrvTelemetryProvider::Provider(), - __FUNCTION__, - TraceLoggingLevel(WINEVENT_LEVEL_ERROR), - TraceLoggingPointer(this, "this"), - TraceLoggingWideString(L"Failed to request discovery information") - ); - } - - if (SUCCEEDED(hr)) - { - // Wait until all metadata arrives or we timeout - if (!m_allMessagesReceived.wait(m_currentWorkItem.TimeoutMS)) - { - // we didn't receive everything, but that's not a failure condition for this. - } - } - - if (m_allMessagesReceived.is_signaled()) m_allMessagesReceived.ResetEvent(); - - m_currentWorkItem.Endpoint->Cleanup(); - m_currentWorkItem.Endpoint = nullptr; + //// Send initial discovery request + //// the rest happens in response to messages in the callback + //LOG_IF_FAILED(hr = RequestAllEndpointDiscoveryInformation()); + + //if (SUCCEEDED(hr)) + //{ + // OutputDebugString(__FUNCTION__ L" - Requested discovery information"); + //} + //else + //{ + // TraceLoggingWrite( + // MidiSrvTelemetryProvider::Provider(), + // __FUNCTION__, + // TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + // TraceLoggingPointer(this, "this"), + // TraceLoggingWideString(L"Failed to request discovery information") + // ); + //} + + //if (SUCCEEDED(hr)) + //{ + // // Wait until all metadata arrives or we timeout + // if (!m_allMessagesReceived.wait(m_currentWorkItem.TimeoutMS)) + // { + // // we didn't receive everything, but that's not a failure condition for this. + // } + //} + + //if (m_allMessagesReceived.is_signaled()) m_allMessagesReceived.ResetEvent(); + + //m_currentWorkItem.Endpoint->Cleanup(); + //m_currentWorkItem.Endpoint = nullptr; return hr; } diff --git a/src/api/Service/Exe/MidiEndpointProtocolManager.h b/src/api/Service/Exe/MidiEndpointProtocolManager.h index 4b2e84e8..fd9b851a 100644 --- a/src/api/Service/Exe/MidiEndpointProtocolManager.h +++ b/src/api/Service/Exe/MidiEndpointProtocolManager.h @@ -17,7 +17,7 @@ struct ProtocolManagerWork bool PreferToSendJRTimestampsToEndpoint{ false }; bool PreferToReceiveJRTimestampsFromEndpoint{ false }; uint8_t PreferredMidiProtocol{}; - uint16_t TimeoutMS{ 2000 }; + uint16_t TimeoutMS{ 5000 }; wil::com_ptr_nothrow Endpoint; @@ -36,17 +36,19 @@ struct ProtocolManagerWork }; + + + class CMidiEndpointProtocolManager : public Microsoft::WRL::RuntimeClass< Microsoft::WRL::RuntimeClassFlags, IMidiEndpointProtocolManagerInterface, IMidiCallback> { public: - CMidiEndpointProtocolManager() = default; ~CMidiEndpointProtocolManager() {} - HRESULT Initialize( + STDMETHOD(Initialize)( _In_ std::shared_ptr& ClientManager, _In_ std::shared_ptr& DeviceManager, _In_ std::shared_ptr& SessionTracker @@ -57,10 +59,10 @@ class CMidiEndpointProtocolManager : public Microsoft::WRL::RuntimeClass< _In_ BOOL PreferToSendJRTimestampsToEndpoint, _In_ BOOL PreferToReceiveJRTimestampsFromEndpoint, _In_ BYTE PreferredMidiProtocol, - _In_ WORD TimeoutMS + _In_ WORD TimeoutMS, + _Out_ PENDPOINTPROTOCOLNEGOTIATIONRESULTS* NegotiationResults ); - STDMETHOD(Callback)(_In_ PVOID Data, _In_ UINT Size, _In_ LONGLONG Position, _In_ LONGLONG Context); HRESULT Cleanup(); @@ -90,8 +92,6 @@ class CMidiEndpointProtocolManager : public Microsoft::WRL::RuntimeClass< std::thread m_queueWorkerThread; ProtocolManagerWork m_currentWorkItem; - wil::unique_event_nothrow m_allMessagesReceived; - wil::unique_event_nothrow m_queueWorkerThreadWakeup; // true if we're closing down bool m_shutdown{ false }; diff --git a/src/api/Service/Exe/MidiEndpointProtocolWorker.cpp b/src/api/Service/Exe/MidiEndpointProtocolWorker.cpp new file mode 100644 index 00000000..6ac10b56 --- /dev/null +++ b/src/api/Service/Exe/MidiEndpointProtocolWorker.cpp @@ -0,0 +1,839 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#include "stdafx.h" +#include "Midi2MidiSrvAbstraction.h" +#include "ump_helpers.h" +#include "midi_ump_message_defs.h" + + + +_Use_decl_annotations_ +DEVPROPKEY +CMidiEndpointProtocolWorker::FunctionBlockPropertyKeyFromNumber( + uint8_t functionBlockNumber +) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + DEVPROPKEY key{}; + GUID propertyId{}; + + if (functionBlockNumber < MIDI_MAX_FUNCTION_BLOCKS) + { + if (SUCCEEDED(::CLSIDFromString(MIDI_STRING_PKEY_GUID, &propertyId))) + { + key.fmtid = propertyId; + key.pid = MIDI_FUNCTION_BLOCK_PROPERTY_INDEX_START + functionBlockNumber; + } + else + { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"CLSIDFromString failed for Function Block Property Key", "message") + ); + } + } + + return key; +} + +_Use_decl_annotations_ +DEVPROPKEY +CMidiEndpointProtocolWorker::FunctionBlockNamePropertyKeyFromNumber( + uint8_t functionBlockNumber +) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + DEVPROPKEY key{}; + GUID propertyId{}; + + if (functionBlockNumber < MIDI_MAX_FUNCTION_BLOCKS) + { + if (SUCCEEDED(::CLSIDFromString(MIDI_STRING_PKEY_GUID, &propertyId))) + { + key.fmtid = propertyId; + key.pid = MIDI_FUNCTION_BLOCK_NAME_PROPERTY_INDEX_START + functionBlockNumber; + } + } + + return key; +} + + + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::Initialize( + GUID SessionId, + GUID AbstractionGuid, + LPCWSTR DeviceInterfaceId, + std::shared_ptr& ClientManager, + std::shared_ptr& DeviceManager, + std::shared_ptr& SessionTracker +) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + m_abstractionGuid = AbstractionGuid; + m_sessionId = SessionId; + m_deviceInterfaceId = DeviceInterfaceId; + + m_clientManager = ClientManager; + m_deviceManager = DeviceManager; + m_sessionTracker = SessionTracker; + + wil::com_ptr_nothrow midiAbstraction; + + // we only support UMP data format for protocol negotiation + ABSTRACTIONCREATIONPARAMS abstractionCreationParams; + abstractionCreationParams.DataFormat = MidiDataFormat::MidiDataFormat_UMP; + + DWORD mmcssTaskId{ 0 }; + LONGLONG context{ 0 }; + + RETURN_IF_FAILED(CoCreateInstance(m_abstractionGuid, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&midiAbstraction))); + RETURN_IF_FAILED(midiAbstraction->Activate(__uuidof(IMidiBiDi), (void**)&m_midiBiDiDevice)); + RETURN_IF_FAILED(m_midiBiDiDevice->Initialize(m_deviceInterfaceId.c_str(), &abstractionCreationParams, &mmcssTaskId, this, context, m_sessionId)); + + try + { + // start background processing thread + + //std::thread workerThread( + // &CMidiEndpointProtocolManager::ThreadWorker, + // this); + + //m_queueWorkerThread = std::move(workerThread); + + //// start up the worker thread + //m_queueWorkerThread.detach(); + + } + CATCH_RETURN(); + + return S_OK; +} + +HRESULT +CMidiEndpointProtocolWorker::ListenForMetadata() +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + // TODO: Connect to the endpoint and listen + + + + return S_OK; +} + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::Callback( + PVOID Data, + UINT Size, + LONGLONG Position, + LONGLONG Context) +{ + UNREFERENCED_PARAMETER(Position); + UNREFERENCED_PARAMETER(Context); + + if (Size == UMP128_BYTE_COUNT) + { + internal::PackedUmp128 ump; + + if (internal::FillPackedUmp128FromBytePointer((byte*)Data, (uint8_t)Size, ump)) + { + // if type F, process it. + + if (internal::GetUmpMessageTypeFromFirstWord(ump.word0) == 0xF) + { + // process the metadata + LOG_IF_FAILED(ProcessStreamMessage(ump)); + } + else + { + // not a stream message. Ignore and move on + } + } + else + { + // couldn't fill the UMP. Shouldn't happen since we pre-validate. Just ignore. + //return E_FAIL; + } + } + else + { + // Not a UMP128 so can't be a stream message. Fall out quickly + } + + + return S_OK; +} + +HRESULT +CMidiEndpointProtocolWorker::Cleanup() +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + + + + return S_OK; +} + + + + +HRESULT +CMidiEndpointProtocolWorker::UpdateEndpointNameProperty() +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + auto cleanedValue{ internal::TrimmedWStringCopy(m_endpointName) + L"\0" }; + + FILETIME currentTime; + GetSystemTimePreciseAsFileTime(¤tTime); + + + DEVPROPERTY props[] = + { + {{ PKEY_MIDI_EndpointProvidedName, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_STRING, static_cast((cleanedValue.length() + 1) * sizeof(WCHAR)), (PVOID)(cleanedValue.c_str()) }, + {{ PKEY_MIDI_EndpointProvidedNameLastUpdateTime, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_FILETIME, static_cast(sizeof(FILETIME)), (PVOID)(¤tTime) }, + }; + + RETURN_IF_FAILED(m_deviceManager->UpdateEndpointProperties(m_deviceInstanceId.c_str(), ARRAYSIZE(props), (PVOID)props)); + + // clear out any old value that's in there + //m_endpointName.clear(); + + return S_OK; +} + +HRESULT +CMidiEndpointProtocolWorker::UpdateEndpointProductInstanceIdProperty() +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + + FILETIME currentTime; + GetSystemTimePreciseAsFileTime(¤tTime); + + + std::wstring cleanedValue{ internal::TrimmedWStringCopy(m_productInstanceId) + L"\0" }; + + DEVPROPERTY props[] = + { + {{ PKEY_MIDI_EndpointProvidedProductInstanceId, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_STRING, static_cast((cleanedValue.length() + 1) * sizeof(WCHAR)), (PVOID)(cleanedValue.c_str()) }, + {{ PKEY_MIDI_EndpointProvidedProductInstanceIdLastUpdateTime, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_FILETIME, static_cast(sizeof(FILETIME)), (PVOID)(¤tTime) }, + }; + + RETURN_IF_FAILED(m_deviceManager->UpdateEndpointProperties(m_deviceInstanceId.c_str(), ARRAYSIZE(props), (PVOID)props)); + + // clear out any old value that's in there + //m_productInstanceId.clear(); + + return S_OK; +} + + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::UpdateFunctionBlockNameProperty(uint8_t functionBlockNumber, std::wstring name) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + FILETIME currentTime; + GetSystemTimePreciseAsFileTime(¤tTime); + + + std::wstring cleanedValue{ internal::TrimmedWStringCopy(name) + L"\0" }; + + DEVPROPERTY props[] = + { + {{ FunctionBlockNamePropertyKeyFromNumber(functionBlockNumber), DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_STRING, static_cast((cleanedValue.length() + 1) * sizeof(WCHAR)), (PVOID)(cleanedValue.c_str()) }, + {{ PKEY_MIDI_FunctionBlocksLastUpdateTime, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_FILETIME, static_cast(sizeof(FILETIME)), (PVOID)(¤tTime) }, + }; + + RETURN_IF_FAILED(m_deviceManager->UpdateEndpointProperties(m_deviceInstanceId.c_str(), ARRAYSIZE(props), (PVOID)props)); + + // clear out any old name that's in there + // m_functionBlockNames.erase(functionBlockNumber); + + return S_OK; +} + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::UpdateStreamConfigurationProperties(internal::PackedUmp128& endpointStreamConfigurationNotificationMessage) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + FILETIME currentTime; + GetSystemTimePreciseAsFileTime(¤tTime); + + BYTE configuredProtocol = MIDIWORDBYTE3(endpointStreamConfigurationNotificationMessage.word0); + + DEVPROP_BOOLEAN configuredToSendJR = MIDIWORDBYTE4LOWBIT1(endpointStreamConfigurationNotificationMessage.word0) ? DEVPROP_TRUE : DEVPROP_FALSE; + DEVPROP_BOOLEAN configuredToReceiveJR = MIDIWORDBYTE4LOWBIT2(endpointStreamConfigurationNotificationMessage.word0) ? DEVPROP_TRUE : DEVPROP_FALSE; + + DEVPROPERTY props[] = + { + {{ PKEY_MIDI_EndpointConfiguredToSendJRTimestamps, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(configuredToSendJR)), &configuredToSendJR }, + + {{ PKEY_MIDI_EndpointConfiguredToReceiveJRTimestamps, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(configuredToReceiveJR)), &configuredToReceiveJR }, + + {{ PKEY_MIDI_EndpointConfiguredProtocol, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BYTE, static_cast(sizeof(configuredProtocol)), &configuredProtocol }, + + {{ PKEY_MIDI_EndpointConfigurationLastUpdateTime, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_FILETIME, static_cast(sizeof(FILETIME)), (PVOID)(¤tTime) }, + + }; + + RETURN_IF_FAILED(m_deviceManager->UpdateEndpointProperties(m_deviceInstanceId.c_str(), ARRAYSIZE(props), (PVOID)props)); + + //OutputDebugString(L"" __FUNCTION__ " Properties Updated"); + + return S_OK; +} + + + +// The device identity is a single message, so we don't keep a copy. Instead, we just do +// the parsing here and then update the property. We assume this has already been identified +// as a type=F, form=0, status=0x02 stream message +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::UpdateDeviceIdentityProperty(internal::PackedUmp128& identityMessage) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + + MidiDeviceIdentityProperty prop; + + prop.Reserved0 = MIDIWORDSHORT2(identityMessage.word0); + + prop.Reserved1 = internal::CleanupByte7(MIDIWORDBYTE1(identityMessage.word1)); + prop.ManufacturerSysExIdByte1 = internal::CleanupByte7(MIDIWORDBYTE2(identityMessage.word1)); + prop.ManufacturerSysExIdByte2 = internal::CleanupByte7(MIDIWORDBYTE3(identityMessage.word1)); + prop.ManufacturerSysExIdByte3 = internal::CleanupByte7(MIDIWORDBYTE4(identityMessage.word1)); + + prop.DeviceFamilyLsb = internal::CleanupByte7(MIDIWORDBYTE1(identityMessage.word2)); + prop.DeviceFamilyMsb = internal::CleanupByte7(MIDIWORDBYTE2(identityMessage.word2)); + prop.DeviceFamilyModelNumberLsb = internal::CleanupByte7(MIDIWORDBYTE3(identityMessage.word2)); + prop.DeviceFamilyModelNumberMsb = internal::CleanupByte7(MIDIWORDBYTE4(identityMessage.word2)); + + prop.SoftwareRevisionLevelByte1 = internal::CleanupByte7(MIDIWORDBYTE1(identityMessage.word3)); + prop.SoftwareRevisionLevelByte2 = internal::CleanupByte7(MIDIWORDBYTE2(identityMessage.word3)); + prop.SoftwareRevisionLevelByte3 = internal::CleanupByte7(MIDIWORDBYTE3(identityMessage.word3)); + prop.SoftwareRevisionLevelByte4 = internal::CleanupByte7(MIDIWORDBYTE4(identityMessage.word3)); + + FILETIME currentTime; + GetSystemTimePreciseAsFileTime(¤tTime); + + DEVPROPERTY props[] = + { + {{ PKEY_MIDI_DeviceIdentity, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BINARY, static_cast(sizeof(prop)), &prop }, + + {{ PKEY_MIDI_DeviceIdentityLastUpdateTime, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_FILETIME, static_cast(sizeof(FILETIME)), (PVOID)(¤tTime) }, + + }; + + RETURN_IF_FAILED(m_deviceManager->UpdateEndpointProperties(m_deviceInstanceId.c_str(), ARRAYSIZE(props), (PVOID)props)); + + //OutputDebugString(L"" __FUNCTION__ " Property Updated"); + + + return S_OK; +} + + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::UpdateEndpointInfoProperties(internal::PackedUmp128& endpointInfoNotificationMessage) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + BYTE umpVersionMajor = MIDIWORDBYTE3(endpointInfoNotificationMessage.word0); + BYTE umpVersionMinor = MIDIWORDBYTE4(endpointInfoNotificationMessage.word0); + BYTE functionBlockCount = internal::CleanupByte7(MIDIWORDBYTE1(endpointInfoNotificationMessage.word1)); + + DEVPROP_BOOLEAN functionBlocksAreStatic = MIDIWORDHIGHBIT(endpointInfoNotificationMessage.word1) ? DEVPROP_TRUE : DEVPROP_FALSE; + + DEVPROP_BOOLEAN supportsMidi1Protocol = MIDIWORDBYTE3LOWBIT1(endpointInfoNotificationMessage.word1) ? DEVPROP_TRUE : DEVPROP_FALSE; + DEVPROP_BOOLEAN supportsMidi2Protocol = MIDIWORDBYTE3LOWBIT2(endpointInfoNotificationMessage.word1) ? DEVPROP_TRUE : DEVPROP_FALSE; + + DEVPROP_BOOLEAN supportsSendingJR = MIDIWORDBYTE4LOWBIT1(endpointInfoNotificationMessage.word1) ? DEVPROP_TRUE : DEVPROP_FALSE; + DEVPROP_BOOLEAN supportsReceivingJR = MIDIWORDBYTE4LOWBIT2(endpointInfoNotificationMessage.word1) ? DEVPROP_TRUE : DEVPROP_FALSE; + + FILETIME currentTime; + GetSystemTimePreciseAsFileTime(¤tTime); + + + DEVPROPERTY props[] = + { + {{ PKEY_MIDI_EndpointSupportsMidi2Protocol, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(supportsMidi2Protocol)), &supportsMidi2Protocol }, + + {{ PKEY_MIDI_EndpointSupportsMidi1Protocol, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(supportsMidi1Protocol)), &supportsMidi1Protocol }, + + {{ PKEY_MIDI_EndpointSupportsReceivingJRTimestamps, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(supportsReceivingJR)), &supportsReceivingJR }, + + {{ PKEY_MIDI_EndpointSupportsSendingJRTimestamps, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(supportsSendingJR)), &supportsSendingJR }, + + {{ PKEY_MIDI_EndpointUmpVersionMajor, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BYTE, static_cast(sizeof(umpVersionMajor)), &umpVersionMajor }, + + {{ PKEY_MIDI_EndpointUmpVersionMinor, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BYTE, static_cast(sizeof(umpVersionMajor)), &umpVersionMinor }, + + {{ PKEY_MIDI_FunctionBlockCount, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BYTE, static_cast(sizeof(functionBlockCount)), &functionBlockCount }, + + {{ PKEY_MIDI_FunctionBlocksAreStatic, DEVPROP_STORE_SYSTEM, nullptr }, + DEVPROP_TYPE_BOOLEAN, static_cast(sizeof(functionBlocksAreStatic)), &functionBlocksAreStatic }, + + {{ PKEY_MIDI_EndpointInformationLastUpdateTime, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_FILETIME, static_cast(sizeof(FILETIME)), (PVOID)(¤tTime) }, + + }; + + RETURN_IF_FAILED(m_deviceManager->UpdateEndpointProperties(m_deviceInstanceId.c_str(), ARRAYSIZE(props), (PVOID)props)); + + return S_OK; +} + + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::UpdateFunctionBlockProperty(internal::PackedUmp128& functionBlockInfoNotificationMessage) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + MidiFunctionBlockProperty prop; + + prop.IsActive = MIDIWORDBYTE3HIGHBIT(functionBlockInfoNotificationMessage.word0); + prop.BlockNumber = internal::CleanupByte7(MIDIWORDBYTE3(functionBlockInfoNotificationMessage.word0)); + prop.Direction = MIDIWORDBYTE4LOWCRUMB1(functionBlockInfoNotificationMessage.word0); + prop.Midi1 = MIDIWORDBYTE4LOWCRUMB2(functionBlockInfoNotificationMessage.word0); + prop.UIHint = MIDIWORDBYTE4LOWCRUMB3(functionBlockInfoNotificationMessage.word0); + prop.Reserved0 = MIDIWORDBYTE4LOWCRUMB4(functionBlockInfoNotificationMessage.word0); + + prop.FirstGroup = MIDIWORDBYTE1(functionBlockInfoNotificationMessage.word1); + prop.NumberOfGroupsSpanned = MIDIWORDBYTE2(functionBlockInfoNotificationMessage.word1); + prop.MidiCIMessageVersionFormat = MIDIWORDBYTE3(functionBlockInfoNotificationMessage.word1); + prop.MaxSysEx8Streams = MIDIWORDBYTE4(functionBlockInfoNotificationMessage.word1); + + prop.Reserved1 = functionBlockInfoNotificationMessage.word2; + prop.Reserved2 = functionBlockInfoNotificationMessage.word3; + + FILETIME currentTime; + GetSystemTimePreciseAsFileTime(¤tTime); + + DEVPROPKEY propKey = FunctionBlockPropertyKeyFromNumber(prop.BlockNumber); + + DEVPROPERTY props[] = + { + {{ propKey, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_BINARY, static_cast(sizeof(prop)), &prop }, + + {{ PKEY_MIDI_FunctionBlocksLastUpdateTime, DEVPROP_STORE_SYSTEM, nullptr}, + DEVPROP_TYPE_FILETIME, static_cast(sizeof(FILETIME)), (PVOID)(¤tTime) }, + + }; + + + RETURN_IF_FAILED(m_deviceManager->UpdateEndpointProperties(m_deviceInstanceId.c_str(), ARRAYSIZE(props), (PVOID)props)); + + return S_OK; +} + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::ProcessStreamMessage(internal::PackedUmp128 ump) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + auto messageStatus = internal::GetStatusFromStreamMessageFirstWord(ump.word0); + + switch (messageStatus) + { + case MIDI_STREAM_MESSAGE_STATUS_ENDPOINT_INFO_NOTIFICATION: + UpdateEndpointInfoProperties(ump); + //m_currentWorkItem.TaskEndpointInfoReceived = true; + //m_currentWorkItem.DeclaredFunctionBlockCount = internal::GetEndpointInfoNotificationNumberOfFunctionBlocksFromSecondWord(ump.word1); + + //RequestAllFunctionBlocks(); + break; + + case MIDI_STREAM_MESSAGE_STATUS_DEVICE_IDENTITY_NOTIFICATION: + UpdateDeviceIdentityProperty(ump); + //m_currentWorkItem.TaskDeviceIdentityReceived = true; + break; + + case MIDI_STREAM_MESSAGE_STATUS_STREAM_CONFIGURATION_NOTIFICATION: + UpdateStreamConfigurationProperties(ump); + break; + + case MIDI_STREAM_MESSAGE_STATUS_FUNCTION_BLOCK_INFO_NOTIFICATION: + UpdateFunctionBlockProperty(ump); + break; + + case MIDI_STREAM_MESSAGE_STATUS_FUNCTION_BLOCK_NAME_NOTIFICATION: + HandleFunctionBlockNameMessage(ump); + break; + + case MIDI_STREAM_MESSAGE_STATUS_ENDPOINT_PRODUCT_INSTANCE_ID_NOTIFICATION: + HandleProductInstanceIdMessage(ump); + break; + + case MIDI_STREAM_MESSAGE_STATUS_ENDPOINT_NAME_NOTIFICATION: + HandleEndpointNameMessage(ump); + break; + + default: + OutputDebugString(L" Message is unidentified stream message\n"); + break; + } + + return S_OK; +} + + + + +_Use_decl_annotations_ +std::wstring +CMidiEndpointProtocolWorker::ParseStreamTextMessage(internal::PackedUmp128& message) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO) + ); + + + // check the status to know which byte is first to be grabbed + + // preallocate a wstring to the maximum length for a single message to avoid reallocation. Fill with zero + + uint8_t maxCharsThisMessage{ 14 }; // TODO + + uint16_t messageStatus = internal::GetStatusFromStreamMessageFirstWord(message.word0); + + if (messageStatus == MIDI_STREAM_MESSAGE_STATUS_FUNCTION_BLOCK_NAME_NOTIFICATION) + { + // function block name messages have 13 character bytes instead of 14 due to having the FB number in the message + maxCharsThisMessage = 13; + } + else + { + maxCharsThisMessage = 14; + } + + std::wstring text; + text.reserve(maxCharsThisMessage + 1); // try to avoid reallocations + + + if (maxCharsThisMessage == 14) + { + if (MIDIWORDBYTE3(message.word0) != 0) text.push_back((wchar_t)MIDIWORDBYTE3(message.word0)); + } + + // this unroll is easier than looping, honestly. + // Also, the property set completely fails if there are any embedded nuls, so need to + // ignore any in the source data + if (MIDIWORDBYTE4(message.word0) != 0) text.push_back((wchar_t)MIDIWORDBYTE4(message.word0)); + + if (MIDIWORDBYTE1(message.word1) != 0) text.push_back((wchar_t)MIDIWORDBYTE1(message.word1)); + if (MIDIWORDBYTE2(message.word1) != 0) text.push_back((wchar_t)MIDIWORDBYTE2(message.word1)); + if (MIDIWORDBYTE3(message.word1) != 0) text.push_back((wchar_t)MIDIWORDBYTE3(message.word1)); + if (MIDIWORDBYTE4(message.word1) != 0) text.push_back((wchar_t)MIDIWORDBYTE4(message.word1)); + + if (MIDIWORDBYTE1(message.word2) != 0) text.push_back((wchar_t)MIDIWORDBYTE1(message.word2)); + if (MIDIWORDBYTE2(message.word2) != 0) text.push_back((wchar_t)MIDIWORDBYTE2(message.word2)); + if (MIDIWORDBYTE3(message.word2) != 0) text.push_back((wchar_t)MIDIWORDBYTE3(message.word2)); + if (MIDIWORDBYTE4(message.word2) != 0) text.push_back((wchar_t)MIDIWORDBYTE4(message.word2)); + + if (MIDIWORDBYTE1(message.word3) != 0) text.push_back((wchar_t)MIDIWORDBYTE1(message.word3)); + if (MIDIWORDBYTE2(message.word3) != 0) text.push_back((wchar_t)MIDIWORDBYTE2(message.word3)); + if (MIDIWORDBYTE3(message.word3) != 0) text.push_back((wchar_t)MIDIWORDBYTE3(message.word3)); + if (MIDIWORDBYTE4(message.word3) != 0) text.push_back((wchar_t)MIDIWORDBYTE4(message.word3)); + + text.shrink_to_fit(); + + return text; +} + + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::HandleFunctionBlockNameMessage(internal::PackedUmp128& functionBlockNameMessage) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + + uint8_t functionBlockNumber = MIDIWORDBYTE3(functionBlockNameMessage.word0); + + switch (internal::GetFormFromStreamMessageFirstWord(functionBlockNameMessage.word0)) + { + case MIDI_STREAM_MESSAGE_MULTI_FORM_COMPLETE: // complete name in single message. Just update property + RETURN_IF_FAILED(UpdateFunctionBlockNameProperty(functionBlockNumber, ParseStreamTextMessage(functionBlockNameMessage))); + break; + + case MIDI_STREAM_MESSAGE_MULTI_FORM_START: // start of multi-part name message. Overwrite any other name in the map + { + std::wstring name = ParseStreamTextMessage(functionBlockNameMessage); + + m_functionBlockNames.insert_or_assign(functionBlockNumber, name); + } + break; + + case MIDI_STREAM_MESSAGE_MULTI_FORM_CONTINUE: //continuation of multi-part name message. Append to name in map + if (m_functionBlockNames.find(functionBlockNumber) != m_functionBlockNames.end()) + { + std::wstring name = m_functionBlockNames.find(functionBlockNumber)->second; + name += ParseStreamTextMessage(functionBlockNameMessage); + + m_functionBlockNames.insert_or_assign(functionBlockNumber, name); + } + else + { + // name isn't already in the map, so a start message was skipped. Don't append anything at all. + } + break; + + case MIDI_STREAM_MESSAGE_MULTI_FORM_END: // end of multi-part name message. Finish name and update property + if (m_functionBlockNames.find(functionBlockNumber) != m_functionBlockNames.end()) + { + std::wstring name = m_functionBlockNames.find(functionBlockNumber)->second; + name += ParseStreamTextMessage(functionBlockNameMessage); + + RETURN_IF_FAILED(UpdateFunctionBlockNameProperty(functionBlockNumber, name)); + } + else + { + // name isn't already in the map, so at least start message was skipped. Don't append anything at all. + } + break; + + default: + // won't actually happen because the Form field is only 2 bits + break; + } + + return S_OK; +} + + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::HandleEndpointNameMessage(internal::PackedUmp128& endpointNameMessage) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + switch (internal::GetFormFromStreamMessageFirstWord(endpointNameMessage.word0)) + { + case MIDI_STREAM_MESSAGE_MULTI_FORM_COMPLETE: // complete name in single message. Just update property + m_endpointName = ParseStreamTextMessage(endpointNameMessage); + RETURN_IF_FAILED(UpdateEndpointNameProperty()); + break; + + case MIDI_STREAM_MESSAGE_MULTI_FORM_START: // start of multi-part name message. Overwrite any other name in the map + { + m_endpointName = ParseStreamTextMessage(endpointNameMessage); + } + break; + + case MIDI_STREAM_MESSAGE_MULTI_FORM_CONTINUE: //continuation of multi-part name message. Append to name in map + if (!m_endpointName.empty()) + { + m_endpointName += ParseStreamTextMessage(endpointNameMessage); + } + else + { + // name isn't already started, so a start message was skipped. Don't append anything at all. + } + break; + + case MIDI_STREAM_MESSAGE_MULTI_FORM_END: // end of multi-part name message. Finish name and update property + if (!m_endpointName.empty()) + { + m_endpointName += ParseStreamTextMessage(endpointNameMessage); + RETURN_IF_FAILED(UpdateEndpointNameProperty()); + } + else + { + // name isn't already started, so a start message was skipped. Don't append anything at all. + } + + break; + + default: + // won't actually happen because the Form field is only 2 bits + break; + } + + return S_OK; +} + +_Use_decl_annotations_ +HRESULT +CMidiEndpointProtocolWorker::HandleProductInstanceIdMessage(internal::PackedUmp128& productInstanceIdMessage) +{ + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + switch (internal::GetFormFromStreamMessageFirstWord(productInstanceIdMessage.word0)) + { + case MIDI_STREAM_MESSAGE_MULTI_FORM_COMPLETE: // complete name in single message. Just update property + OutputDebugString(L" MIDI_STREAM_MESSAGE_MULTI_FORM_COMPLETE\n"); + m_productInstanceId = ParseStreamTextMessage(productInstanceIdMessage); + RETURN_IF_FAILED(UpdateEndpointProductInstanceIdProperty()); + break; + + case MIDI_STREAM_MESSAGE_MULTI_FORM_START: // start of multi-part name message. Overwrite any other name in the map + { + OutputDebugString(L" MIDI_STREAM_MESSAGE_MULTI_FORM_START\n"); + m_productInstanceId = ParseStreamTextMessage(productInstanceIdMessage); + } + break; + + case MIDI_STREAM_MESSAGE_MULTI_FORM_CONTINUE: //continuation of multi-part name message. Append to name in map + OutputDebugString(L" MIDI_STREAM_MESSAGE_MULTI_FORM_CONTINUE\n"); + if (!m_productInstanceId.empty()) + { + m_productInstanceId += ParseStreamTextMessage(productInstanceIdMessage); + } + else + { + // name isn't already started, so a start message was skipped. Don't append anything at all. + } + break; + + case MIDI_STREAM_MESSAGE_MULTI_FORM_END: // end of multi-part name message. Finish name and update property + OutputDebugString(L" MIDI_STREAM_MESSAGE_MULTI_FORM_END\n"); + if (!m_productInstanceId.empty()) + { + m_productInstanceId += ParseStreamTextMessage(productInstanceIdMessage); + RETURN_IF_FAILED(UpdateEndpointProductInstanceIdProperty()); + } + else + { + // name isn't already started, so a start message was skipped. Don't append anything at all. + } + + break; + + default: + // won't actually happen because the Form field is only 2 bits + break; + } + + return S_OK; + +} + + diff --git a/src/api/Service/Exe/MidiEndpointProtocolWorker.h b/src/api/Service/Exe/MidiEndpointProtocolWorker.h new file mode 100644 index 00000000..40bc4ec8 --- /dev/null +++ b/src/api/Service/Exe/MidiEndpointProtocolWorker.h @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#pragma once + +#include +#include + +class CMidiEndpointProtocolWorker : public Microsoft::WRL::RuntimeClass< + Microsoft::WRL::RuntimeClassFlags, + IMidiCallback> +{ +public: + CMidiEndpointProtocolWorker() = default; + ~CMidiEndpointProtocolWorker() {} + + STDMETHOD(Initialize)( + _In_ GUID SessionId, + _In_ GUID AbstractionGuid, + _In_ LPCWSTR DeviceInterfaceId, + _In_ std::shared_ptr& ClientManager, + _In_ std::shared_ptr& DeviceManager, + _In_ std::shared_ptr& SessionTracker + ); + + STDMETHOD(ListenForMetadata)(); + + STDMETHOD(Callback)(_In_ PVOID Data, _In_ UINT Size, _In_ LONGLONG Position, _In_ LONGLONG Context); + + STDMETHOD(Cleanup)(); + +private: + std::wstring m_deviceInterfaceId; + std::wstring m_deviceInstanceId; + GUID m_sessionId; + GUID m_abstractionGuid; + + LONGLONG m_context{ 0 }; + + wil::unique_event_nothrow m_allNegotiationMessagesReceived; + wil::unique_event_nothrow m_queueWorkerThreadWakeup; + + // true if we're closing down + bool m_shutdown{ false }; + + //wil::com_ptr_nothrow m_MidiDeviceManager; + std::shared_ptr m_clientManager; + std::shared_ptr m_deviceManager; + std::shared_ptr m_sessionTracker; + + wil::com_ptr_nothrow m_midiBiDiDevice; + + + // these are holding locations while names are built + std::wstring m_endpointName{}; + std::wstring m_productInstanceId{}; + std::map m_functionBlockNames; + + + std::wstring ParseStreamTextMessage(_In_ internal::PackedUmp128& message); + + DEVPROPKEY FunctionBlockPropertyKeyFromNumber(_In_ uint8_t functionBlockNumber); + DEVPROPKEY FunctionBlockNamePropertyKeyFromNumber(_In_ uint8_t functionBlockNumber); + + HRESULT UpdateEndpointNameProperty(); + HRESULT UpdateEndpointProductInstanceIdProperty(); + HRESULT UpdateFunctionBlockNameProperty(_In_ uint8_t functionBlockNumber, _In_ std::wstring value); + + HRESULT UpdateDeviceIdentityProperty(_In_ internal::PackedUmp128& identityMessage); + HRESULT UpdateEndpointInfoProperties(_In_ internal::PackedUmp128& endpointInfoNotificationMessage); + HRESULT UpdateStreamConfigurationProperties(_In_ internal::PackedUmp128& endpointStreamConfigurationNotificationMessage); + HRESULT UpdateFunctionBlockProperty(_In_ internal::PackedUmp128& functionBlockInfoNotificationMessage); + + HRESULT HandleFunctionBlockNameMessage(_In_ internal::PackedUmp128& functionBlockNameMessage); + HRESULT HandleEndpointNameMessage(_In_ internal::PackedUmp128& endpointNameMessage); + HRESULT HandleProductInstanceIdMessage(_In_ internal::PackedUmp128& productInstanceIdMessage); + + HRESULT ProcessStreamMessage(_In_ internal::PackedUmp128 ump); +}; + + diff --git a/src/api/Service/Exe/MidiSessionTracker.cpp b/src/api/Service/Exe/MidiSessionTracker.cpp index 0d5633d6..de99dae3 100644 --- a/src/api/Service/Exe/MidiSessionTracker.cpp +++ b/src/api/Service/Exe/MidiSessionTracker.cpp @@ -37,9 +37,18 @@ CMidiSessionTracker::AddClientSession( MidiSessionEntry entry; entry.SessionId = SessionId; - entry.SessionName = internal::TrimmedWStringCopy(SessionName); + + if (SessionName != nullptr) + { + entry.SessionName = internal::TrimmedWStringCopy(SessionName); + } + entry.ClientProcessId = ClientProcessId; - entry.ProcessName = ClientProcessName; + + if (ClientProcessName != nullptr) + { + entry.ProcessName = ClientProcessName; + } entry.StartTime = std::chrono::system_clock::now(); diff --git a/src/api/Service/Exe/MidiSrv.cpp b/src/api/Service/Exe/MidiSrv.cpp index f7c133c1..64f47d06 100644 --- a/src/api/Service/Exe/MidiSrv.cpp +++ b/src/api/Service/Exe/MidiSrv.cpp @@ -92,28 +92,53 @@ CMidiSrv::Initialize() NULL)); - // this is an ugly set of casts, but the reinterpret_cast error only - // comes up with C++/20. Risk of a straight c-style cast here going - // poorly is low. Error comes from the function taking non-const params - // but our arguments here are const. - RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(RpcServerUseProtseqEp( - (RPC_WSTR)MIDISRV_LRPC_PROTOCOL, - RPC_C_PROTSEQ_MAX_REQS_DEFAULT, - (RPC_WSTR)MIDISRV_ENDPOINT, - rpcSecurityDescriptor.get()))); - - //RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(RpcServerUseProtseqEp( - // reinterpret_cast(MIDISRV_LRPC_PROTOCOL), - // RPC_C_PROTSEQ_MAX_REQS_DEFAULT, - // reinterpret_cast(MIDISRV_ENDPOINT), - // rpcSecurityDescriptor.get()))); - - RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(RpcServerRegisterIf3( - MidiSrvRPC_v1_0_s_ifspec, - NULL, NULL, - RPC_IF_AUTOLISTEN | RPC_IF_ALLOW_LOCAL_ONLY, - RPC_C_LISTEN_MAX_CALLS_DEFAULT, 0, - MidiSrvRpcIfCallback, rpcSecurityDescriptor.get()))); + if (rpcSecurityDescriptor.is_valid()) + { + // this is an ugly set of casts, but the reinterpret_cast error only + // comes up with C++/20. Risk of a straight c-style cast here going + // poorly is low. Error comes from the function taking non-const params + // but our arguments here are const. + auto rpcStatus = RpcServerUseProtseqEp( + (RPC_WSTR)MIDISRV_LRPC_PROTOCOL, + RPC_C_PROTSEQ_MAX_REQS_DEFAULT, + (RPC_WSTR)MIDISRV_ENDPOINT, + rpcSecurityDescriptor.get()); + + auto rpcHr = HRESULT_FROM_RPCSTATUS(rpcStatus); + + if (FAILED(rpcHr)) + { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingHResult(rpcHr, "hresult"), + TraceLoggingLong(rpcStatus, "rpc_status"), + TraceLoggingWideString(L"RpcServerUseProtseqEp failed. It's likely the service is not responding due to a failed startup.", "message") + ); + } + + RETURN_IF_FAILED(rpcHr); + + RETURN_IF_FAILED(HRESULT_FROM_RPCSTATUS(RpcServerRegisterIf3( + MidiSrvRPC_v1_0_s_ifspec, + NULL, NULL, + RPC_IF_AUTOLISTEN | RPC_IF_ALLOW_LOCAL_ONLY, + RPC_C_LISTEN_MAX_CALLS_DEFAULT, 0, + MidiSrvRpcIfCallback, + rpcSecurityDescriptor.get()))); + } + else + { + TraceLoggingWrite( + MidiSrvTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Returned RPC Security Descriptor is not valid", "message") + ); + } cleanupOnError.release(); diff --git a/src/api/Service/Exe/MidiSrv.vcxproj b/src/api/Service/Exe/MidiSrv.vcxproj index 15039a27..f1a086c6 100644 --- a/src/api/Service/Exe/MidiSrv.vcxproj +++ b/src/api/Service/Exe/MidiSrv.vcxproj @@ -125,12 +125,12 @@ - %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) + %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\Midi2.UmpProtocolDownscalerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) - %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) + %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\Midi2.UmpProtocolDownscalerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) @@ -169,12 +169,12 @@ - %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) + %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\Midi2.UmpProtocolDownscalerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) - %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) + %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\Midi2.UmpProtocolDownscalerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) @@ -213,7 +213,7 @@ - %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) + %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\Midi2.UmpProtocolDownscalerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) @@ -237,7 +237,7 @@ - %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) + %(AdditionalIncludeDirectories);$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(ProjectDir)\..\Inc;$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.diagnosticsabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.virtualmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.networkmidiabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\MidiSrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.JitterReductionTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.EndpointMetadataListenerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\Midi2.UmpProtocolDownscalerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.BS2UMPTransform\$(Platform)\$(Configuration);$(SolutionDir)Libs\AbstractionUtilities\inc;$(SolutionDir)VSFiles\intermediate\midi2.SchedulerTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.UMP2BSTransform\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) @@ -264,6 +264,7 @@ + @@ -286,6 +287,7 @@ + diff --git a/src/api/Service/Exe/MidiSrv.vcxproj.filters b/src/api/Service/Exe/MidiSrv.vcxproj.filters index e6831e87..f30c2149 100644 --- a/src/api/Service/Exe/MidiSrv.vcxproj.filters +++ b/src/api/Service/Exe/MidiSrv.vcxproj.filters @@ -13,6 +13,12 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + {93df485d-cd67-4e5c-92f6-2593d5bb9550} + + + {c9dff48d-794b-440a-a8e3-7365ee0e48ce} + @@ -51,12 +57,15 @@ Source Files - - Source Files - Source Files + + Source Files\Protocol + + + Source Files\Protocol + @@ -98,12 +107,15 @@ Header Files - - Header Files - Header Files + + Header Files\Protocol + + + Header Files\Protocol + diff --git a/src/api/Service/Exe/MidiSrvRpc.cpp b/src/api/Service/Exe/MidiSrvRpc.cpp index 1c284188..c3562e52 100644 --- a/src/api/Service/Exe/MidiSrvRpc.cpp +++ b/src/api/Service/Exe/MidiSrvRpc.cpp @@ -98,13 +98,7 @@ HRESULT MidiSrvCreateClient( // Client manager creates the client, fills in the MIDISRV_CLIENT information RETURN_IF_FAILED(g_MidiService->GetClientManager(clientManager)); - RETURN_IF_FAILED(clientManager->CreateMidiClient(BindingHandle, MidiDevice, SessionId, CreationParams, createdClient)); - - // Register this client (this code moved to the client manager) - // std::shared_ptr sessionTracker; - // RETURN_IF_FAILED(g_MidiService->GetSessionTracker(sessionTracker)); - // RETURN_IF_FAILED(sessionTracker->AddClientEndpointConnection(SessionId, MidiDevice)); - + RETURN_IF_FAILED(clientManager->CreateMidiClient(BindingHandle, MidiDevice, SessionId, CreationParams, createdClient, false)); // Success, transfer the MIDISRV_CLIENT data to the caller. *Client = createdClient; diff --git a/src/api/Service/Exe/stdafx.h b/src/api/Service/Exe/stdafx.h index c155d9a6..94c77ba7 100644 --- a/src/api/Service/Exe/stdafx.h +++ b/src/api/Service/Exe/stdafx.h @@ -108,6 +108,7 @@ namespace shared = ::Windows::Devices::Midi2::Internal::Shared; #include "Midi2UMP2BSTransform.h" #include "Midi2SchedulerTransform.h" #include "Midi2EndpointMetadataListenerTransform.h" +#include "Midi2UmpProtocolDownscalerTransform.h" // MidiSrv internal classes @@ -134,6 +135,7 @@ class CMidiDevicePipe; #include "MidiTransformPipe.h" #include "MidiClientManager.h" +#include "MidiEndpointProtocolWorker.h" #include "MidiEndpointProtocolManager.h" #include "MidiSessionTracker.h" diff --git a/src/api/Service/Inc/MidiClientManager.h b/src/api/Service/Inc/MidiClientManager.h index d7816560..494f795e 100644 --- a/src/api/Service/Inc/MidiClientManager.h +++ b/src/api/Service/Inc/MidiClientManager.h @@ -15,64 +15,80 @@ class CMidiClientManager CMidiClientManager() {} ~CMidiClientManager() {} - HRESULT Initialize(_In_ std::shared_ptr& performanceManager, - _In_ std::shared_ptr& processManager, - _In_ std::shared_ptr& deviceManager, - _In_ std::shared_ptr& sessionTracker); + HRESULT Initialize( + _In_ std::shared_ptr& performanceManager, + _In_ std::shared_ptr& processManager, + _In_ std::shared_ptr& deviceManager, + _In_ std::shared_ptr& sessionTracker); + + HRESULT CreateMidiClient( + _In_ handle_t, + _In_ LPCWSTR, + _In_ GUID, + _In_ PMIDISRV_CLIENTCREATION_PARAMS, + _In_ PMIDISRV_CLIENT, + _In_ BOOL); + + HRESULT DestroyMidiClient( + _In_ handle_t, + _In_ MidiClientHandle); - HRESULT CreateMidiClient(_In_ handle_t, - _In_ LPCWSTR, - _In_ GUID, - _In_ PMIDISRV_CLIENTCREATION_PARAMS, - _In_ PMIDISRV_CLIENT); - HRESULT DestroyMidiClient(_In_ handle_t, - _In_ MidiClientHandle); HRESULT Cleanup(); private: - HRESULT GetMidiClient(_In_ handle_t, - _In_ LPCWSTR, - _In_ GUID, - _In_ PMIDISRV_CLIENTCREATION_PARAMS, - _In_ PMIDISRV_CLIENT, - _In_ wil::unique_handle&, - _In_ wil::com_ptr_nothrow&, - _In_ BOOL); - - HRESULT GetMidiDevice(_In_ handle_t, - _In_ LPCWSTR, - _In_ PMIDISRV_CLIENTCREATION_PARAMS, - _In_ wil::com_ptr_nothrow&); + HRESULT GetMidiClient( + _In_ handle_t, + _In_ LPCWSTR, + _In_ GUID, + _In_ PMIDISRV_CLIENTCREATION_PARAMS, + _In_ PMIDISRV_CLIENT, + _In_ wil::unique_handle&, + _In_ wil::com_ptr_nothrow&, + _In_ BOOL); + + HRESULT GetMidiDevice( + _In_ handle_t, + _In_ LPCWSTR, + _In_ PMIDISRV_CLIENTCREATION_PARAMS, + _In_ wil::com_ptr_nothrow&); // TODO: These should be made more generic and go by a configuration, rather than have // discrete methods for each type of transform. But right now, it's about making the // transform process work properly HRESULT GetMidiTransform( - _In_ handle_t, - _In_ MidiFlow, - _In_ MidiDataFormat, - _In_ MidiDataFormat, - _In_ wil::com_ptr_nothrow&, - _In_ wil::com_ptr_nothrow&); + _In_ handle_t, + _In_ MidiFlow, + _In_ MidiDataFormat, + _In_ MidiDataFormat, + _In_ wil::com_ptr_nothrow&, + _In_ wil::com_ptr_nothrow&); + + HRESULT + GetMidiProtocolDownscalerTransform( + _In_ handle_t, + _In_ MidiFlow, + _In_ wil::com_ptr_nothrow&, + _In_ wil::com_ptr_nothrow&, + _In_ wil::com_ptr_nothrow&); HRESULT GetMidiScheduler( - _In_ handle_t, - _In_ MidiFlow, - _In_ wil::com_ptr_nothrow&, - _In_ wil::com_ptr_nothrow&, - _In_ wil::com_ptr_nothrow&); + _In_ handle_t, + _In_ MidiFlow, + _In_ wil::com_ptr_nothrow&, + _In_ wil::com_ptr_nothrow&, + _In_ wil::com_ptr_nothrow&); HRESULT GetMidiEndpointMetadataHandler( - _In_ handle_t, - _In_ MidiFlow, - _In_ wil::com_ptr_nothrow&, - _In_ wil::com_ptr_nothrow&, - _In_ wil::com_ptr_nothrow&); + _In_ handle_t, + _In_ MidiFlow, + _In_ wil::com_ptr_nothrow&, + _In_ wil::com_ptr_nothrow&, + _In_ wil::com_ptr_nothrow&); wil::critical_section m_ClientManagerLock; diff --git a/src/api/Test/Libs/MidiTestCommon/MidiTestCommon.vcxproj b/src/api/Test/Libs/MidiTestCommon/MidiTestCommon.vcxproj index b0d5fef3..cc061da1 100644 --- a/src/api/Test/Libs/MidiTestCommon/MidiTestCommon.vcxproj +++ b/src/api/Test/Libs/MidiTestCommon/MidiTestCommon.vcxproj @@ -73,13 +73,13 @@ true $(SolutionDir)VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration)\ $(SolutionDir)VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration)\ - $(VC_LibraryPath_x64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(WindowsSDK_LibraryPath_x64) + $(VC_LibraryPath_Arm64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(WindowsSDK_LibraryPath_Arm64) true $(SolutionDir)VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration)\ $(SolutionDir)VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration)\ - $(VC_LibraryPath_x64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(WindowsSDK_LibraryPath_x64) + $(VC_LibraryPath_Arm64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(WindowsSDK_LibraryPath_Arm64) $(SolutionDir)VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration)\ diff --git a/src/api/Test/Midi2.Abstraction.unittests/Midi2.Abstraction.unittests.vcxproj b/src/api/Test/Midi2.Abstraction.unittests/Midi2.Abstraction.unittests.vcxproj index 314fb1e9..08c8fae7 100644 --- a/src/api/Test/Midi2.Abstraction.unittests/Midi2.Abstraction.unittests.vcxproj +++ b/src/api/Test/Midi2.Abstraction.unittests/Midi2.Abstraction.unittests.vcxproj @@ -73,9 +73,9 @@ $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)\VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\midiswenum\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midixproc\$(Platform)\$(Configuration) - $(VC_LibraryPath_ARM64);$(WindowsSDK_LibraryPath_ARM64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)\VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\midiswenum\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midixproc\$(Platform)\$(Configuration) $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(VC_LibraryPath_ARM64);$(WindowsSDK_LibraryPath_ARM64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)\VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\midiswenum\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midixproc\$(Platform)\$(Configuration) $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ @@ -83,9 +83,9 @@ $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)\VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\midiswenum\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midixproc\$(Platform)\$(Configuration) - $(VC_LibraryPath_ARM64);$(WindowsSDK_LibraryPath_ARM64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)\VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\midiswenum\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midixproc\$(Platform)\$(Configuration) $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(VC_LibraryPath_ARM64);$(WindowsSDK_LibraryPath_ARM64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)\VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\midiswenum\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\test\miditestcommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midixproc\$(Platform)\$(Configuration) @@ -103,7 +103,7 @@ %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)test\inc;$(WindowsSdkDir)Testing\Development\inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.ksabstraction\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) Level4 true - stdcpp20 + stdcpp17 %(AdditionalDependencies);midixproc.lib;onecoreuap.lib;ksuser.lib;avrt.lib;midikscommon.lib;midiswenum.lib;miditestcommon.lib;$(CoreLibraryDependencies) diff --git a/src/api/Test/Midi2.Service.unittests/Midi2.Service.unittests.vcxproj b/src/api/Test/Midi2.Service.unittests/Midi2.Service.unittests.vcxproj index bc8c45e2..4391f465 100644 --- a/src/api/Test/Midi2.Service.unittests/Midi2.Service.unittests.vcxproj +++ b/src/api/Test/Midi2.Service.unittests/Midi2.Service.unittests.vcxproj @@ -103,7 +103,7 @@ Level4 %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)test\inc;$(WindowsSdkDir)\Testing\Development\inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midisrv\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midi2.midisrvabstraction\$(Platform)\$(Configuration) true - stdcpp20 + stdcpp17 %(AdditionalDependencies);onecoreuap.lib;ksuser.lib;avrt.lib;midixproc.lib;midikscommon.lib;midiswenum.lib;miditestcommon.lib;$(CoreLibraryDependencies) diff --git a/src/api/Transform/ByteStreamToUMP/Midi2.BS2UMPMidiTransform.cpp b/src/api/Transform/ByteStreamToUMP/Midi2.BS2UMPMidiTransform.cpp index 52b80e4c..fb0de236 100644 --- a/src/api/Transform/ByteStreamToUMP/Midi2.BS2UMPMidiTransform.cpp +++ b/src/api/Transform/ByteStreamToUMP/Midi2.BS2UMPMidiTransform.cpp @@ -56,8 +56,6 @@ CMidi2BS2UMPMidiTransform::SendMidiMessage( LONGLONG Position ) { - OutputDebugString(L"" __FUNCTION__); - // Send the bytestream byte(s) to the parser BYTE *data = (BYTE *)Data; for (UINT i = 0; i < Length; i++) @@ -65,7 +63,7 @@ CMidi2BS2UMPMidiTransform::SendMidiMessage( m_BS2UMP.bytestreamParse(data[i]); } - // retrieve teh UMP(s) from the parser + // retrieve the UMP(s) from the parser // and sent it on while (m_BS2UMP.availableUMP()) { @@ -76,6 +74,8 @@ CMidi2BS2UMPMidiTransform::SendMidiMessage( umpMessage[umpCount] = m_BS2UMP.readUMP(); } + // Note from PMB for Gary: Pretty sure "ump" in this context is just a single UMP word. Some messages like + // SysEx are larger (64 bit) and so would be two words back-to-back, so umpCount would be 2 or greater. if (umpCount > 0) { // there are 4 bytes per each 32 bit UMP returned by the parser. diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerMidiTransform.cpp b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerMidiTransform.cpp new file mode 100644 index 00000000..cb40d510 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerMidiTransform.cpp @@ -0,0 +1,248 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#include "pch.h" +#include "bytestreamToUMP.h" +//#include "midi2.BS2UMPtransform.h" + +_Use_decl_annotations_ +HRESULT +CMidi2UmpProtocolDownscalerMidiTransform::Initialize( + LPCWSTR Device, + PTRANSFORMCREATIONPARAMS CreationParams, + DWORD *, + IMidiCallback * Callback, + LONGLONG Context, + IUnknown* /*MidiDeviceManager*/ +) +{ + TraceLoggingWrite( + MidiUmpProtocolDownscalerTransformTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + // this only converts UMP to UMP + RETURN_HR_IF(ERROR_UNSUPPORTED_TYPE, CreationParams->DataFormatIn != MidiDataFormat_UMP); + RETURN_HR_IF(ERROR_UNSUPPORTED_TYPE, CreationParams->DataFormatOut != MidiDataFormat_UMP); + + m_Device = Device; + m_Callback = Callback; + m_Context = Context; + + return S_OK; +} + +HRESULT +CMidi2UmpProtocolDownscalerMidiTransform::Cleanup() +{ + TraceLoggingWrite( + MidiUmpProtocolDownscalerTransformTelemetryProvider::Provider(), + __FUNCTION__, + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this") + ); + + return S_OK; +} + + + + +_Use_decl_annotations_ +HRESULT +CMidi2UmpProtocolDownscalerMidiTransform::SendMidiMessage( + PVOID Data, + UINT Length, + LONGLONG Position +) +{ + // only translate MT4 to MT2 (MIDI 2.0 protocol to MIDI 1.0 protocol) + if (internal::GetUmpMessageTypeFromFirstWord((uint32_t&)Data) == MIDI_UMP_MESSAGE_TYPE_MIDI2_CHANNEL_VOICE_64) + { + // downscale MIDI 2 to MIDI 1. Note that we treat the note index as a note number no matter + // what. There was quite a bit of discussion about this, and the main architects in the MIDI + // association felt that a wrong note is better than no note. So in cases where the note number + // is an index and exact pitch is specified, this will send an incorrect note. This is by + // consensus and therefore by design. + + uint8_t* incomingDataPointer = (uint8_t*)Data; + + uint32_t originalWord0 = *(const uint32_t*)(incomingDataPointer); + uint32_t originalWord1 = *(const uint32_t*)(incomingDataPointer + sizeof(uint32_t)); + + uint8_t groupIndex = internal::GetGroupIndexFromFirstWord(originalWord0); + uint8_t channelIndex = internal::GetChannelIndexFromFirstWord(originalWord0); + uint8_t status = internal::GetStatusFromChannelVoiceMessage(originalWord0); + + // converting only to 32 bit messages, but some MIDI 2.0 messages convert into multiple MIDI 1.0 messages + // a stack reference here is ok because none of these callbacks are async in any way. It's a chain. + + // first we check for messages that return more than one resulting translated message + if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_PROGRAM_CHANGE || + status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_REG_CONTROLLER || + status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_ASSIGN_CONTROLLER) + { + uint32_t resultingMessages[4]; + uint32_t resultingMessageCount{ 0 }; + + if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_PROGRAM_CHANGE) + { + // this one needs to create three separate messages so we special case here + uint8_t program = MIDIWORDBYTE1(originalWord1); + + // the bank is valid if the least significant bit in word 0 is set + bool bankValid = (originalWord0 & (uint32_t)0x1) == 0x1; + + resultingMessageCount = 0; + + if (bankValid) + { + uint8_t bankMsb = MIDIWORDBYTE3(originalWord1); + uint8_t bankLsb = MIDIWORDBYTE4(originalWord1); + + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_UMP_MIDI1_BANK_SELECT_MSB_CC_INDEX, bankMsb); + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_UMP_MIDI1_BANK_SELECT_LSB_CC_INDEX, bankLsb);; + } + + resultingMessages[resultingMessageCount++] = UMPMessage::mt2ProgramChange(groupIndex, channelIndex, program); + } + else if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_REG_CONTROLLER) + { + // RPN - converts to four MIDI 1.0 messages + + uint8_t bank = internal::CleanupByte7(MIDIWORDBYTE3(originalWord0)); + uint8_t index = internal::CleanupByte7(MIDIWORDBYTE4(originalWord0)); + + uint8_t coarse = internal::CleanupByte7(MIDIWORDBYTE1(originalWord1)); + uint8_t fine = internal::CleanupByte7(MIDIWORDBYTE2(originalWord1)); + + resultingMessageCount = 0; + + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_RPN_CC_NUMBER_BANK, bank); + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_RPN_CC_NUMBER_INDEX, index); + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_RPN_CC_NUMBER_DATA_COARSE, coarse); + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_RPN_CC_NUMBER_DATA_FINE, fine); + } + else if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_ASSIGN_CONTROLLER) + { + // NRPN - converts to four MIDI 1.0 messages + + uint8_t bank = MIDIWORDBYTE3(originalWord0); + uint8_t index = MIDIWORDBYTE4(originalWord0); + + uint8_t coarse = internal::CleanupByte7(MIDIWORDBYTE1(originalWord1)); + uint8_t fine = internal::CleanupByte7(MIDIWORDBYTE2(originalWord1)); + + resultingMessageCount = 0; + + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_NRPN_CC_NUMBER_BANK, bank); + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_NRPN_CC_NUMBER_INDEX, index); + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_NRPN_CC_NUMBER_DATA_COARSE, coarse); + resultingMessages[resultingMessageCount++] = UMPMessage::mt2CC(groupIndex, channelIndex, MIDI_NRPN_CC_NUMBER_DATA_FINE, fine); + } + + // send all the generated messages in order + if (resultingMessageCount > 0 && m_Callback != nullptr) + { + for (uint32_t i = 0; i < resultingMessageCount; i++) + { + RETURN_IF_FAILED(m_Callback->Callback(&resultingMessages[i], sizeof(uint32_t), Position + i, m_Context)); + } + } + } + else + { + // these messages all result in a single new message to send. + // the one exception is the fall-through which just passes along the original data. + + uint32_t newWord0; + PVOID newData; + UINT newLength; + + if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_NOTE_OFF) + { + // note number / index is third byte + uint8_t noteNumber = MIDIWORDBYTE3(originalWord0); + uint8_t newVelocity = (uint8_t)(M2Utils::scaleDown(MIDIWORDSHORT1(originalWord1), 16, 7)); + newWord0 = UMPMessage::mt2NoteOff(groupIndex, channelIndex, noteNumber, newVelocity); + newData = &newWord0; + newLength = sizeof(uint32_t); + } + else if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_NOTE_ON) + { + uint8_t noteNumber = MIDIWORDBYTE3(originalWord0); + uint8_t newVelocity = (uint8_t)(M2Utils::scaleDown(MIDIWORDSHORT1(originalWord1), 16, 7)); + //if (newVelocity == 0) newVelocity = 1; + newWord0 = UMPMessage::mt2NoteOff(groupIndex, channelIndex, noteNumber, newVelocity); + newData = &newWord0; + newLength = sizeof(uint32_t); + } + else if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_POLY_PRESSURE) + { + uint8_t noteNumber = MIDIWORDBYTE3(originalWord0); + // pressure is entire second word + uint8_t newPressure = (uint8_t)(M2Utils::scaleDown(originalWord1, 32, 7)); + newWord0 = UMPMessage::mt2PolyPressure(groupIndex, channelIndex, noteNumber, newPressure); + + newData = &newWord0; + newLength = sizeof(uint32_t); + } + else if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_CONTROL_CHANGE) + { + uint8_t controlIndex = MIDIWORDBYTE3(originalWord0); + // value is entire second word + uint8_t newValue = (uint8_t)(M2Utils::scaleDown(originalWord1, 32, 7)); + newWord0 = UMPMessage::mt2CC(groupIndex, channelIndex, controlIndex, newValue); + + newData = &newWord0; + newLength = sizeof(uint32_t); + } + else if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_CHANNEL_PRESSURE) + { + // the pressure data value is the entire second word + uint8_t pressureData = (uint8_t)(M2Utils::scaleDown(originalWord1, 32, 7)); + + newWord0 = UMPMessage::mt2ChannelPressure(groupIndex, channelIndex, pressureData); + + newData = &newWord0; + newLength = sizeof(uint32_t); + } + else if (status == MIDI_UMP_MIDI2_CHANNEL_VOICE_STATUS_PITCH_BEND) + { + // the bend value is the entire second word + uint16_t bend = (uint16_t)M2Utils::scaleDown(originalWord1, 32, 14); + + newWord0 = UMPMessage::mt2PitchBend(groupIndex, channelIndex, bend); + + newData = &newWord0; + newLength = sizeof(uint32_t); + } + else + { + // just pass it through without any processing. We don't have a specific conversion for this CV message + newData = Data; + newLength = Length; + } + + + if (m_Callback != nullptr) + { + return m_Callback->Callback(newData, newLength, Position, m_Context); + } + + } + } + else + { + // pass through because it's not a message type we handle + + if (m_Callback != nullptr) + { + return m_Callback->Callback(Data, Length, Position, m_Context); + } + } + + + return S_OK; +} diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerMidiTransform.h b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerMidiTransform.h new file mode 100644 index 00000000..50415923 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerMidiTransform.h @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +#pragma once + +#include "bytestreamToUMP.h" + +class CMidi2UmpProtocolDownscalerMidiTransform : + public Microsoft::WRL::RuntimeClass< + Microsoft::WRL::RuntimeClassFlags, + IMidiDataTransform> +{ +public: + + STDMETHOD(Initialize(_In_ LPCWSTR, _In_ PTRANSFORMCREATIONPARAMS, _In_ DWORD *, _In_opt_ IMidiCallback *, _In_ LONGLONG, _In_ IUnknown*)); + STDMETHOD(SendMidiMessage(_In_ PVOID message, _In_ UINT size, _In_ LONGLONG)); + STDMETHOD(Cleanup)(); + +private: + + std::wstring m_Device; + wil::com_ptr_nothrow m_Callback; + LONGLONG m_Context; +}; + + diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.cpp b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.cpp new file mode 100644 index 00000000..688c7f67 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.cpp @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#include "pch.h" + +_Use_decl_annotations_ +HRESULT +CMidi2UmpProtocolDownscalerTransform::Activate( + REFIID Iid, + void **Interface +) +{ + RETURN_HR_IF(E_INVALIDARG, nullptr == Interface); + + if (__uuidof(IMidiDataTransform) == Iid) + { + TraceLoggingWrite( + MidiUmpProtocolDownscalerTransformTelemetryProvider::Provider(), + __FUNCTION__ "- Midi Transform", + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingValue(__FUNCTION__), + TraceLoggingPointer(this, "this") + ); + + wil::com_ptr_nothrow midiTransform; + RETURN_IF_FAILED(Microsoft::WRL::MakeAndInitialize(&midiTransform)); + *Interface = midiTransform.detach(); + } + + else + { + return E_NOINTERFACE; + } + + return S_OK; +} + diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.def b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.def new file mode 100644 index 00000000..42a13bf2 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.def @@ -0,0 +1,10 @@ +; Copyright (c) Microsoft Corporation. All rights reserved. + +LIBRARY + +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + DllInstall PRIVATE diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.h b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.h new file mode 100644 index 00000000..9d59e1bc --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.h @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +#pragma once + +class MidiUmpProtocolDownscalerTransformTelemetryProvider : public wil::TraceLoggingProvider +{ + IMPLEMENT_TRACELOGGING_CLASS_WITH_MICROSOFT_TELEMETRY( + MidiUmpProtocolDownscalerTransformTelemetryProvider, + "Microsoft.Windows.Midi2.UmpProtocolDownscalerTransform", + // {b4d50468-e9d8-5080-5b75-e49eb39fee9c} + (0xb4d50468,0xe9d8,0x5080,0x5b,0x75,0xe4,0x9e,0xb3,0x9f,0xee,0x9c)) +}; + +using namespace ATL; + +class ATL_NO_VTABLE CMidi2UmpProtocolDownscalerTransform : + public CComObjectRootEx, + public CComCoClass, + public IMidiTransform +{ +public: + CMidi2UmpProtocolDownscalerTransform() + { + } + + DECLARE_REGISTRY_RESOURCEID(IDR_MIDI2UMPPROTOCOLDOWNSCALERTRANSFORM) + + BEGIN_COM_MAP(CMidi2UmpProtocolDownscalerTransform) + COM_INTERFACE_ENTRY(IMidiTransform) + END_COM_MAP() + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + STDMETHOD(Activate)(_In_ REFIID, _Out_ void**); +private: +}; + +OBJECT_ENTRY_AUTO(__uuidof(Midi2UmpProtocolDownscalerTransform), CMidi2UmpProtocolDownscalerTransform) + diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.rc b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.rc new file mode 100644 index 00000000..1321d6b9 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.rc @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#ifndef APSTUDIO_INVOKED +#include +#endif +#include "winres.h" +#include "verrsrc.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#ifndef APSTUDIO_INVOKED\r\n" + "#include ""targetver.h""\r\n" + "#endif\r\n" + "#include ""winres.h""\r\n" + "#include ""verrsrc.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "1 TYPELIB ""Midi2UmpProtocolDownscalerTransform.tlb""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "TODO: " + VALUE "FileDescription", "TODO: " + VALUE "FileVersion", "1.0.0.1" + VALUE "LegalCopyright", "TODO: (c) . All rights reserved." + VALUE "InternalName", "Midi2.UmpProtocolDownscalerTransform.dll" + VALUE "OriginalFilename", "Midi2.UmpProtocolDownscalerTransform.dll" + VALUE "ProductName", "TODO: " + VALUE "ProductVersion", "1.0.0.1" + VALUE "OLESelfRegister", "" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END +END + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_PROJNAME "Midi2.UmpProtocolDownscalerTransform" +END + +IDR_MIDI2UMPPROTOCOLDOWNSCALERTRANSFORM REGISTRY "Midi2.UmpProtocolDownscalerTransform.rgs" +//////////////////////////////////////////////////////////////////////////// + + +#endif + +#if 0 +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +1 TYPELIB "Midi2UmpProtocolDownscalerTransform.tlb" +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED +#endif + diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.rgs b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.rgs new file mode 100644 index 00000000..19ebd43f --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.rgs @@ -0,0 +1,14 @@ +HKCR +{ + NoRemove CLSID + { + ForceRemove {dc638b31-cf31-48ed-9e79-02740bf5d013} = s 'Midi2BS2UMPTransform Class' + { + InprocServer32 = s '%MODULE%' + { + val ThreadingModel = s 'Both' + } + 'Version' = s '1.0' + } + } +} diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.vcxproj b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.vcxproj new file mode 100644 index 00000000..e7e66f54 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.vcxproj @@ -0,0 +1,310 @@ + + + + + Debug + ARM64 + + + Debug + ARM64EC + + + Release + ARM64 + + + Debug + x64 + + + Release + ARM64EC + + + Release + x64 + + + + 17.0 + {C31CEE7B-8BEC-46A6-A8FD-74BABB01A4FB} + Win32Proj + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + Unicode + + + DynamicLibrary + false + v143 + Unicode + + + DynamicLibrary + false + v143 + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(VC_LibraryPath_ARM64);$(WindowsSDK_LibraryPath_ARM64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midiksenum\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midiks\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\AM_MIDI2 \$(Platform)\$(Configuration)\ + + + $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(VC_LibraryPath_ARM64);$(WindowsSDK_LibraryPath_ARM64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midiksenum\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midiks\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\AM_MIDI2 \$(Platform)\$(Configuration)\ + + + $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(VC_LibraryPath_ARM64);$(WindowsSDK_LibraryPath_ARM64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)\VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midiksenum\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midiks\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\AM_MIDI2\$(Platform)\$(Configuration)\ + + + $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(VC_LibraryPath_ARM64);$(WindowsSDK_LibraryPath_ARM64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)\VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midiksenum\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midiks\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\AM_MIDI2 \$(Platform)\$(Configuration)\ + + + $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midiksenum\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\midiks\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\AM_MIDI2 \$(Platform)\$(Configuration)\ + + + $(SolutionDir)VSFiles\$(Platform)\$(Configuration)\ + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(WindowsSdkDir)\Testing\Development\lib\$(Platform);$(SolutionDir)\VSFiles\intermediate\midikscommon\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midiksenum\$(Platform)\$(Configuration);$(SolutionDir)\VSFiles\intermediate\midiks\$(Platform)\$(Configuration);$(SolutionDir)VSFiles\intermediate\AM_MIDI2\$(Platform)\$(Configuration)\ + + + + Level4 + + + + + Level4 + + + + + Level4 + + + + + true + %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(IntDir);$(SolutionDir)Libs\AM_MIDI2\Include + Create + pch.h + stdcpp20 + + + %(AdditionalDependencies);onecoreuap.lib;avrt.lib;am_midi2.lib;$(CoreLibraryDependencies) + Midi2.UmpProtocolDownscalerTransform.def + + + $(SolutionDir)\idl;%(AdditionalIncludeDirectories) + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + %(Filename).h + + + + + true + %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(IntDir);$(SolutionDir)Libs\AM_MIDI2\Include + Create + pch.h + stdcpp20 + + + %(AdditionalDependencies);onecoreuap.lib;avrt.lib;am_midi2.lib;$(CoreLibraryDependencies) + Midi2.UmpProtocolDownscalerTransform.def + + + $(SolutionDir)\idl;%(AdditionalIncludeDirectories) + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + %(Filename).h + + + + + true + %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(IntDir);$(SolutionDir)Libs\AM_MIDI2\Include + Create + pch.h + stdcpp20 + + + %(AdditionalDependencies);onecoreuap.lib;avrt.lib;am_midi2.lib;$(CoreLibraryDependencies) + Midi2.UmpProtocolDownscalerTransform.def + + + $(SolutionDir)\idl;%(AdditionalIncludeDirectories) + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + %(Filename).h + + + + + %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(IntDir);$(SolutionDir)Libs\AM_MIDI2\Include + + + + + %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(IntDir);$(SolutionDir)Libs\AM_MIDI2\Include + + + + + Level4 + + + + + Level4 + + + + + true + Create + pch.h + stdcpp20 + + + %(AdditionalDependencies);onecoreuap.lib;avrt.lib;am_midi2.lib;$(CoreLibraryDependencies) + Midi2.UmpProtocolDownscalerTransform.def + + + $(SolutionDir)\idl;%(AdditionalIncludeDirectories) + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + %(Filename).h + + + + + true + Create + pch.h + stdcpp20 + + + %(AdditionalDependencies);onecoreuap.lib;avrt.lib;am_midi2.lib;$(CoreLibraryDependencies) + Midi2.UmpProtocolDownscalerTransform.def + + + $(SolutionDir)\idl;%(AdditionalIncludeDirectories) + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + %(Filename).h + + + + + %(AdditionalIncludeDirectories);$(SolutionDir)inc;$(SolutionDir)VSFiles\intermediate\idl\$(Platform)\$(Configuration);$(IntDir);$(SolutionDir)Libs\AM_MIDI2\Include + + + + + Level4 + + + + + true + Create + pch.h + stdcpp20 + + + %(AdditionalDependencies);onecoreuap.lib;avrt.lib;am_midi2.lib;$(CoreLibraryDependencies) + Midi2.UmpProtocolDownscalerTransform.def + + + $(SolutionDir)\idl;%(AdditionalIncludeDirectories) + $(SolutionDir)VSFiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + %(Filename).h + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.vcxproj.filters b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.vcxproj.filters new file mode 100644 index 00000000..9f76c4c2 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2.UmpProtocolDownscalerTransform.vcxproj.filters @@ -0,0 +1,67 @@ + + + + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + + Resource Files + + + Source Files + + + + + {e577d6ce-71a4-4a68-bebf-c03b6c8349c9} + + + {f9db7a7c-1f53-4633-9405-eb5c06082f5d} + + + {dd93dd2d-55b3-4c59-b8b2-d7ef64c9915c} + + + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/api/Transform/UmpProtocolDownscaler/Midi2UmpProtocolDownscalerTransform.idl b/src/api/Transform/UmpProtocolDownscaler/Midi2UmpProtocolDownscalerTransform.idl new file mode 100644 index 00000000..98f59e47 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Midi2UmpProtocolDownscalerTransform.idl @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +import "oaidl.idl"; +import "ocidl.idl"; + +import "MidiAbstraction.idl"; + +[ + uuid(1b2e6e7f-e780-4406-92e0-14f4b8b04552), + version(1.0), +] +library Midi2UmpProtocolDownscalerTransformLib +{ + importlib("stdole2.tlb"); + + [uuid(dc638b31-cf31-48ed-9e79-02740bf5d013), version(1.0)] + coclass Midi2UmpProtocolDownscalerTransform + { + [default] interface IMidiDataTransform; + } +}; + diff --git a/src/api/Transform/UmpProtocolDownscaler/Resource.h b/src/api/Transform/UmpProtocolDownscaler/Resource.h new file mode 100644 index 00000000..3b5e3495 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/Resource.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#define IDS_PROJNAME 100 +#define IDR_MIDI2UMPPROTOCOLDOWNSCALERTRANSFORM 101 + +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 201 +#define _APS_NEXT_COMMAND_VALUE 32768 +#define _APS_NEXT_CONTROL_VALUE 201 +#define _APS_NEXT_SYMED_VALUE 106 +#endif +#endif diff --git a/src/api/Transform/UmpProtocolDownscaler/dllmain.cpp b/src/api/Transform/UmpProtocolDownscaler/dllmain.cpp new file mode 100644 index 00000000..bbb9ff71 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/dllmain.cpp @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#include "pch.h" + +CMidi2UmpProtocolDownscalerTransformModule _AtlModule; + +extern "C" BOOL WINAPI +DllMain( + HINSTANCE /* hInstance */, + DWORD Reason, + LPVOID Reserved +) +{ + if (Reason == DLL_PROCESS_ATTACH) + { + wil::SetResultTelemetryFallback(MidiUmpProtocolDownscalerTransformTelemetryProvider::FallbackTelemetryCallback); + } + + return _AtlModule.DllMain(Reason, Reserved); +} + +_Use_decl_annotations_ +STDAPI +DllCanUnloadNow(void) +{ + return _AtlModule.DllCanUnloadNow(); +} + +_Use_decl_annotations_ +STDAPI +DllGetClassObject( + REFCLSID Clsid, + REFIID Riid, + LPVOID* Object +) +{ + return _AtlModule.DllGetClassObject(Clsid, Riid, Object); +} + +STDAPI +DllRegisterServer(void) +{ + // registers object, typelib and all interfaces in typelib + HRESULT hr = _AtlModule.DllRegisterServer(FALSE); + return hr; +} + +STDAPI +DllUnregisterServer(void) +{ + HRESULT hr = _AtlModule.DllUnregisterServer(FALSE); + return hr; +} + +_Use_decl_annotations_ +STDAPI +DllInstall( + BOOL Install, + LPCWSTR ) +{ + HRESULT hr = E_FAIL; + + if (Install) + { + hr = DllRegisterServer(); + if (FAILED(hr)) + { + DllUnregisterServer(); + } + } + else + { + hr = DllUnregisterServer(); + } + + return hr; +} + diff --git a/src/api/Transform/UmpProtocolDownscaler/dllmain.h b/src/api/Transform/UmpProtocolDownscaler/dllmain.h new file mode 100644 index 00000000..f2bd7232 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/dllmain.h @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#pragma once + +class CMidi2UmpProtocolDownscalerTransformModule : public ATL::CAtlDllModuleT< CMidi2UmpProtocolDownscalerTransformModule > +{ +public : + DECLARE_LIBID(LIBID_Midi2UmpProtocolDownscalerTransformLib) + DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MIDI2UMPPROTOCOLDOWNSCALERTRANSFORM, "{1b2e6e7f-e780-4406-92e0-14f4b8b04552}") +}; + +extern class CMidi2UmpProtocolDownscalerTransformModule _AtlModule; diff --git a/src/api/Transform/UmpProtocolDownscaler/packages.config b/src/api/Transform/UmpProtocolDownscaler/packages.config new file mode 100644 index 00000000..09be25d9 --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/api/Transform/UmpProtocolDownscaler/pch.cpp b/src/api/Transform/UmpProtocolDownscaler/pch.cpp new file mode 100644 index 00000000..f197443b --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/pch.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#include "pch.h" + diff --git a/src/api/Transform/UmpProtocolDownscaler/pch.h b/src/api/Transform/UmpProtocolDownscaler/pch.h new file mode 100644 index 00000000..2b1304fa --- /dev/null +++ b/src/api/Transform/UmpProtocolDownscaler/pch.h @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#pragma once + +#ifndef STRICT +#define STRICT +#endif + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define _ATL_APARTMENT_THREADED +#define _ATL_NO_AUTOMATIC_NAMESPACE +#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit +#define ATL_NO_ASSERT_ON_DESTROY_NONEXISTENT_WINDOW + +#include "resource.h" +#include +#include +#include +#include +#include + +#include +#include + +#include "mididefs.h" + +#include "utils.h" // AM_MIDI2 +#include "umpMessageCreate.h" // AM_MIDI2 +#include "ump_helpers.h" // internal helpers + +namespace internal = ::Windows::Devices::Midi2::Internal; + + +#include "MidiServicePlugin.h" +#include "MidiServicePlugin_i.c" + +#include "Midi2UmpProtocolDownscalerTransform_i.c" +#include "Midi2UmpProtocolDownscalerTransform.h" + +#include "dllmain.h" + +#include "Midi2.UmpProtocolDownscalerTransform.h" +#include "Midi2.UmpProtocolDownscalerMidiTransform.h" + diff --git a/src/api/idl/MidiEndpointProtocolManagerInterface.idl b/src/api/idl/MidiEndpointProtocolManagerInterface.idl index 22a3cb0f..993fa107 100644 --- a/src/api/idl/MidiEndpointProtocolManagerInterface.idl +++ b/src/api/idl/MidiEndpointProtocolManagerInterface.idl @@ -6,9 +6,44 @@ // Further information: https://github.com/microsoft/MIDI/ // ============================================================================ - +import "oaidl.idl"; import "unknwn.idl"; + +typedef struct +{ + BYTE FirstGroup; + BYTE NumberOfGroupsSpanned; + BOOL IsActive; + BOOL IsMIDIMessageDestination; // if clients can send messages TO this block. We should create a MIDI 1.0 output port + BOOL IsMIDIMessageSource; // if clients can receive messages FROM this block. We should create a MIDI 1.0 input port + BSTR Name; + +} DISCOVEREDFUNCTIONBLOCK, *PDISCOVEREDFUNCTIONBLOCK; + + +// this only contains key info required by the endpoint managers +// so they can create compatible MIDI 1.0 ports from the results +// from MIDI 2.0 protocol negotiation +typedef struct +{ + BOOL EndpointInformationReceived; + + BSTR EndpointSuppliedName; + BSTR EndpointSuppliedProductInstanceId; + + BYTE NumberOfFunctionBlocksDeclared; + BYTE NumberOfFunctionBlocksReceived; + BOOL FunctionBlocksAreStatic; + + BOOL EndpointSupportsMIDI2Protocol; + BOOL EndpointSupportsMIDI1Protocol; + + SAFEARRAY(DISCOVEREDFUNCTIONBLOCK) DiscoveredFunctionBlocks; + +} ENDPOINTPROTOCOLNEGOTIATIONRESULTS, *PENDPOINTPROTOCOLNEGOTIATIONRESULTS; + + [ object, local, @@ -22,7 +57,11 @@ interface IMidiEndpointProtocolManagerInterface : IUnknown [in] BOOL PreferToSendJRTimestampsToEndpoint, [in] BOOL PreferToReceiveJRTimestampsFromEndpoint, [in] BYTE PreferredMidiProtocol, - [in] WORD TimeoutMS + [in] WORD TimeoutMS, + [out, retval] PENDPOINTPROTOCOLNEGOTIATIONRESULTS* NegotiationResults ); + // TODO: The above method should have out params for the discovered function blocks + // to make it easier for the endpoint to create initial MIDI 1.0 compat devices + }; diff --git a/src/oob-setup/Directory.Build.props b/src/oob-setup/Directory.Build.props.zzold similarity index 100% rename from src/oob-setup/Directory.Build.props rename to src/oob-setup/Directory.Build.props.zzold diff --git a/src/oob-setup/RegistryCustomActions/RegistryCustomActions.csproj b/src/oob-setup/RegistryCustomActions/RegistryCustomActions.csproj index b8d3833f..0062dd94 100644 --- a/src/oob-setup/RegistryCustomActions/RegistryCustomActions.csproj +++ b/src/oob-setup/RegistryCustomActions/RegistryCustomActions.csproj @@ -1,7 +1,7 @@ net48 - AnyCPU;x64 + AnyCPU;x64;Arm64 diff --git a/src/oob-setup/api-package/WindowsMidiServices.wxs b/src/oob-setup/api-package/WindowsMidiServices.wxs index 106b7382..fc1b3079 100644 --- a/src/oob-setup/api-package/WindowsMidiServices.wxs +++ b/src/oob-setup/api-package/WindowsMidiServices.wxs @@ -3,7 +3,7 @@ + SourceFile="$(env.MIDI_REPO_ROOT)src\oob-setup\RegistryCustomActions\bin\$(var.Platform)\Release\net48\RegistryCustomActions.CA.dll" /> @@ -47,21 +47,23 @@ Bitness="always64" Directory="SERVICE_INSTALLFOLDER" Guid="{6a2300f5-ccae-4f61-b849-d41aefb8a999}" > - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + @@ -69,14 +71,14 @@ Bitness="always64" Directory="SERVICE_INSTALLFOLDER" Guid="6a2300f5-ccae-4f61-b849-d41aefb8a2f7" > - + - - - - - - + @@ -129,14 +126,21 @@ + + + + + + + - + - + @@ -157,10 +161,10 @@ Bitness="always64" Directory="API_INSTALLFOLDER" Guid="dc48ab14-dd46-4ff2-8d59-10b9a8215c7e"> - + - + - + - - - - + + + + - + - - + + - - + + - - - - - + + + + + diff --git a/src/oob-setup/main-bundle/Bundle.wxs b/src/oob-setup/main-bundle/Bundle.wxs index a346506c..dc9b5b81 100644 --- a/src/oob-setup/main-bundle/Bundle.wxs +++ b/src/oob-setup/main-bundle/Bundle.wxs @@ -49,7 +49,7 @@ Id="SearchForVCRuntime" Variable="VCRuntimeInstalled" Root="HKLM" - Key="SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64" + Key="SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\$(var.Platform)" Value="Installed" Result="exists"/> @@ -75,7 +75,7 @@ - + @@ -99,7 +99,7 @@ Bundle WindowsMidiServicesCompleteSetup - - $([System.DateTime]::Now.ToString("yyyyMMddhhmm")) - - BuildDate=$(BuildDate) + $([System.DateTime]::Now.ToString("yyyyMMddhhmm")) + BuildDate=$(BuildDate) true @@ -13,15 +11,13 @@ true - - - - - + + + diff --git a/src/oob-setup/midi-services-setup.sln b/src/oob-setup/midi-services-setup.sln index e38f61eb..f11c0bfe 100644 --- a/src/oob-setup/midi-services-setup.sln +++ b/src/oob-setup/midi-services-setup.sln @@ -10,33 +10,61 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegistryCustomActions", "RegistryCustomActions\RegistryCustomActions.csproj", "{8049E08D-92F2-4BDE-B07F-BCF7DAD5F134}" EndProject Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "console-package", "console-package\console-package.wixproj", "{6894F024-2ADF-4A9B-87FC-70C8209C6CDE}" + ProjectSection(ProjectDependencies) = postProject + {E18CC5FC-F028-48A7-9343-E32A85CDF7B3} = {E18CC5FC-F028-48A7-9343-E32A85CDF7B3} + EndProjectSection EndProject Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "settings-package", "settings-package\settings-package.wixproj", "{0C3D23A8-A238-4551-BEB4-2912EFA3245C}" + ProjectSection(ProjectDependencies) = postProject + {E18CC5FC-F028-48A7-9343-E32A85CDF7B3} = {E18CC5FC-F028-48A7-9343-E32A85CDF7B3} + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 + Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E18CC5FC-F028-48A7-9343-E32A85CDF7B3}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E18CC5FC-F028-48A7-9343-E32A85CDF7B3}.Debug|ARM64.Build.0 = Debug|ARM64 {E18CC5FC-F028-48A7-9343-E32A85CDF7B3}.Debug|x64.ActiveCfg = Debug|x64 {E18CC5FC-F028-48A7-9343-E32A85CDF7B3}.Debug|x64.Build.0 = Debug|x64 + {E18CC5FC-F028-48A7-9343-E32A85CDF7B3}.Release|ARM64.ActiveCfg = Release|ARM64 + {E18CC5FC-F028-48A7-9343-E32A85CDF7B3}.Release|ARM64.Build.0 = Release|ARM64 {E18CC5FC-F028-48A7-9343-E32A85CDF7B3}.Release|x64.ActiveCfg = Release|x64 {E18CC5FC-F028-48A7-9343-E32A85CDF7B3}.Release|x64.Build.0 = Release|x64 + {182F17E6-CF91-4D65-B767-6469D523F5AC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {182F17E6-CF91-4D65-B767-6469D523F5AC}.Debug|ARM64.Build.0 = Debug|ARM64 {182F17E6-CF91-4D65-B767-6469D523F5AC}.Debug|x64.ActiveCfg = Debug|x64 {182F17E6-CF91-4D65-B767-6469D523F5AC}.Debug|x64.Build.0 = Debug|x64 + {182F17E6-CF91-4D65-B767-6469D523F5AC}.Release|ARM64.ActiveCfg = Release|ARM64 + {182F17E6-CF91-4D65-B767-6469D523F5AC}.Release|ARM64.Build.0 = Release|ARM64 {182F17E6-CF91-4D65-B767-6469D523F5AC}.Release|x64.ActiveCfg = Release|x64 {182F17E6-CF91-4D65-B767-6469D523F5AC}.Release|x64.Build.0 = Release|x64 + {8049E08D-92F2-4BDE-B07F-BCF7DAD5F134}.Debug|ARM64.ActiveCfg = Debug|Arm64 + {8049E08D-92F2-4BDE-B07F-BCF7DAD5F134}.Debug|ARM64.Build.0 = Debug|Arm64 {8049E08D-92F2-4BDE-B07F-BCF7DAD5F134}.Debug|x64.ActiveCfg = Debug|x64 {8049E08D-92F2-4BDE-B07F-BCF7DAD5F134}.Debug|x64.Build.0 = Debug|x64 + {8049E08D-92F2-4BDE-B07F-BCF7DAD5F134}.Release|ARM64.ActiveCfg = Release|Arm64 + {8049E08D-92F2-4BDE-B07F-BCF7DAD5F134}.Release|ARM64.Build.0 = Release|Arm64 {8049E08D-92F2-4BDE-B07F-BCF7DAD5F134}.Release|x64.ActiveCfg = Release|x64 {8049E08D-92F2-4BDE-B07F-BCF7DAD5F134}.Release|x64.Build.0 = Release|x64 + {6894F024-2ADF-4A9B-87FC-70C8209C6CDE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6894F024-2ADF-4A9B-87FC-70C8209C6CDE}.Debug|ARM64.Build.0 = Debug|ARM64 {6894F024-2ADF-4A9B-87FC-70C8209C6CDE}.Debug|x64.ActiveCfg = Debug|x64 {6894F024-2ADF-4A9B-87FC-70C8209C6CDE}.Debug|x64.Build.0 = Debug|x64 + {6894F024-2ADF-4A9B-87FC-70C8209C6CDE}.Release|ARM64.ActiveCfg = Release|ARM64 + {6894F024-2ADF-4A9B-87FC-70C8209C6CDE}.Release|ARM64.Build.0 = Release|ARM64 {6894F024-2ADF-4A9B-87FC-70C8209C6CDE}.Release|x64.ActiveCfg = Release|x64 {6894F024-2ADF-4A9B-87FC-70C8209C6CDE}.Release|x64.Build.0 = Release|x64 + {0C3D23A8-A238-4551-BEB4-2912EFA3245C}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0C3D23A8-A238-4551-BEB4-2912EFA3245C}.Debug|ARM64.Build.0 = Debug|ARM64 {0C3D23A8-A238-4551-BEB4-2912EFA3245C}.Debug|x64.ActiveCfg = Debug|x64 {0C3D23A8-A238-4551-BEB4-2912EFA3245C}.Debug|x64.Build.0 = Debug|x64 + {0C3D23A8-A238-4551-BEB4-2912EFA3245C}.Release|ARM64.ActiveCfg = Release|ARM64 + {0C3D23A8-A238-4551-BEB4-2912EFA3245C}.Release|ARM64.Build.0 = Release|ARM64 {0C3D23A8-A238-4551-BEB4-2912EFA3245C}.Release|x64.ActiveCfg = Release|x64 {0C3D23A8-A238-4551-BEB4-2912EFA3245C}.Release|x64.Build.0 = Release|x64 EndGlobalSection diff --git a/src/oob-setup/settings-package/MidiSettings.wxs b/src/oob-setup/settings-package/MidiSettings.wxs index 375d0c4f..0b147ce2 100644 --- a/src/oob-setup/settings-package/MidiSettings.wxs +++ b/src/oob-setup/settings-package/MidiSettings.wxs @@ -3,7 +3,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Source="$(StagingSourceRootFolder)\midi-settings\$(var.Platform)\MidiSettings.exe" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - + + diff --git a/src/prototypes/ble-midi1-proto/Additional Properties.txt b/src/prototypes/ble-midi1-proto/Additional Properties.txt new file mode 100644 index 00000000..c65dc2cf --- /dev/null +++ b/src/prototypes/ble-midi1-proto/Additional Properties.txt @@ -0,0 +1,8 @@ + + +We're going to need additional properties for BLE specifically + +DEVPKEY_AssociatedBluetoothDeviceId + +Probably something for the current connection status because we need to manage that better + diff --git a/src/prototypes/ble-midi1-proto/MidiBleAdvertisedEndpointManager.cpp b/src/prototypes/ble-midi1-proto/MidiBleAdvertisedEndpointManager.cpp new file mode 100644 index 00000000..b9f631a9 --- /dev/null +++ b/src/prototypes/ble-midi1-proto/MidiBleAdvertisedEndpointManager.cpp @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#include "pch.h" + +HRESULT MidiBleAdvertisedEndpointManager::Initialize() +{ + winrt::guid MIDI_BLE_SERVICE_UUID{ MIDI_BLE_SERVICE }; + + BluetoothLEAdvertisementWatcher watcher; + watcher.AdvertisementFilter().Advertisement().ServiceUuids().Append(MIDI_BLE_SERVICE_UUID); + + + BluetoothLEAdvertisementFilter ad; + + BluetoothLEAdvertisementBytePattern pattern; + + // filter by our service UUID + pattern.DataType(BluetoothLEAdvertisementDataTypes::CompleteService128BitUuids()); + + + +// ad.BytePatterns().Append(); + + + return S_OK; +} + +MidiBleDevice* MidiBleAdvertisedEndpointManager::GetDevice(_In_ std::wstring endpointDeviceInterfaceId) +{ + + return nullptr; +} + +HRESULT MidiBleAdvertisedEndpointManager::Cleanup() +{ + + return S_OK; +} + diff --git a/src/prototypes/ble-midi1-proto/MidiBleAdvertisedEndpointManager.h b/src/prototypes/ble-midi1-proto/MidiBleAdvertisedEndpointManager.h new file mode 100644 index 00000000..e4f8e513 --- /dev/null +++ b/src/prototypes/ble-midi1-proto/MidiBleAdvertisedEndpointManager.h @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + + +#pragma once + +class MidiBleAdvertisedEndpointManager +{ +public: + HRESULT Initialize(); + + MidiBleDevice* GetDevice(_In_ std::wstring endpointDeviceInterfaceId); + + HRESULT Cleanup(); + +private: + + + void WatcherThreadWorker(); + + //enumeration::DeviceWatcher m_Watcher{ nullptr }; + //bool m_enumerationCompleted{ false }; + + BluetoothLEAdvertisementWatcher m_adWatcher{ nullptr }; + + std::thread m_watcherThread{}; + + // map of SWD ids to BLE endpoint devices + std::map m_devices{}; + + + //winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Added_revoker m_DeviceAdded; + //winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Removed_revoker m_DeviceRemoved; + //winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Updated_revoker m_DeviceUpdated; + //winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Stopped_revoker m_DeviceStopped; + //winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::EnumerationCompleted_revoker m_DeviceEnumerationCompleted; + + + //void OnDeviceAdded(_In_ enumeration::DeviceWatcher, _In_ enumeration::DeviceInformation const& info); + //void OnDeviceRemoved(_In_ enumeration::DeviceWatcher, _In_ enumeration::DeviceInformationUpdate const& upd); + //void OnDeviceUpdated(_In_ enumeration::DeviceWatcher, _In_ enumeration::DeviceInformationUpdate const& upd); + + //void OnDeviceWatcherStopped(_In_ enumeration::DeviceWatcher, _In_ foundation::IInspectable); + //void OnEnumerationCompleted(_In_ enumeration::DeviceWatcher, _In_ foundation::IInspectable); + +}; diff --git a/src/api/Abstraction/BleMidiAbstraction/MidiBluetoothDevice.h b/src/prototypes/ble-midi1-proto/MidiBleBiDi.cpp similarity index 68% rename from src/api/Abstraction/BleMidiAbstraction/MidiBluetoothDevice.h rename to src/prototypes/ble-midi1-proto/MidiBleBiDi.cpp index 0212d595..12291b44 100644 --- a/src/api/Abstraction/BleMidiAbstraction/MidiBluetoothDevice.h +++ b/src/prototypes/ble-midi1-proto/MidiBleBiDi.cpp @@ -7,9 +7,25 @@ // ============================================================================ -#pragma once -struct MidiBluetoothDevice +#include "pch.h" + +_Use_decl_annotations_ +HRESULT MidiBleBiDi::Initialize(LPCWSTR /*endpointDeviceInterfaceId*/) { + return S_OK; +} + + + + + + +HRESULT MidiBleBiDi::SendMidiMessage() +{ + + + return S_OK; +} + -}; \ No newline at end of file diff --git a/src/prototypes/ble-midi1-proto/MidiBleBiDi.h b/src/prototypes/ble-midi1-proto/MidiBleBiDi.h new file mode 100644 index 00000000..47f63634 --- /dev/null +++ b/src/prototypes/ble-midi1-proto/MidiBleBiDi.h @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#pragma once + + +// class for communicating with the endpoint. Not for enumeration + +class MidiBleBiDi +{ +public: + HRESULT Initialize(_In_ LPCWSTR endpointDeviceInterfaceId); + + HRESULT SendMidiMessage(); + + // todo: Callback for receiving messages. We'll just display them for now. + +private: + std::unique_ptr m_device{ nullptr }; + + +}; \ No newline at end of file diff --git a/src/prototypes/ble-midi1-proto/MidiBleDevice.cpp b/src/prototypes/ble-midi1-proto/MidiBleDevice.cpp new file mode 100644 index 00000000..8c0af4e4 --- /dev/null +++ b/src/prototypes/ble-midi1-proto/MidiBleDevice.cpp @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +// NOTE: This is prototype code for working through some BLE approaches before +// implementing it in the Windows service. Eventually, this code will go stale, +// but we'll keep it around. This is not production code. + +#include "pch.h" + + +_Use_decl_annotations_ +HRESULT MidiBleDevice::Initialize( + std::wstring const endpointDeviceInterfaceId, + std::wstring const bleDeviceId, + bool const supportsMidiOut, + bool const supportsMidiIn, + bool const supportsNotify) +{ + std::cout << __FUNCTION__ << std::endl; + + m_endpointDeviceInterfaceId = endpointDeviceInterfaceId; + m_bleDeviceId = bleDeviceId; + + m_supportsMidiOut = supportsMidiOut; + m_supportsMidiIn = supportsMidiIn; + m_supportsNotify = supportsNotify; + + return S_OK; +} + + + + +HRESULT MidiBleDevice::Connect() +{ + std::cout << __FUNCTION__ << std::endl; + + m_bleDevice = GetBleDeviceFromEnumerationDeviceId(m_bleDeviceId); + + if (m_bleDevice != nullptr) + { + m_bleDevice.ConnectionStatusChanged({ this, &MidiBleDevice::OnConnectionStatusChanged }); + + //std::cout << "Bluetooth device connected: " << winrt::to_string(m_bleDevice.Name()) << std::endl; + + // force connection by, as our docs say, calling an uncached service discovery method + + auto service = GetBleMidiServiceFromDevice(m_bleDevice); + + OnConnected(); + + return S_OK; + } + + //std::cout << "Couldn't connect" << std::endl; + + return E_FAIL; +} + + +HRESULT MidiBleDevice::Disconnect() +{ + std::cout << __FUNCTION__ << std::endl; + + // terminate the session + if (m_bleDevice != nullptr && m_session != nullptr) + { + std::cout << "Disconnecting from " << winrt::to_string(m_bleDevice.Name()) << std::endl; + + m_session.Close(); + } + + return S_OK; +} + +void MidiBleDevice::OnDisconnected() +{ + std::cout << __FUNCTION__ << std::endl; + +} + +void MidiBleDevice::OnConnected() +{ + std::cout << __FUNCTION__ << std::endl; + + m_midiService = GetBleMidiServiceFromDevice(m_bleDevice); + + if (m_midiService != nullptr) + { + auto openStatus = m_midiService.OpenAsync(GattSharingMode::SharedReadAndWrite).get(); + + if (openStatus == GattOpenStatus::Success) + { + std::cout << "Service opened" << std::endl; + + auto session = GattSession::FromDeviceIdAsync(m_bleDevice.BluetoothDeviceId()).get(); + + session.MaintainConnection(true); + + m_session = std::move(session); + } + else if (openStatus == GattOpenStatus::AccessDenied) + { + std::cout << "Open returned AccessDenied" << std::endl; + } + else if (openStatus == GattOpenStatus::AlreadyOpened) + { + std::cout << "Open returned AlreadyOpened" << std::endl; + } + else if (openStatus == GattOpenStatus::NotFound) + { + std::cout << "Open returned NotFound" << std::endl; + } + else if (openStatus == GattOpenStatus::SharingViolation) + { + std::cout << "Open returned SharingViolation" << std::endl; + } + else if (openStatus == GattOpenStatus::Unspecified) + { + std::cout << "Open returned Unspecified" << std::endl; + } + + m_midiDataIOCharacteristic = GetBleMidiDataIOCharacteristicFromService(m_midiService); + + if (m_midiDataIOCharacteristic != nullptr) + { + m_midiDataIOCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync( + GattClientCharacteristicConfigurationDescriptorValue::Notify).get(); + + + // todo: keep token to unwire this on disconnect + m_midiDataIOCharacteristic.ValueChanged({ this, &MidiBleDevice::OnValueChanged }); + + std::cout << "ValueChanged wired up" << std::endl; + + return; + } + } + else + { + std::cout << "Service is nullptr" << std::endl; + } + + + +} + + +_Use_decl_annotations_ +void MidiBleDevice::OnConnectionStatusChanged(BluetoothLEDevice const& sender, foundation::IInspectable const& /*args*/) +{ + std::cout << __FUNCTION__ << std::endl; + + if (sender.ConnectionStatus() == BluetoothConnectionStatus::Connected) + { + OnConnected(); + } + else + { + OnDisconnected(); + } +} + + + + + +_Use_decl_annotations_ +void MidiBleDevice::OnValueChanged(GattCharacteristic const& /*sender*/, GattValueChangedEventArgs const& args) +{ + std::cout << __FUNCTION__ << std::endl; + + std::cout << "> "; + + for (unsigned i = 0; i < args.CharacteristicValue().Length(); i++) + { + // read next byte + auto b = *(args.CharacteristicValue().data() + i); + + std::cout << "0x" << std::hex << (unsigned)b << " "; + } + + std::cout << std::endl; +} \ No newline at end of file diff --git a/src/prototypes/ble-midi1-proto/MidiBleDevice.h b/src/prototypes/ble-midi1-proto/MidiBleDevice.h new file mode 100644 index 00000000..9f13f33c --- /dev/null +++ b/src/prototypes/ble-midi1-proto/MidiBleDevice.h @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#pragma once + + +// class for communicating with the endpoint. Not for enumeration + +class MidiBleDevice +{ +public: + HRESULT Initialize( + _In_ std::wstring const endpointDeviceInterfaceId, + _In_ std::wstring const bleDeviceId, + _In_ bool const supportsMidiOut, + _In_ bool const supportsMidiIn, + _In_ bool const supportsNotify); + + HRESULT Connect(); + + HRESULT Disconnect(); + +private: + + GattSession m_session{ nullptr }; + + std::wstring m_endpointDeviceInterfaceId{}; + std::wstring m_bleDeviceId{}; + + BluetoothLEDevice m_bleDevice{ nullptr }; + GattDeviceService m_midiService{ nullptr }; + GattCharacteristic m_midiDataIOCharacteristic{ nullptr }; + + bool m_supportsMidiOut{ false }; + bool m_supportsMidiIn{ false }; + bool m_supportsNotify{ false }; + + + void OnConnected(); + void OnDisconnected(); + + + void OnConnectionStatusChanged(_In_ BluetoothLEDevice const& sender, _In_ foundation::IInspectable const& args); + void OnValueChanged(_In_ GattCharacteristic const& sender, _In_ GattValueChangedEventArgs const& args); + +}; \ No newline at end of file diff --git a/src/prototypes/ble-midi1-proto/MidiBleEndpointManager.cpp b/src/prototypes/ble-midi1-proto/MidiBleEndpointManager.cpp new file mode 100644 index 00000000..5256747f --- /dev/null +++ b/src/prototypes/ble-midi1-proto/MidiBleEndpointManager.cpp @@ -0,0 +1,250 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + + + +#include "pch.h" + + +HRESULT MidiBleEndpointManager::Initialize() +{ + winrt::hstring deviceSelector = BluetoothLEDevice::GetDeviceSelector(); + + auto requestedProperties = winrt::single_threaded_vector( + { + L"System.DeviceInterface.Bluetooth.DeviceAddress", + L"System.DeviceInterface.Bluetooth.Flags", + L"System.DeviceInterface.Bluetooth.LastConnectedTime", + L"System.DeviceInterface.Bluetooth.Manufacturer", + L"System.DeviceInterface.Bluetooth.ModelNumber", + L"System.DeviceInterface.Bluetooth.ProductId", + L"System.DeviceInterface.Bluetooth.ProductVersion", + L"System.DeviceInterface.Bluetooth.ServiceGuid", + L"System.DeviceInterface.Bluetooth.VendorId", + L"System.DeviceInterface.Bluetooth.VendorIdSource", + L"System.Devices.Connected", + L"System.Devices.DeviceCapabilities", + L"System.Devices.DeviceCharacteristics" + } + ); + + m_Watcher = enumeration::DeviceInformation::CreateWatcher(deviceSelector, requestedProperties); + + auto deviceAddedHandler = foundation::TypedEventHandler(this, &MidiBleEndpointManager::OnDeviceAdded); + auto deviceRemovedHandler = foundation::TypedEventHandler(this, &MidiBleEndpointManager::OnDeviceRemoved); + auto deviceUpdatedHandler = foundation::TypedEventHandler(this, &MidiBleEndpointManager::OnDeviceUpdated); + auto deviceStoppedHandler = foundation::TypedEventHandler(this, &MidiBleEndpointManager::OnDeviceWatcherStopped); + auto deviceEnumerationCompletedHandler = foundation::TypedEventHandler(this, &MidiBleEndpointManager::OnEnumerationCompleted); + + m_DeviceAdded = m_Watcher.Added(winrt::auto_revoke, deviceAddedHandler); + m_DeviceRemoved = m_Watcher.Removed(winrt::auto_revoke, deviceRemovedHandler); + m_DeviceUpdated = m_Watcher.Updated(winrt::auto_revoke, deviceUpdatedHandler); + m_DeviceStopped = m_Watcher.Stopped(winrt::auto_revoke, deviceStoppedHandler); + m_DeviceEnumerationCompleted = m_Watcher.EnumerationCompleted(winrt::auto_revoke, deviceEnumerationCompletedHandler); + + + std::thread watcherThread(&MidiBleEndpointManager::WatcherThreadWorker, this); + + m_watcherThread = std::move(watcherThread); + + m_watcherThread.detach(); + + return S_OK; +} + +void MidiBleEndpointManager::WatcherThreadWorker() +{ + m_Watcher.Start(); +} + + +HRESULT MidiBleEndpointManager::Cleanup() +{ + std::cout << "MidiBleEndpointManager::Cleanup" << std::endl; + + // terminate background thread + m_Watcher.Stop(); + +// if (m_watcherThread.joinable()) m_watcherThread.join(); + + return S_OK; +} + +_Use_decl_annotations_ +MidiBleDevice* MidiBleEndpointManager::GetDevice(std::wstring endpointDeviceInterfaceId) +{ + // todo: clean the id + if (auto deviceIter = m_devices.find(endpointDeviceInterfaceId); deviceIter != m_devices.end()) + { + return &(deviceIter->second); + } + + return nullptr; +} + + + + + + + + +_Use_decl_annotations_ +void MidiBleEndpointManager::OnDeviceAdded(enumeration::DeviceWatcher, enumeration::DeviceInformation const& info) +{ + std::hash hasher; + std::wstring hash; + + std::cout << "Added: " << winrt::to_string(info.Id()) << std::endl; + std::cout << "Name: " << winrt::to_string(info.Name()) << std::endl; + + // get the BLE device from this + + auto bleDevice = BluetoothLEDevice::FromIdAsync(info.Id()).get(); + + if (bleDevice != nullptr) + { + std::cout << "BLE Name: " << winrt::to_string(bleDevice.Name()) << std::endl; + + if (bleDevice.ConnectionStatus() == BluetoothConnectionStatus::Connected) + { + std::cout << "Current status: Connected " << std::endl; + } + else + { + std::cout << "Current status: Disconnected " << std::endl; + } + + auto midiService = GetBleMidiServiceFromDevice(bleDevice); + + if (midiService != nullptr) + { + auto characteristic = GetBleMidiDataIOCharacteristicFromService(midiService); + + if (characteristic != nullptr) + { + auto properties = characteristic.CharacteristicProperties(); + + bool supportsMidiIn{ false }; + bool supportsMidiOut{ false }; + bool supportsNotify{ false }; + + if ((properties & GattCharacteristicProperties::Read) == GattCharacteristicProperties::Read) + { + // includes input port + std::cout << "- Supports READ (input port)" << std::endl; + + supportsMidiIn = true; + } + + if ((properties & GattCharacteristicProperties::Write) == GattCharacteristicProperties::Write) + { + // includes output port + std::cout << "- Supports WRITE (output port)" << std::endl; + + supportsMidiOut = true; + } + + if ((properties & GattCharacteristicProperties::Notify) == GattCharacteristicProperties::Notify) + { + std::cout << "- Supports NOTIFY" << std::endl; + + supportsNotify = true; + } + + // lock the map and create and store the device + + MidiBleDevice endpointDevice; + + // we'll create a real id in the actual implementation + // todo: clean the id + + + hash = std::to_wstring(hasher(std::wstring{ bleDevice.DeviceId() })); + + std::wstring endpointDeviceId = std::wstring{ L"\\\\?\\SWD&MIDIU_BLE_" } + hash; + + endpointDevice.Initialize( + endpointDeviceId, + bleDevice.DeviceId().c_str(), + supportsMidiOut, + supportsMidiIn, + supportsNotify); + + m_devices.emplace(endpointDeviceId, endpointDevice); + + std::wcout << L"Added device: " << endpointDeviceId << std::endl; + } + } + + } + + std::cout << "------------------------" << std::endl; +} + +_Use_decl_annotations_ +void MidiBleEndpointManager::OnDeviceRemoved(enumeration::DeviceWatcher, enumeration::DeviceInformationUpdate const& upd) +{ + std::cout << "Removed: " << winrt::to_string(upd.Id()) << std::endl; +} + +_Use_decl_annotations_ +void MidiBleEndpointManager::OnDeviceUpdated(enumeration::DeviceWatcher, enumeration::DeviceInformationUpdate const& upd) +{ + std::cout << "-----------" << std::endl; + + std::cout << "Updated: " << winrt::to_string(upd.Id()).c_str() << std::endl; + + auto bleDevice = BluetoothLEDevice::FromIdAsync(upd.Id()).get(); + + if (bleDevice != nullptr) + { + std::cout << "Name: " << winrt::to_string(bleDevice.Name()) << " : "; + + if (bleDevice.ConnectionStatus() == BluetoothConnectionStatus::Connected) + { + std::cout << "Connected " << std::endl; + } + else + { + std::cout << "Disconnected " << std::endl; + } + } + + // spit out the updated properties + for (auto prop : upd.Properties()) + { + std::cout << winrt::to_string(prop.Key()) << std::endl; + + if (upd.Id() == bleDevice.DeviceId()) + { + if (prop.Key() == L"System.DeviceInterface.Bluetooth.Flags") + { + // TODO: get the property value and decipher + + + } + } + + } + + std::cout << "-----------" << std::endl; +} + +_Use_decl_annotations_ +void MidiBleEndpointManager::OnDeviceWatcherStopped(enumeration::DeviceWatcher, foundation::IInspectable) +{ + std::cout << "Device watcher stopped" << std::endl; +} + +_Use_decl_annotations_ +void MidiBleEndpointManager::OnEnumerationCompleted(enumeration::DeviceWatcher, foundation::IInspectable) +{ + std::cout << "Enumeration completed" << std::endl; + +} \ No newline at end of file diff --git a/src/prototypes/ble-midi1-proto/MidiBleEndpointManager.h b/src/prototypes/ble-midi1-proto/MidiBleEndpointManager.h new file mode 100644 index 00000000..71b491c0 --- /dev/null +++ b/src/prototypes/ble-midi1-proto/MidiBleEndpointManager.h @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#pragma once + +class MidiBleEndpointManager +{ +public: + HRESULT Initialize(); + + MidiBleDevice* GetDevice(_In_ std::wstring endpointDeviceInterfaceId); + + HRESULT Cleanup(); + +private: + + + void WatcherThreadWorker(); + + enumeration::DeviceWatcher m_Watcher{ nullptr }; + bool m_enumerationCompleted{ false }; + + + std::thread m_watcherThread{}; + + // map of SWD ids to BLE endpoint devices + std::map m_devices{}; + + + winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Added_revoker m_DeviceAdded; + winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Removed_revoker m_DeviceRemoved; + winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Updated_revoker m_DeviceUpdated; + winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Stopped_revoker m_DeviceStopped; + winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::EnumerationCompleted_revoker m_DeviceEnumerationCompleted; + + + void OnDeviceAdded(_In_ enumeration::DeviceWatcher, _In_ enumeration::DeviceInformation const& info); + void OnDeviceRemoved(_In_ enumeration::DeviceWatcher, _In_ enumeration::DeviceInformationUpdate const& upd); + void OnDeviceUpdated(_In_ enumeration::DeviceWatcher, _In_ enumeration::DeviceInformationUpdate const& upd); + + void OnDeviceWatcherStopped(_In_ enumeration::DeviceWatcher, _In_ foundation::IInspectable); + void OnEnumerationCompleted(_In_ enumeration::DeviceWatcher, _In_ foundation::IInspectable); + +}; diff --git a/src/prototypes/ble-midi1-proto/abstraction_defs.h b/src/prototypes/ble-midi1-proto/abstraction_defs.h new file mode 100644 index 00000000..db2ebadb --- /dev/null +++ b/src/prototypes/ble-midi1-proto/abstraction_defs.h @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#pragma once + + +#ifndef ABSTRACTION_DEFS_H +#define ABSTRACTION_DEFS_H + +#define MIDI_BLE_SERVICE L"{03B80E5A-EDE8-4B33-A751-6CE34EC4C700}" +#define MIDI_BLE_DATA_IO_CHARACTERISTIC L"{7772E5DB-3868-4112-A1A9-F2669D106BF3}" +#define MIDI_BLE_COMPANY_CODE_MICROSOFT 0xFE08 + +// Notes: +// Write (encryption recommended, write without response is required) +// Read (encryption recommended, respond with no payload) +// Notify (encryption recommended) +// Max connection interval is 15ms. Lower is better. + +#endif \ No newline at end of file diff --git a/src/prototypes/ble-midi1-proto/ble-midi1-proto.vcxproj b/src/prototypes/ble-midi1-proto/ble-midi1-proto.vcxproj index d0c8e495..c659ec6c 100644 --- a/src/prototypes/ble-midi1-proto/ble-midi1-proto.vcxproj +++ b/src/prototypes/ble-midi1-proto/ble-midi1-proto.vcxproj @@ -103,10 +103,20 @@ + + + + + + + + + + Create @@ -115,6 +125,9 @@ + + + diff --git a/src/prototypes/ble-midi1-proto/ble_utilities.h b/src/prototypes/ble-midi1-proto/ble_utilities.h new file mode 100644 index 00000000..a80dda70 --- /dev/null +++ b/src/prototypes/ble-midi1-proto/ble_utilities.h @@ -0,0 +1,59 @@ +#pragma once + +inline BluetoothLEDevice GetBleDeviceFromEnumerationDeviceId(_In_ std::wstring deviceId) +{ +// std::cout << __FUNCTION__ << std::endl; + + auto device = BluetoothLEDevice::FromIdAsync(winrt::to_hstring(deviceId.c_str())).get(); + + return device; +} + + +inline GattDeviceService GetBleMidiServiceFromDevice(_In_ BluetoothLEDevice bleDevice) +{ +// std::cout << __FUNCTION__ << std::endl; + + winrt::guid MIDI_BLE_SERVICE_UUID{ MIDI_BLE_SERVICE }; + + auto gattServicesResult = bleDevice.GetGattServicesAsync(BluetoothCacheMode::Uncached).get(); + if (gattServicesResult.Status() == GattCommunicationStatus::Success) + { + auto services = gattServicesResult.Services(); + + // find the MIDI BLE service + if (auto it = std::find_if(services.begin(), services.end(), [&](GattDeviceService const& x) { return x.Uuid() == MIDI_BLE_SERVICE_UUID; }); it != services.end()) + { + // just grab the first one. Shouldn't be more than one match. + auto midiService = *it; + + return midiService; + } + } + + return nullptr; +} + + + +inline GattCharacteristic GetBleMidiDataIOCharacteristicFromService(_In_ GattDeviceService midiService) +{ +// std::cout << __FUNCTION__ << std::endl; + + winrt::guid MIDI_BLE_DATA_IO_CHARACTERISTIC_UUID{ MIDI_BLE_DATA_IO_CHARACTERISTIC }; + + if (midiService != nullptr) + { + auto characteristicsResult = midiService.GetCharacteristicsForUuidAsync(MIDI_BLE_DATA_IO_CHARACTERISTIC_UUID).get(); + + if (characteristicsResult.Status() == GattCommunicationStatus::Success && characteristicsResult.Characteristics().Size() > 0) + { + // we're just getting the first one + auto characteristic = characteristicsResult.Characteristics().GetAt(0); + + return characteristic; + } + } + + return nullptr; +} diff --git a/src/prototypes/ble-midi1-proto/main.cpp b/src/prototypes/ble-midi1-proto/main.cpp index 1636b377..fef3f34c 100644 --- a/src/prototypes/ble-midi1-proto/main.cpp +++ b/src/prototypes/ble-midi1-proto/main.cpp @@ -19,406 +19,342 @@ using namespace Windows::Foundation; // https://learn.microsoft.com/en-us/windows/uwp/devices-sensors/gatt-client +struct MidiBleDeviceEntry +{ + uint64_t BluetoothAddress{}; -#define MIDI_BLE_SERVICE L"{03B80E5A-EDE8-4B33-A751-6CE34EC4C700}" -#define MIDI_BLE_DATA_IO_CHARACTERISTIC L"{7772E5DB-3868-4112-A1A9-F2669D106BF3}" - -winrt::guid MIDI_BLE_SERVICE_UUID(MIDI_BLE_SERVICE); -winrt::guid MIDI_BLE_DATA_IO_CHARACTERISTIC_UUID(MIDI_BLE_DATA_IO_CHARACTERISTIC); - -// Notes: -// Write (encryption recommended, write without response is required) -// Read (encryption recommended, respond with no payload) -// Notify (encryption recommended) -// Max connection interval is 15ms. Lower is better. - - -enumeration::DeviceWatcher m_Watcher{ nullptr }; -bool m_enumerationCompleted{ false }; - -winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Added_revoker m_DeviceAdded; -winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Removed_revoker m_DeviceRemoved; -winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Updated_revoker m_DeviceUpdated; -winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::Stopped_revoker m_DeviceStopped; -winrt::impl::consume_Windows_Devices_Enumeration_IDeviceWatcher::EnumerationCompleted_revoker m_DeviceEnumerationCompleted; + BluetoothLEDevice Device{ nullptr }; + GattDeviceService Service{ nullptr }; +}; +std::vector m_foundMidiDevices; -void OnDeviceAdded(enumeration::DeviceWatcher, enumeration::DeviceInformation info) +void TestFindingDevices() { - std::cout << "Added: " << winrt::to_string(info.Id()) << std::endl; - std::cout << "Name: " << winrt::to_string(info.Name()) << std::endl; - +// GattCharacteristic characteristic{ nullptr }; - // get the BLE device from this + winrt::guid MIDI_BLE_SERVICE_UUID{ MIDI_BLE_SERVICE }; - auto bleDevice = bt::BluetoothLEDevice::FromIdAsync(info.Id()).get(); - - if (bleDevice != nullptr) - { - std::cout << "BLE Name: " << winrt::to_string(bleDevice.Name()) << std::endl; + BluetoothLEAdvertisementWatcher watcher; - if (bleDevice.ConnectionStatus() == bt::BluetoothConnectionStatus::Connected) + watcher.Received([&](BluetoothLEAdvertisementWatcher /*source*/, BluetoothLEAdvertisementReceivedEventArgs const& args) { - std::cout << "Current status: Connected " << std::endl; - } - else - { - std::cout << "Current status: Disconnected " << std::endl; - } - - + auto device = BluetoothLEDevice::FromBluetoothAddressAsync(args.BluetoothAddress()).get(); - auto gattServicesResult = bleDevice.GetGattServicesAsync().get(); - - if (gattServicesResult.Status() == gatt::GattCommunicationStatus::Success) - { - for (auto service : gattServicesResult.Services()) + if (device != nullptr) { - auto serviceUuid = service.Uuid(); + auto service = GetBleMidiServiceFromDevice(device); - if (serviceUuid == MIDI_BLE_SERVICE_UUID) + if (service != nullptr) { - std::cout << "MIDI Service Found: " << winrt::to_string(winrt::to_hstring(serviceUuid)) << std::endl; - - auto characteristics = service.GetAllCharacteristics(); - - for (auto characteristic : characteristics) + // if not found, add it + if (std::find_if(m_foundMidiDevices.begin(), m_foundMidiDevices.end(), [&](const MidiBleDeviceEntry& dev) { return device.BluetoothAddress() == dev.BluetoothAddress; }) == m_foundMidiDevices.end()) { - auto uuid = characteristic.Uuid(); - //auto descriptorResult = characteristic.GetDescriptorsForUuidAsync(uuid).get(); - - //for (auto desc : descriptorResult.Descriptors()) - //{ - // desc. - //} - - if (uuid == MIDI_BLE_DATA_IO_CHARACTERISTIC_UUID) - { - std::cout << "MIDI Data IO Characteristic Found: " << winrt::to_string(winrt::to_hstring(uuid)) << std::endl; - - auto properties = characteristic.CharacteristicProperties(); - - if ((properties & gatt::GattCharacteristicProperties::Read) == gatt::GattCharacteristicProperties::Read) + device.ConnectionStatusChanged([&](BluetoothLEDevice connectionSource, IInspectable /*args*/) { - // includes input port - std::cout << "- Supports READ (input port)" << std::endl; - } - - if ((properties & gatt::GattCharacteristicProperties::Write) == gatt::GattCharacteristicProperties::Write) - { - // includes output port - std::cout << "- Supports WRITE (output port)" << std::endl; - } - - if ((properties & gatt::GattCharacteristicProperties::Notify) == gatt::GattCharacteristicProperties::Notify) - { - std::cout << "- Supports NOTIFY" << std::endl; - } - - } - - + if (connectionSource.ConnectionStatus() == BluetoothConnectionStatus::Connected) + { + std::cout << "Connection status changed for " << winrt::to_string(connectionSource.Name()) << ", new status: Connected" << std::endl; + } + else + { + std::cout << "Connection status changed for " << winrt::to_string(connectionSource.Name()) << ", new status: Disconnected" << std::endl; + } + }); + + std::cout + << "MIDI Device Found" << std::endl + << " - Name: " << winrt::to_string(device.Name()) << std::endl + << " - Timestamp: " << args.Timestamp().time_since_epoch().count() << std::endl + << " - Address: " << args.BluetoothAddress() << std::endl + << " - IsConnectable: " << args.IsConnectable() << std::endl + << " - IsAnonymous: " << args.IsAnonymous() << std::endl + << " - IsDirected: " << args.IsDirected() << std::endl + << " - IsScannable: " << args.IsScannable() << std::endl + << " - RawSignalStrengthInDBm: " << args.RawSignalStrengthInDBm() << std::endl; + + + MidiBleDeviceEntry entry; + entry.BluetoothAddress = device.BluetoothAddress(); + entry.Device = std::move(device); + entry.Service = std::move(service); + + m_foundMidiDevices.push_back(std::move(entry)); } - - // no need to continue looking - break; } + //else + //{ + // std::cout << "Not a BLE MIDI Device" << std::endl; + //} } } - else - { - std::cout << "Unable to communicate with device to get gatt services" << std::endl; - } + ); - std::cout << "------------------------" << std::endl; - } + watcher.AdvertisementFilter().Advertisement().ServiceUuids().Append(MIDI_BLE_SERVICE_UUID); -} + std::cout << "Starting watcher..." << std::endl; -void OnDeviceRemoved(enumeration::DeviceWatcher, enumeration::DeviceInformationUpdate upd) -{ - std::cout << "Removed: " << winrt::to_string(upd.Id()) << std::endl; + watcher.Start(); + + system("pause > nul"); } -void OnDeviceUpdated(enumeration::DeviceWatcher, enumeration::DeviceInformationUpdate upd) +void ReportAdapterCapabilities() { - std::cout << "-----------" << std::endl; - - std::cout << "Updated: " << winrt::to_string(upd.Id()).c_str() << std::endl; + auto adapter = BluetoothAdapter::GetDefaultAsync().get(); - auto bleDevice = bt::BluetoothLEDevice::FromIdAsync(upd.Id()).get(); - - if (bleDevice != nullptr) + if (adapter != nullptr) { - std::cout << "Name: " << winrt::to_string(bleDevice.Name()) << " : "; - - if (bleDevice.ConnectionStatus() == bt::BluetoothConnectionStatus::Connected) - { - std::cout << "Connected " << std::endl; - } - else - { - std::cout << "Disconnected " << std::endl; - } + std::cout << "IsCentralRoleSupported : " << adapter.IsCentralRoleSupported() << std::endl; + std::cout << "IsPeripheralRoleSupported : " << adapter.IsPeripheralRoleSupported() << std::endl; + std::cout << "IsClassicSupported : " << adapter.IsClassicSupported() << std::endl; + std::cout << "IsLowEnergySupported : " << adapter.IsLowEnergySupported() << std::endl; + std::cout << "IsExtendedAdvertisingSupported : " << adapter.IsExtendedAdvertisingSupported() << std::endl; + std::cout << "IsAdvertisementOffloadSupported : " << adapter.IsAdvertisementOffloadSupported() << std::endl; + std::cout << "AreClassicSecureConnectionsSupported : " << adapter.AreClassicSecureConnectionsSupported() << std::endl; + std::cout << "AreLowEnergySecureConnectionsSupported : " << adapter.AreLowEnergySecureConnectionsSupported() << std::endl; } - - // spit out the updated properties - for (auto prop : upd.Properties()) + else { - std::cout << winrt::to_string(prop.Key()) << std::endl; + std::cout << "No BT adapter present" << std::endl; } - std::cout << "-----------" << std::endl; -} - -void OnDeviceStopped(enumeration::DeviceWatcher, foundation::IInspectable) -{ - + std::cout << "------------------------------------------" << std::endl; } -void OnEnumerationCompleted(enumeration::DeviceWatcher, foundation::IInspectable) -{ - m_enumerationCompleted = true; -} -void TestEnumeration() -{ - // enumerate ble MIDI devices - winrt::hstring deviceSelector = bt::BluetoothLEDevice::GetDeviceSelector(); +GattServiceProvider m_provider{ nullptr }; +winrt::event_token Revoke_AdvertisementStatusChanged; +winrt::event_token Revoke_GattReadRequest; +winrt::event_token Revoke_GattWriteRequest; +GattLocalCharacteristic m_dataIOCharacteristic{ nullptr }; - auto requestedProperties = winrt::single_threaded_vector( - { - L"System.DeviceInterface.Bluetooth.DeviceAddress", - L"System.DeviceInterface.Bluetooth.Flags", - L"System.DeviceInterface.Bluetooth.LastConnectedTime", - L"System.DeviceInterface.Bluetooth.Manufacturer", - L"System.DeviceInterface.Bluetooth.ModelNumber", - L"System.DeviceInterface.Bluetooth.ProductId", - L"System.DeviceInterface.Bluetooth.ProductVersion", - L"System.DeviceInterface.Bluetooth.ServiceGuid", - L"System.DeviceInterface.Bluetooth.VendorId", - L"System.DeviceInterface.Bluetooth.VendorIdSource", - L"System.Devices.Connected", - L"System.Devices.DeviceCapabilities", - L"System.Devices.DeviceCharacteristics" - } - ); - +void OnDataIOReadRequested(GattLocalCharacteristic const& /*source*/, GattReadRequestedEventArgs const& args) +{ + //std::cout << __FUNCTION__ << std::endl; - m_Watcher = enumeration::DeviceInformation::CreateWatcher(deviceSelector, requestedProperties); + auto request = args.GetRequestAsync().get(); - auto deviceAddedHandler = foundation::TypedEventHandler(OnDeviceAdded); - auto deviceRemovedHandler = foundation::TypedEventHandler(OnDeviceRemoved); - auto deviceUpdatedHandler = foundation::TypedEventHandler(OnDeviceUpdated); - auto deviceStoppedHandler = foundation::TypedEventHandler(OnDeviceStopped); - auto deviceEnumerationCompletedHandler = foundation::TypedEventHandler(OnEnumerationCompleted); + args.Session().MaintainConnection(true); - m_DeviceAdded = m_Watcher.Added(winrt::auto_revoke, deviceAddedHandler); - m_DeviceRemoved = m_Watcher.Removed(winrt::auto_revoke, deviceRemovedHandler); - m_DeviceUpdated = m_Watcher.Updated(winrt::auto_revoke, deviceUpdatedHandler); - m_DeviceStopped = m_Watcher.Stopped(winrt::auto_revoke, deviceStoppedHandler); - m_DeviceEnumerationCompleted = m_Watcher.EnumerationCompleted(winrt::auto_revoke, deviceEnumerationCompletedHandler); + std::cout << "Read Request: " << winrt::to_string(args.Session().DeviceId().Id()) + << ", length=" << request.Length() + << std::endl; - // this blocks - m_Watcher.Start(); + // should respond with zero only the first time + streams::DataWriter writer; + writer.WriteByte(0); + request.RespondWithValue(writer.DetachBuffer()); } - -IAsyncAction TestReceivingData() +void ProcessIncomingBuffer(streams::IBuffer buffer) { - std::cout << "Test Receiving Data ---------------------------------------------------------" << std::endl; - - winrt::hstring id = L"BluetoothLE#BluetoothLE3c:6a:a7:f0:4e:b0-48:b6:20:1a:71:9d"; - - try - { - gatt::GattSession session{ nullptr }; - - std::cout << "Creating session" << std::endl; + auto reader = streams::DataReader::FromBuffer(buffer); - auto bleId = bt::BluetoothDeviceId::FromId(id); + reader.ByteOrder(streams::ByteOrder::LittleEndian); - session = co_await gatt::GattSession::FromDeviceIdAsync(bleId); + // TODO: Process the data. We'll just display for now - std::cout << "Session created" << std::endl; + // Read header bytes + auto headerByte = reader.ReadByte(); - session.MaintainConnection(true); - - auto bleDevice = bt::BluetoothLEDevice::FromIdAsync(id).get(); + if (headerByte & 0x80) + { + // valid header byte. Get the rest of the info + auto timestampByte = reader.ReadByte(); - if (bleDevice != nullptr) + if (timestampByte & 0x80) { - std::cout << "Using device " << winrt::to_string(bleDevice.Name()) << std::endl; - - auto service = bleDevice.GetGattService(MIDI_BLE_SERVICE_UUID); - - if (service != nullptr) + // timestamp high is bits 5-0 of header (6 bits) + uint16_t timestampHigh = headerByte & 0x3F; + + // timestamp low is bites 6-0 of timestamp byte (7 bits) + uint16_t timestampLow = timestampByte & 0x7F; + + uint16_t fullTimestamp = timestampLow | (timestampHigh << 7); + + std::cout + << "TS:" + << std::setw(5) << std::setfill('0') + << std::dec + << fullTimestamp + << std::nouppercase + << " Data: "; + + // now read the message data + // In the real impl, this needs to account for + // timestamp (ts low) embedded in here between + // messages, as well as things like running + // status messages. This is just quick and dirty. + while (reader.UnconsumedBufferLength() > 0) { - std::cout << "Found service" << std::endl;; - - auto openStatus = co_await service.OpenAsync(gatt::GattSharingMode::SharedReadAndWrite); - - if (openStatus == gatt::GattOpenStatus::Success) - { - std::cout << "Service opened" << std::endl; - } - else - { - std::cout << "Unable to open service" << std::endl; - co_return; - } - - std::cout << "Getting MIDI Data IO characteristic" << std::endl; - - auto characteristicsResult = co_await service.GetCharacteristicsForUuidAsync(MIDI_BLE_DATA_IO_CHARACTERISTIC_UUID); - - if (characteristicsResult.Status() == gatt::GattCommunicationStatus::Success) - { - if (characteristicsResult.Characteristics().Size() == 0) - { - // this is unexpected - std::cout << "Returned no characteristics for the required UUID" << std::endl; - } - else if (characteristicsResult.Characteristics().Size() > 1) - { - // TODO: Need to find out under which cases this happens - std::cout << "Returned more than one characteristic for the same UUID" << std::endl; - } - else - { - std::cout << "Returned just the one characteristic for the UUID" << std::endl; - - auto characteristic = characteristicsResult.Characteristics().GetAt(0); - - co_await characteristic.WriteClientCharacteristicConfigurationDescriptorAsync( - gatt::GattClientCharacteristicConfigurationDescriptorValue::Notify); - - - auto valueChangedHandler = [&](gatt::GattCharacteristic const& sender, gatt::GattValueChangedEventArgs const& args) - { - //std::cout << "Value changed" << std::endl; - - // we get an IBuffer here - - //std::cout << "Data size is " << args.CharacteristicValue().Length() << " bytes" << std::endl; - - std::cout << "> "; - - for (int i = 0; i < args.CharacteristicValue().Length(); i++) - { - // read next byte - auto b = *(args.CharacteristicValue().data() + i); - - std::cout << "0x" << std::hex << (unsigned)b << " "; - } - - std::cout << std::endl; - }; - - // wire up ValueChanged so we actually get data - characteristic.ValueChanged(valueChangedHandler); - - while (true) - { - ::Sleep(100); - } - - session.Close(); + std::cout + << std::hex << std::setw(2) << std::setfill('0') << std::noshowbase + << std::uppercase + << (unsigned)reader.ReadByte() + << std::nouppercase + << " "; + } - } - } + std::cout << std::endl; + } + else + { + std::cout << "Invalid timestamp byte" << std::endl; + } + } + else + { + std::cout << "Invalid data" << std::endl; + } +} - service.Close(); - } - } +void OnDataIOWriteRequested(GattLocalCharacteristic const& /*source*/, GattWriteRequestedEventArgs const& args) +{ + //std::cout << __FUNCTION__ << std::endl; - std::cout << "Done" << std::endl; + auto request = args.GetRequestAsync().get(); + //std::cout << "Write Request: " << winrt::to_string(args.Session().DeviceId().Id()) + // << ", offset=" << request.Offset() + // << std::endl; - // wait for incoming data indefinitely + ProcessIncomingBuffer(request.Value()); - //auto characteristic = characteristicsResult.Characteristics().GetAt(0); +} - //auto readResult = characteristic.ReadValueAsync().get(); +void OnDataIOSubscribedClientsChanged(GattLocalCharacteristic const& /*source*/, foundation::IInspectable const& /*args*/) +{ + std::cout << __FUNCTION__ << std::endl; - //if (readResult.Status() == gatt::GattCommunicationStatus::Success) - //{ - // // we get an IBuffer here +} - // for (int i = 0; i < readResult.Value().Length(); i++) - // { - // // read next byte - // auto b = *(readResult.Value().data() + i); - // std::cout << std::hex << b << " "; - // } +void OnGattServiceProviderAdvertisementStatusChanged(GattServiceProvider const& /*source*/, GattServiceProviderAdvertisementStatusChangedEventArgs const& args) +{ +// std::cout << __FUNCTION__ << std::endl; - // std::cout << std::endl; + std::cout << "Provider ad status: "; - //} - //session.Close(); + switch (args.Status()) + { + case GattServiceProviderAdvertisementStatus::StartedWithoutAllAdvertisementData: + std::cout << "Started without all ad data"; + break; + case GattServiceProviderAdvertisementStatus::Started: + std::cout << "Started"; + break; + case GattServiceProviderAdvertisementStatus::Created: + std::cout << "Created"; + break; + case GattServiceProviderAdvertisementStatus::Stopped: + std::cout << "Stopped"; + break; + } + std::cout << std::endl; +} +void StartAsPeripheral() +{ + winrt::guid MIDI_BLE_SERVICE_UUID{ MIDI_BLE_SERVICE }; + winrt::guid MIDI_BLE_DATA_IO_CHARACTERISTIC_UUID{ MIDI_BLE_DATA_IO_CHARACTERISTIC }; + auto result = GattServiceProvider::CreateAsync(MIDI_BLE_SERVICE_UUID).get(); + if (result.Error() == BluetoothError::Success) + { + m_provider = result.ServiceProvider(); + + Revoke_AdvertisementStatusChanged = m_provider.AdvertisementStatusChanged(OnGattServiceProviderAdvertisementStatusChanged); + + GattServiceProviderAdvertisingParameters params; + params.IsConnectable(true); + params.IsDiscoverable(true); + + // BLE MIDI 1.0 requires WriteWithoutResponse + GattCharacteristicProperties dataIOProperties = + GattCharacteristicProperties::WriteWithoutResponse | + GattCharacteristicProperties::Read | + GattCharacteristicProperties::Notify; + + // BLE MIDI 1.0 supports encryption, and recommends it, but it isn't required. + // We'll have this as a user setting later so the user can decide if, when the + // PC is acting as a peripheral, encryption and/or auth is required. + GattLocalCharacteristicParameters dataIOParameters; + dataIOParameters.ReadProtectionLevel(GattProtectionLevel::Plain); + dataIOParameters.WriteProtectionLevel(GattProtectionLevel::Plain); // same + dataIOParameters.UserDescription(L"MIDI Prototype"); + dataIOParameters.CharacteristicProperties(dataIOProperties); + + auto characteristicResult = m_provider.Service().CreateCharacteristicAsync( + MIDI_BLE_DATA_IO_CHARACTERISTIC_UUID, + dataIOParameters + ).get(); + + if (characteristicResult.Error() == BluetoothError::Success) + { + m_dataIOCharacteristic = characteristicResult.Characteristic(); + Revoke_GattReadRequest = m_dataIOCharacteristic.ReadRequested(OnDataIOReadRequested); + Revoke_GattWriteRequest = m_dataIOCharacteristic.WriteRequested(OnDataIOWriteRequested); + m_dataIOCharacteristic.SubscribedClientsChanged(OnDataIOSubscribedClientsChanged); - } - catch (winrt::hresult_error err) - { - // Important: using .get() means we don't actually get to handle these exceptions. + // m_dataIOCharacteristic.StaticValue(); - if (err.code() == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)) - { - std::cout << "Cannot access the BLE device because it's being used by another process" << std::endl; + std::cout << "Start Advertising..." << std::endl; + m_provider.StartAdvertising(params); } else { - std::cout << "HRESULT exception 0x" << std::hex << err.code() << std::endl; + std::cout << "Failed to create characteristic" << std::endl; } - - co_return; } - catch (...) +} + +void StopPeripheral() +{ + std::cout << "Stopping peripheral..." << std::endl; + + if (m_provider != nullptr) { - std::cout << "Exception" << std::endl; + m_provider.StopAdvertising(); - co_return; - } + // clean up event handler + m_provider.AdvertisementStatusChanged(Revoke_AdvertisementStatusChanged); + + m_dataIOCharacteristic.ReadRequested(Revoke_GattReadRequest); + m_dataIOCharacteristic.WriteRequested(Revoke_GattWriteRequest); -} + m_provider == nullptr; + } +} int main() { init_apartment(); - //TestEnumeration(); - std::thread enumerationThread(TestEnumeration); + ReportAdapterCapabilities(); - enumerationThread.detach(); + TestFindingDevices(); + //StartAsPeripheral(); - while (!m_enumerationCompleted) - { - Sleep(1000); - } + //system("pause"); - TestReceivingData().get(); + //StopPeripheral(); - //system("pause > nul"); system("pause"); - - } diff --git a/src/prototypes/ble-midi1-proto/pch.cpp b/src/prototypes/ble-midi1-proto/pch.cpp index bcb5590b..6c31ef31 100644 --- a/src/prototypes/ble-midi1-proto/pch.cpp +++ b/src/prototypes/ble-midi1-proto/pch.cpp @@ -1 +1,9 @@ -#include "pch.h" +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App API and should be used +// in your Windows application via an official binary distribution. +// Further information: https://github.com/microsoft/MIDI/ +// ============================================================================ + +#include "pch.h" diff --git a/src/prototypes/ble-midi1-proto/pch.h b/src/prototypes/ble-midi1-proto/pch.h index 7854996e..cb645ba5 100644 --- a/src/prototypes/ble-midi1-proto/pch.h +++ b/src/prototypes/ble-midi1-proto/pch.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include @@ -26,8 +28,20 @@ namespace json = ::winrt::Windows::Data::Json; namespace enumeration = ::winrt::Windows::Devices::Enumeration; namespace foundation = ::winrt::Windows::Foundation; namespace collections = ::winrt::Windows::Foundation::Collections; -namespace bt = ::winrt::Windows::Devices::Bluetooth; -namespace gatt = ::winrt::Windows::Devices::Bluetooth::GenericAttributeProfile; namespace streams = ::winrt::Windows::Storage::Streams; -#include \ No newline at end of file +using namespace winrt::Windows::Devices::Bluetooth; +using namespace winrt::Windows::Devices::Bluetooth::Advertisement; +using namespace winrt::Windows::Devices::Bluetooth::Background; +using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile; + +#include +#include + +#include "abstraction_defs.h" +#include "ble_utilities.h" + +#include "MidiBleDevice.h" +#include "MidiBleBiDi.h" +#include "MidiBleEndpointManager.h" +#include "MidiBleAdvertisedEndpointManager.h" \ No newline at end of file diff --git a/src/user-tools/midi-console/Midi/Midi.csproj b/src/user-tools/midi-console/Midi/Midi.csproj index 986e7868..208ae7cf 100644 --- a/src/user-tools/midi-console/Midi/Midi.csproj +++ b/src/user-tools/midi-console/Midi/Midi.csproj @@ -27,11 +27,11 @@ - - - - - + + + + + diff --git a/src/user-tools/midi-console/midi-console.sln b/src/user-tools/midi-console/midi-console.sln index 896c4cf6..9ab554b0 100644 --- a/src/user-tools/midi-console/midi-console.sln +++ b/src/user-tools/midi-console/midi-console.sln @@ -5,22 +5,42 @@ VisualStudioVersion = 17.8.34004.107 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Midi", "Midi\Midi.csproj", "{2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Devices.Midi2.Tools.Shared", "..\shared\Microsoft.Devices.Midi2.Tools.Shared\Microsoft.Devices.Midi2.Tools.Shared.csproj", "{1830A525-42BF-4568-9F4B-A192671CB16F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Debug|Any CPU.ActiveCfg = Debug|x64 + {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Debug|Any CPU.Build.0 = Debug|x64 {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Debug|ARM64.ActiveCfg = Debug|ARM64 {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Debug|ARM64.Build.0 = Debug|ARM64 {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Debug|x64.ActiveCfg = Debug|x64 {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Debug|x64.Build.0 = Debug|x64 + {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Release|Any CPU.ActiveCfg = Release|x64 + {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Release|Any CPU.Build.0 = Release|x64 {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Release|ARM64.ActiveCfg = Release|ARM64 {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Release|ARM64.Build.0 = Release|ARM64 {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Release|x64.ActiveCfg = Release|x64 {2EC5F5EE-BA4F-4D9C-90A8-4E18A08A6CD7}.Release|x64.Build.0 = Release|x64 + {1830A525-42BF-4568-9F4B-A192671CB16F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Debug|ARM64.Build.0 = Debug|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Debug|x64.ActiveCfg = Debug|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Debug|x64.Build.0 = Debug|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Release|Any CPU.Build.0 = Release|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Release|ARM64.ActiveCfg = Release|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Release|ARM64.Build.0 = Release|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Release|x64.ActiveCfg = Release|Any CPU + {1830A525-42BF-4568-9F4B-A192671CB16F}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/user-tools/midi-settings-extensibility/midi-settings-extensibility.csproj b/src/user-tools/midi-settings-extensibility/midi-settings-extensibility.csproj index 8c5dc094..157ba824 100644 --- a/src/user-tools/midi-settings-extensibility/midi-settings-extensibility.csproj +++ b/src/user-tools/midi-settings-extensibility/midi-settings-extensibility.csproj @@ -3,7 +3,7 @@ enable enable - net8.0-windows10.0.20348.0 + net8.0-windows10.0.22621.0 10.0.20348.0 Microsoft.Midi.Settings.Extensibility Microsoft.Midi.Settings.Extensibility diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings.Core/Microsoft.Midi.Settings.Core.csproj b/src/user-tools/midi-settings/Microsoft.Midi.Settings.Core/Microsoft.Midi.Settings.Core.csproj index f21fc394..440a1033 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings.Core/Microsoft.Midi.Settings.Core.csproj +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings.Core/Microsoft.Midi.Settings.Core.csproj @@ -1,7 +1,9 @@  net8.0-windows10.0.20348.0 - Microsoft.Midi.Settings.Core + 10.0.22621.0 + 10.0.20348.0 + Microsoft.Midi.Settings.Core AnyCPU;x64;x86 x86;x64;arm64;AnyCPU enable @@ -9,6 +11,7 @@ true false + 10.0.20348.0 diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Config/ConfigFile.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Config/ConfigFile.cs new file mode 100644 index 00000000..58dc0c63 --- /dev/null +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Config/ConfigFile.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Windows.Data.Json; +using System.Threading.Tasks; + +namespace Microsoft.Midi.Settings.Config +{ + // this code really should be refactored into a shared C++/WinRT lib used in the service and here + + + internal class ConfigFile + { + private JsonObject _jsonObject; + + //public JsonObject GetEndpointTransportPluginSettingsForTransport(Guid transportId) + //{ + + + //} + + + + + + + } +} diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Config/ConfigFileManager.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Config/ConfigFileManager.cs new file mode 100644 index 00000000..dfac677d --- /dev/null +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Config/ConfigFileManager.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Hosting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Midi.Settings.Config +{ + internal class ConfigFileManager + { + + // should likely take a path + public void ChangeActiveConfigFile(string configFileName) + { + + } + + // should likely return a path + public string GetConfigFileFolder() + { + return string.Empty; + } + + // should likely return a path or similar object + public string GetActiveConfigFileName() + { + // gets the active config file name from the registry + + return string.Empty; + + } + + public void BackupCurrentConfigFile() + { + // backs up the current config file using a numbering scheme + + + } + + + } +} diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Config/EndpointConfigMetadataPayload.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Config/EndpointConfigMetadataPayload.cs new file mode 100644 index 00000000..e61380c3 --- /dev/null +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Config/EndpointConfigMetadataPayload.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.Data.Json; + + +namespace Microsoft.Midi.Settings.Config +{ + internal class EndpointConfigMetadataPayload : IMidiServiceTransportPluginConfiguration + { + public bool IsFromConfigurationFile => true; + + public JsonObject SettingsJson { get; private set; } + + public Guid TransportId { get; private set; } + + private string _newName; + private string _newDescription; + private string _newSmallImagePath; + private string _newLargeImagePath; + + public EndpointConfigMetadataPayload(Guid transportId, string newName, string newDescription, string newSmallImagePath, string newLargeImagePath) + { + TransportId = transportId; + + _newName = newName.Trim(); + _newDescription = newDescription.Trim(); + _newSmallImagePath = newSmallImagePath.Trim(); + _newLargeImagePath = newLargeImagePath.Trim(); + } + + // build the json for endpoint properties updates + // "endpointTransportPluginSettings": + // { + // abstraction guid : + // { + // "update" + // [ + // { + // "match": + // [ + // { + // "match_prop_name" : "match_value" + // } + // ] + // "prop_name" : "value" + // } + // ] + // } + // } + + public void BuildJson() + { + + } + } +} diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Helpers/BooleanToInverseVisibilityConverter.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Helpers/BooleanToInverseVisibilityConverter.cs new file mode 100644 index 00000000..860b02aa --- /dev/null +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Helpers/BooleanToInverseVisibilityConverter.cs @@ -0,0 +1,31 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Data; + +namespace Microsoft.Midi.Settings.Helpers; + +public class BooleanToInverseVisibilityConverter : IValueConverter +{ + public BooleanToInverseVisibilityConverter() + { + } + + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is bool val) + { + if (!val) + { + return Visibility.Visible; + } + + return Visibility.Collapsed; + } + + throw new ArgumentException("BooleanToInverseVisibilityConverter object must be a bool"); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return null; + } +} \ No newline at end of file diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Microsoft.Midi.Settings.csproj b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Microsoft.Midi.Settings.csproj index a5452e69..a89e3e89 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Microsoft.Midi.Settings.csproj +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Microsoft.Midi.Settings.csproj @@ -2,6 +2,7 @@ WinExe net8.0-windows10.0.20348.0 + 10.0.22621.0 10.0.20348.0 enable Microsoft.Midi.Settings @@ -10,12 +11,12 @@ app.manifest x64;arm64 win10-x64;win10-arm64 - Properties\PublishProfiles\win10-$(Platform).pubxml enable true true - None + Properties\PublishProfiles\win10-$(Platform).pubxml + DAC77297934678914CC6B107CE26743135B68C64 Microsoft.Midi.Settings_TemporaryKey.pfx @@ -78,13 +79,13 @@ - + - + @@ -97,6 +98,7 @@ + diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Package.appxmanifest b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Package.appxmanifest index e1a39fa1..a944d499 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Package.appxmanifest +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Package.appxmanifest @@ -7,7 +7,8 @@ xmlns:genTemplate="http://schemas.microsoft.com/appx/developer/templatestudio" xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" - IgnorableNamespaces="uap rescap genTemplate"> + xmlns:iot="http://schemas.microsoft.com/appx/manifest/iot/windows10" + IgnorableNamespaces="uap rescap genTemplate iot"> - - + + - - - - - NetworkMidiTransportPlugin.dll - - - - - - - + + + + + + + diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Properties/launchsettings.json b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Properties/launchsettings.json index 4a231fa9..1a279b4c 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Properties/launchsettings.json +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Properties/launchsettings.json @@ -1,10 +1,16 @@ { "profiles": { "Microsoft.Midi.Settings (Package)": { - "commandName": "MsixPackage", - "allowLocalNetworkLoopbackProperty": true, - "nativeDebugging": true, - "hotReloadEnabled": false + "commandName": "MsixPackage", + "commandLineArgs": "", + "alwaysReinstallApp": false, + "remoteDebugEnabled": false, + "allowLocalNetworkLoopbackProperty": true, + "authenticationMode": "Windows", + "doNotLaunchApp": false, + "remoteDebugMachine": "", + "nativeDebugging": true, + "hotReloadEnabled": false }, "Microsoft.Midi.Settings (Unpackaged)": { "commandName": "Project", diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/Data/EndpointUserMetadata.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/Data/EndpointUserMetadata.cs new file mode 100644 index 00000000..1c607e83 --- /dev/null +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/Data/EndpointUserMetadata.cs @@ -0,0 +1,24 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Midi.Settings.ViewModels.Data +{ + public partial class EndpointUserMetadata : ObservableObject + { + [ObservableProperty] + private string name; + + [ObservableProperty] + private string description; + + [ObservableProperty] + private string smallImagePath; + + [ObservableProperty] + private string largeImagePath; + } +} diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/DeviceDetailViewModel.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/DeviceDetailViewModel.cs index 9d2cacfd..4105502b 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/DeviceDetailViewModel.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/DeviceDetailViewModel.cs @@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.Midi.Settings.Contracts.ViewModels; using Microsoft.Midi.Settings.Models; +using Microsoft.Midi.Settings.ViewModels.Data; using Microsoft.UI.Dispatching; using Windows.Devices.Enumeration; @@ -14,6 +15,7 @@ namespace Microsoft.Midi.Settings.ViewModels { public class DeviceDetailViewModel : ObservableRecipient, INavigationAware { + public EndpointUserMetadata UserMetadata { get; private set; } = new EndpointUserMetadata(); public MidiEndpointDeviceInformation? DeviceInformation { diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/TroubleshootingViewModel.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/TroubleshootingViewModel.cs index bd722159..4ffb8d65 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/TroubleshootingViewModel.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/TroubleshootingViewModel.cs @@ -1,14 +1,68 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Principal; +using System.ServiceProcess; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Microsoft.Midi.Settings.Contracts.Services; +using Microsoft.Midi.Settings.Contracts.ViewModels; +using Microsoft.Midi.Settings.Models; +using Microsoft.UI.Dispatching; + namespace Microsoft.Midi.Settings.ViewModels; -public class TroubleshootingViewModel : ObservableRecipient +public class TroubleshootingViewModel : ObservableRecipient, INavigationAware { - public TroubleshootingViewModel() + private DispatcherQueue _dispatcherQueue; + private INavigationService _navigationService; + + public ICommand RestartServiceCommand + { + get; private set; + } + + public bool IsUserRunningAsAdmin + { + get { return UserHelper.CurrentUserHasAdminRights(); } + } + + public TroubleshootingViewModel(INavigationService navigationService) + { + _navigationService = navigationService; + + _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + + + RestartServiceCommand = new RelayCommand( + async (param) => + { + System.Diagnostics.Debug.WriteLine("RestartServiceCommand exec"); + + //_navigationService.NavigateTo(typeof(DeviceDetailViewModel).FullName!, param); + + }); + } + + + public void RestartService() { + var controller = MidiServiceHelper.GetServiceController(); + MidiServiceHelper.StopService(controller); + MidiServiceHelper.StartService(controller); } + + + public void OnNavigatedFrom() + { + } + + + public void OnNavigatedTo(object parameter) + { + } + } diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml index 97fe713f..1c937b7b 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml @@ -91,47 +91,34 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml.cs index 8c46ced1..5468db70 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml.cs @@ -16,6 +16,12 @@ using Windows.Foundation.Collections; using Windows.UI.Popups; using Microsoft.UI.Windowing; +using Windows.Storage.Pickers; +using WinRT.Interop; +using Microsoft.Midi.Settings.ViewModels.Data; +using Microsoft.UI.Xaml.Media.Imaging; +using Windows.Storage.Streams; +using Windows.Storage; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -40,12 +46,144 @@ public DeviceDetailPage() this.InitializeComponent(); } + private async void Button_Click(object sender, RoutedEventArgs e) { - var messageDialog = App.MainWindow.CreateMessageDialog("Setting endpoint properties through the UI is not yet implemented.", "Settings Preview"); + ViewModel.UserMetadata.Name = ViewModel.DeviceInformation.UserSuppliedName; + ViewModel.UserMetadata.Description = ViewModel.DeviceInformation.UserSuppliedDescription; + ViewModel.UserMetadata.LargeImagePath = ViewModel.DeviceInformation.UserSuppliedLargeImagePath; + ViewModel.UserMetadata.SmallImagePath = ViewModel.DeviceInformation.UserSuppliedSmallImagePath; + editUserDefinedPropertiesDialog.Width = this.Width / 3; + + + // TODO: Should probably have these in the viewmodel as + // we'll need a renderable image anyway. This code is temp + + if (ViewModel.UserMetadata.LargeImagePath != string.Empty) + { + var file = await StorageFile.GetFileFromPathAsync(ViewModel.UserMetadata.LargeImagePath); + + if (file != null) + { + // Open a stream for the selected file. + // The 'using' block ensures the stream is disposed + // after the image is loaded. + using (IRandomAccessStream fileStream = + await file.OpenAsync(FileAccessMode.Read)) + { + // Set the image source to the selected bitmap. + var bitmapImage = new BitmapImage(); + + bitmapImage.SetSource(fileStream); + + UserMetadataLargeImagePreview.Source = bitmapImage; + } + } + } + + if (ViewModel.UserMetadata.SmallImagePath != string.Empty) + { + var file = await StorageFile.GetFileFromPathAsync(ViewModel.UserMetadata.SmallImagePath); + + if (file != null) + { + // Open a stream for the selected file. + // The 'using' block ensures the stream is disposed + // after the image is loaded. + using (IRandomAccessStream fileStream = + await file.OpenAsync(FileAccessMode.Read)) + { + // Set the image source to the selected bitmap. + var bitmapImage = new BitmapImage(); + + bitmapImage.SetSource(fileStream); + + UserMetadataSmallImagePreview.Source = bitmapImage; + } + } + } + + + + ContentDialogResult result = await editUserDefinedPropertiesDialog.ShowAsync(ContentDialogPlacement.Popup); + + if (result == ContentDialogResult.Primary) + { + // save the changes + + + // refresh the device information? + + } + } + + // edit popup + private async void BrowseLargeImage_Click(object sender, RoutedEventArgs e) + { + var filePicker = App.MainWindow.CreateOpenFilePicker(); + + filePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; + filePicker.FileTypeFilter.Add(".png"); + filePicker.FileTypeFilter.Add(".jpg"); + filePicker.FileTypeFilter.Add(".svg"); + + var file = await filePicker.PickSingleFileAsync(); + + if (file != null) + { + // update the edit vm + ViewModel.UserMetadata.LargeImagePath = file.Path; + + // Open a stream for the selected file. + // The 'using' block ensures the stream is disposed + // after the image is loaded. + using (IRandomAccessStream fileStream = + await file.OpenAsync(FileAccessMode.Read)) + { + // Set the image source to the selected bitmap. + var bitmapImage = new BitmapImage(); + + bitmapImage.SetSource(fileStream); + + UserMetadataLargeImagePreview.Source = bitmapImage; + } + + } + } + + // edit popup + private async void BrowseSmallImage_Click(object sender, RoutedEventArgs e) + { + var filePicker = App.MainWindow.CreateOpenFilePicker(); + + filePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; + filePicker.FileTypeFilter.Add(".png"); + filePicker.FileTypeFilter.Add(".jpg"); + filePicker.FileTypeFilter.Add(".svg"); + + var file = await filePicker.PickSingleFileAsync(); + + if (file != null) + { + // update the edit vm + ViewModel.UserMetadata.SmallImagePath = file.Path; + + // Open a stream for the selected file. + // The 'using' block ensures the stream is disposed + // after the image is loaded. + using (IRandomAccessStream fileStream = + await file.OpenAsync(FileAccessMode.Read)) + { + // Set the image source to the selected bitmap. + var bitmapImage = new BitmapImage(); + + bitmapImage.SetSource(fileStream); + + UserMetadataSmallImagePreview.Source = bitmapImage; + } + } - await messageDialog.ShowAsync(); } } } diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/TroubleshootingPage.xaml b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/TroubleshootingPage.xaml index 1ef37337..7aa5d161 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/TroubleshootingPage.xaml +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/TroubleshootingPage.xaml @@ -9,8 +9,18 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:labs="using:CommunityToolkit.Labs.WinUI" + xmlns:midi2="using:Windows.Devices.Midi2" + xmlns:controls="using:CommunityToolkit.WinUI.Controls" + xmlns:helpers="using:Microsoft.Midi.Settings.Helpers" mc:Ignorable="d"> + + + + + + + @@ -19,6 +29,7 @@ Text="If you are running into problems with MIDI on your PC, the tools below may help you recover without requiring a reboot."> + @@ -80,7 +92,13 @@ -