From c3f73148b1d18db4bf2fa3c12b167423e2f46ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Wed, 31 Dec 2025 17:17:25 -0500 Subject: [PATCH 1/3] winmidi: implement the raw API, try to fix SDK issues --- .github/workflows/build_cmake.yml | 4 +- .github/workflows/build_python.yml | 2 +- cmake/libremidi.winmidi.cmake | 9 +- .../libremidi/backends/winmidi/helpers.hpp | 6 ++ .../libremidi/backends/winmidi/midi_in.hpp | 95 +++++++++++++++++++ .../libremidi/backends/winmidi/midi_out.hpp | 35 ++++++- 6 files changed, 146 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml index cf3fd622..d0a310c0 100644 --- a/.github/workflows/build_cmake.yml +++ b/.github/workflows/build_cmake.yml @@ -24,7 +24,7 @@ jobs: name: "Windows (MSVC)", os: windows-latest, generator: "", - cmakeflags: "-DLIBREMIDI_NO_WINUWP=0 -DBOOST_ROOT=$PWD/boost_1_86_0 -DCMAKE_GENERATOR_PLATFORM=version=10.0.22621.0", + cmakeflags: "-DLIBREMIDI_NO_WINUWP=0 -DBOOST_ROOT=$PWD/boost_1_86_0", test_target: "RUN_TESTS", tests: 1, examples: 1 @@ -33,7 +33,7 @@ jobs: name: "Windows (MSVC, arm64)", os: windows-11-arm, generator: "", - cmakeflags: "-DLIBREMIDI_NO_WINUWP=0 -DBOOST_ROOT=$PWD/boost_1_86_0 -DCMAKE_GENERATOR_PLATFORM=version=10.0.22621.0", + cmakeflags: "-DLIBREMIDI_NO_WINUWP=0 -DBOOST_ROOT=$PWD/boost_1_86_0", test_target: "RUN_TESTS", tests: 1, examples: 1 diff --git a/.github/workflows/build_python.yml b/.github/workflows/build_python.yml index 53fc7624..3d00b227 100644 --- a/.github/workflows/build_python.yml +++ b/.github/workflows/build_python.yml @@ -23,7 +23,7 @@ jobs: name: "Windows (MSVC)", os: windows-latest, generator: "", - cmakeflags: "-DLIBREMIDI_NO_WINUWP=0 -DBOOST_ROOT=$PWD/boost_1_86_0 -DCMAKE_GENERATOR_PLATFORM=version=10.0.22621.0", + cmakeflags: "-DLIBREMIDI_NO_WINUWP=0 -DBOOST_ROOT=$PWD/boost_1_86_0", environment: "" } - { diff --git a/cmake/libremidi.winmidi.cmake b/cmake/libremidi.winmidi.cmake index 0b6e74c4..0f6567d4 100644 --- a/cmake/libremidi.winmidi.cmake +++ b/cmake/libremidi.winmidi.cmake @@ -5,7 +5,7 @@ endif() if(LIBREMIDI_DOWNLOAD_CPPWINRT) if(NOT EXISTS "${CMAKE_BINARY_DIR}/winmidi-headers.zip") file(DOWNLOAD - https://github.com/microsoft/MIDI/releases/download/dev-preview-9-namm-4/Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250121-1820.nupkg + https://github.com/microsoft/MIDI/releases/download/rc-1/Microsoft.Windows.Devices.Midi2.1.0.14-rc.1.209.nupkg "${CMAKE_BINARY_DIR}/winmidi-headers.zip" ) endif() @@ -52,6 +52,9 @@ if(CPPWINRT_TOOL) file( COPY "${CMAKE_BINARY_DIR}/winmidi-headers/build/native/include/winmidi/init" + "${CMAKE_BINARY_DIR}/winmidi-headers/build/native/include/winmidi/WindowsMidiServicesAppSdkComExtensions.h" + "${CMAKE_BINARY_DIR}/winmidi-headers/build/native/include/winmidi/WindowsMidiServicesAppSdkComExtensions_i.c" + "${CMAKE_BINARY_DIR}/winmidi-headers/build/native/include/winmidi/WindowsMidiServicesAppSdkComExtensions_p.c" DESTINATION "${CMAKE_BINARY_DIR}/cppwinrt-winmidi/" ) @@ -75,3 +78,7 @@ target_include_directories(libremidi SYSTEM ${_public} ) target_compile_definitions(libremidi ${_public} LIBREMIDI_WINMIDI) target_link_libraries(libremidi ${_public} RuntimeObject windowsapp) +target_sources(libremidi PRIVATE + "${CMAKE_BINARY_DIR}/cppwinrt-winmidi/WindowsMidiServicesAppSdkComExtensions_i.c" + "${CMAKE_BINARY_DIR}/cppwinrt-winmidi/WindowsMidiServicesAppSdkComExtensions_p.c" +) diff --git a/include/libremidi/backends/winmidi/helpers.hpp b/include/libremidi/backends/winmidi/helpers.hpp index 15ea3b07..367e6487 100644 --- a/include/libremidi/backends/winmidi/helpers.hpp +++ b/include/libremidi/backends/winmidi/helpers.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -15,6 +16,11 @@ #include #include +#if __has_include() +#include + #define LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS 1 +#endif + // MinGW support #if !defined(_MSC_VER) namespace Microsoft::Windows::Devices::Midi2::Initialization diff --git a/include/libremidi/backends/winmidi/midi_in.hpp b/include/libremidi/backends/winmidi/midi_in.hpp index 899c56de..947afcee 100644 --- a/include/libremidi/backends/winmidi/midi_in.hpp +++ b/include/libremidi/backends/winmidi/midi_in.hpp @@ -19,6 +19,56 @@ class midi_in_impl final { } configuration; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + struct raw_callback_type final : IMidiEndpointConnectionMessagesReceivedCallback + { + explicit raw_callback_type(midi_in_impl& self) + : self{self} + { + + } + + midi_in_impl& self; + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override + { + if (!ppvObject) + return E_POINTER; + + if (riid == __uuidof(IUnknown) || + riid == __uuidof(IMidiEndpointConnectionMessagesReceivedCallback)) + { + *ppvObject = static_cast(this); + AddRef(); + return S_OK; + } + + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() override + { + return 1; + } + + ULONG STDMETHODCALLTYPE Release() override + { + return 1; + } + + HRESULT STDMETHODCALLTYPE MessagesReceived( + GUID sessionId, + GUID connectionId, + UINT64 timestamp, + UINT32 wordCount, + UINT32 *messages) override { + HRESULT res{}; + self.process_message(sessionId, connectionId, timestamp, wordCount, messages); + return res; + } + } raw_callback{*this}; +#endif + explicit midi_in_impl( libremidi::ump_input_configuration&& conf, winmidi::input_configuration&& apiconf) : configuration{std::move(conf), std::move(apiconf)} @@ -55,12 +105,20 @@ class midi_in_impl final // TODO use a MidiGroupEndpointListener for the filtering m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS m_revoke_token = m_endpoint.MessageReceived( [this]( const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&, const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) { process_message(args); }); +#else + m_raw_endpoint = m_endpoint.as(); + + m_raw_endpoint->SetMessagesReceivedCallback( + &raw_callback + ); +#endif m_endpoint.Open(); @@ -96,9 +154,43 @@ class midi_in_impl final {ump_space, ump_space + b.Size()}, m_processing.timestamp(to_ns, 0)); } +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + void process_message( + const GUID& /* sessionId */, + const GUID& /* connectionId */, + UINT64 timestamp, + UINT32 wordCount, + UINT32 *ump) + { + static constexpr timestamp_backend_info timestamp_info{ + .has_absolute_timestamps = true, + .absolute_is_monotonic = false, + .has_samples = false, + }; + + if(wordCount == 0) + return; + + if (m_group_filter >= 0) + { + int group = cmidi2_ump_get_group(ump); + if (group != m_group_filter) + return; + } + + auto to_ns = [t = timestamp] { return t; }; + m_processing.on_bytes( + {ump, ump + wordCount}, m_processing.timestamp(to_ns, 0)); + } +#endif + stdx::error close_port() override { +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS m_endpoint.MessageReceived(m_revoke_token); +#else + m_raw_endpoint->RemoveMessagesReceivedCallback(); +#endif m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId()); return stdx::error{}; } @@ -109,6 +201,9 @@ class midi_in_impl final MidiSession m_session; winrt::event_token m_revoke_token{}; winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + winrt::impl::com_ref m_raw_endpoint{}; +#endif midi2::input_state_machine m_processing{this->configuration}; int m_group_filter = -1; }; diff --git a/include/libremidi/backends/winmidi/midi_out.hpp b/include/libremidi/backends/winmidi/midi_out.hpp index 1937d248..193f7649 100644 --- a/include/libremidi/backends/winmidi/midi_out.hpp +++ b/include/libremidi/backends/winmidi/midi_out.hpp @@ -47,6 +47,9 @@ class midi_out_impl final return std::errc::address_not_available; m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + m_raw_endpoint = m_endpoint.as(); + #endif m_endpoint.Open(); return stdx::error{}; @@ -61,6 +64,8 @@ class midi_out_impl final stdx::error send_ump(const uint32_t* message, size_t size) override { auto write_func = [this](const uint32_t* ump, int64_t bytes) -> std::errc { + +#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS MidiSendMessageResults ret{}; switch (bytes / 4) { @@ -80,9 +85,34 @@ class midi_out_impl final default: return std::errc::bad_message; } - if (ret != MidiSendMessageResults::Succeeded) return std::errc::bad_message; + #else + HRESULT ret{}; + switch (bytes / 4) + { + case 1: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(1, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 1, (UINT32*)ump); + break; + case 2: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(2, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 2, (UINT32*)ump); + break; + case 3: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(3, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 3, (UINT32*)ump); + break; + case 4: + assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(4, (UINT32*)ump)); + ret = m_raw_endpoint->SendMidiMessagesRaw(0, 4, (UINT32*)ump); + break; + default: + return std::errc::bad_message; + } + #endif + if(ret < 0) + return std::errc::io_error; return std::errc{0}; }; @@ -92,6 +122,9 @@ class midi_out_impl final private: MidiSession m_session; winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr}; +#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS + winrt::impl::com_ref m_raw_endpoint{}; +#endif }; } From 927fddb0c97bf19b76ccb3ea96fff7eab5301f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Wed, 31 Dec 2025 21:53:50 -0500 Subject: [PATCH 2/3] ci: fix for msys2 --- include/libremidi/backends/winmidi/midi_in.hpp | 4 ++-- include/libremidi/backends/winmidi/midi_out.hpp | 2 +- include/libremidi/backends/winuwp/helpers.hpp | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/libremidi/backends/winmidi/midi_in.hpp b/include/libremidi/backends/winmidi/midi_in.hpp index 947afcee..91e651ff 100644 --- a/include/libremidi/backends/winmidi/midi_in.hpp +++ b/include/libremidi/backends/winmidi/midi_in.hpp @@ -35,7 +35,7 @@ class midi_in_impl final return E_POINTER; if (riid == __uuidof(IUnknown) || - riid == __uuidof(IMidiEndpointConnectionMessagesReceivedCallback)) + riid == IID_IMidiEndpointConnectionMessagesReceivedCallback) { *ppvObject = static_cast(this); AddRef(); @@ -113,7 +113,7 @@ class midi_in_impl final process_message(args); }); #else - m_raw_endpoint = m_endpoint.as(); + m_endpoint.as(IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); m_raw_endpoint->SetMessagesReceivedCallback( &raw_callback diff --git a/include/libremidi/backends/winmidi/midi_out.hpp b/include/libremidi/backends/winmidi/midi_out.hpp index 193f7649..74e3adc8 100644 --- a/include/libremidi/backends/winmidi/midi_out.hpp +++ b/include/libremidi/backends/winmidi/midi_out.hpp @@ -48,7 +48,7 @@ class midi_out_impl final m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); #if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS - m_raw_endpoint = m_endpoint.as(); + m_endpoint.as(IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); #endif m_endpoint.Open(); diff --git a/include/libremidi/backends/winuwp/helpers.hpp b/include/libremidi/backends/winuwp/helpers.hpp index 380e4586..e8e6253a 100644 --- a/include/libremidi/backends/winuwp/helpers.hpp +++ b/include/libremidi/backends/winuwp/helpers.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include From a1981b2964c3d4562ee1ee3f886dbf09dbbeacbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Wed, 31 Dec 2025 22:37:19 -0500 Subject: [PATCH 3/3] winmidi: make com extensions work in header-only mode --- cmake/libremidi.winmidi.cmake | 4 ---- include/libremidi/backends/winmidi/helpers.hpp | 8 ++++++++ include/libremidi/backends/winmidi/midi_in.hpp | 4 ++-- include/libremidi/backends/winmidi/midi_out.hpp | 2 +- include/libremidi/cmidi2.hpp | 2 ++ include/libremidi/error.hpp | 4 ++++ 6 files changed, 17 insertions(+), 7 deletions(-) diff --git a/cmake/libremidi.winmidi.cmake b/cmake/libremidi.winmidi.cmake index 0f6567d4..7da5485f 100644 --- a/cmake/libremidi.winmidi.cmake +++ b/cmake/libremidi.winmidi.cmake @@ -78,7 +78,3 @@ target_include_directories(libremidi SYSTEM ${_public} ) target_compile_definitions(libremidi ${_public} LIBREMIDI_WINMIDI) target_link_libraries(libremidi ${_public} RuntimeObject windowsapp) -target_sources(libremidi PRIVATE - "${CMAKE_BINARY_DIR}/cppwinrt-winmidi/WindowsMidiServicesAppSdkComExtensions_i.c" - "${CMAKE_BINARY_DIR}/cppwinrt-winmidi/WindowsMidiServicesAppSdkComExtensions_p.c" -) diff --git a/include/libremidi/backends/winmidi/helpers.hpp b/include/libremidi/backends/winmidi/helpers.hpp index 367e6487..f2150baa 100644 --- a/include/libremidi/backends/winmidi/helpers.hpp +++ b/include/libremidi/backends/winmidi/helpers.hpp @@ -19,6 +19,14 @@ #if __has_include() #include #define LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS 1 + +#define LIBREMIDI_DEFINE_GUID_CONSTEXPR(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ + static constexpr const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} + +namespace libremidi { + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_IMidiEndpointConnectionMessagesReceivedCallback,0x8087b303,0x0519,0x31d1,0x31,0xd1,0x00,0x00,0x00,0x00,0x00,0x10); + LIBREMIDI_DEFINE_GUID_CONSTEXPR(IID, IID_IMidiEndpointConnectionRaw,0x8087b303,0x0519,0x31d1,0x31,0xd1,0x00,0x00,0x00,0x00,0x00,0x20); +} #endif // MinGW support diff --git a/include/libremidi/backends/winmidi/midi_in.hpp b/include/libremidi/backends/winmidi/midi_in.hpp index 91e651ff..2906f23b 100644 --- a/include/libremidi/backends/winmidi/midi_in.hpp +++ b/include/libremidi/backends/winmidi/midi_in.hpp @@ -35,7 +35,7 @@ class midi_in_impl final return E_POINTER; if (riid == __uuidof(IUnknown) || - riid == IID_IMidiEndpointConnectionMessagesReceivedCallback) + riid == libremidi::IID_IMidiEndpointConnectionMessagesReceivedCallback) { *ppvObject = static_cast(this); AddRef(); @@ -113,7 +113,7 @@ class midi_in_impl final process_message(args); }); #else - m_endpoint.as(IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); + m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); m_raw_endpoint->SetMessagesReceivedCallback( &raw_callback diff --git a/include/libremidi/backends/winmidi/midi_out.hpp b/include/libremidi/backends/winmidi/midi_out.hpp index 74e3adc8..577123e0 100644 --- a/include/libremidi/backends/winmidi/midi_out.hpp +++ b/include/libremidi/backends/winmidi/midi_out.hpp @@ -48,7 +48,7 @@ class midi_out_impl final m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId()); #if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS - m_endpoint.as(IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); + m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void()); #endif m_endpoint.Open(); diff --git a/include/libremidi/cmidi2.hpp b/include/libremidi/cmidi2.hpp index 7148df4f..42ad4636 100644 --- a/include/libremidi/cmidi2.hpp +++ b/include/libremidi/cmidi2.hpp @@ -6,8 +6,10 @@ #include #include +#if !defined(_MSC_VER) #pragma GCC system_header #pragma clang system_header +#endif #define CMIDI2_MIDI_2_0_RESERVED 0 #define CMIDI2_JR_TIMESTAMP_TICKS_PER_SECOND 31250 diff --git a/include/libremidi/error.hpp b/include/libremidi/error.hpp index cf1bcf60..f8c30d96 100644 --- a/include/libremidi/error.hpp +++ b/include/libremidi/error.hpp @@ -1,11 +1,15 @@ #pragma once #include +#if !defined(_MSC_VER) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #pragma GCC diagnostic ignored "-Wunused-parameter" +#endif #include +#if !defined(_MSC_VER) #pragma GCC diagnostic pop +#endif #include #include