From 1129ac9b161746e6555fbedb283bc749bb4f23e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Mon, 5 Jan 2026 22:55:21 -0500 Subject: [PATCH 1/3] core: allow to use boost::variant2 instead of std::variant --- include/libremidi/backends.hpp | 6 +- .../backends/alsa_seq/shared_handler.hpp | 6 +- .../backends/jack/shared_handler.hpp | 12 ++-- .../backends/pipewire/shared_handler.hpp | 10 ++-- .../libremidi/backends/winmidi/midi_in.hpp | 2 +- .../libremidi/backends/winmidi/midi_out.hpp | 2 +- include/libremidi/cmidi2.hpp | 2 + include/libremidi/config.hpp | 55 +++++++++++++++++-- include/libremidi/configurations.hpp | 16 +++--- include/libremidi/libremidi.cpp | 6 +- include/libremidi/midi_in.cpp | 10 ++-- include/libremidi/midi_out.cpp | 6 +- include/libremidi/observer.cpp | 6 +- include/libremidi/port_comparison.hpp | 4 +- include/libremidi/port_information.hpp | 4 +- include/libremidi/types.hpp | 12 ++-- 16 files changed, 103 insertions(+), 56 deletions(-) diff --git a/include/libremidi/backends.hpp b/include/libremidi/backends.hpp index 9d06f646..5c445edb 100644 --- a/include/libremidi/backends.hpp +++ b/include/libremidi/backends.hpp @@ -234,7 +234,7 @@ auto for_backend(libremidi::API api, F&& f) void for_input_configuration(auto f, libremidi::input_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; @@ -249,7 +249,7 @@ void for_input_configuration(auto f, libremidi::input_api_configuration& api_con void for_output_configuration(auto f, libremidi::output_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; @@ -264,7 +264,7 @@ void for_output_configuration(auto f, libremidi::output_api_configuration& api_c void for_observer_configuration(auto f, libremidi::observer_api_configuration& api_conf) { auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { f(*conf); return true; diff --git a/include/libremidi/backends/alsa_seq/shared_handler.hpp b/include/libremidi/backends/alsa_seq/shared_handler.hpp index 3e9dd668..7404a468 100644 --- a/include/libremidi/backends/alsa_seq/shared_handler.hpp +++ b/include/libremidi/backends/alsa_seq/shared_handler.hpp @@ -127,13 +127,13 @@ struct shared_handler : public libremidi::shared_context { case callback_added: { auto [addr, cb] - = std::move(*std::get_if(&ev.payload)); + = std::move(*get_if(&ev.payload)); addresses.push_back(addr); callbacks.push_back(std::move(cb)); break; } case callback_removed: - auto addr = *std::get_if(&ev.payload); + auto addr = *get_if(&ev.payload); if (auto index = index_of_address(addr); index >= 0) { addresses.erase(addresses.begin() + index); @@ -179,7 +179,7 @@ struct shared_handler : public libremidi::shared_context struct event { event_type type; - std::variant payload; + libremidi_variant_alias::variant payload; }; snd_seq_t* client{}; diff --git a/include/libremidi/backends/jack/shared_handler.hpp b/include/libremidi/backends/jack/shared_handler.hpp index f1563fc6..b44f37d8 100644 --- a/include/libremidi/backends/jack/shared_handler.hpp +++ b/include/libremidi/backends/jack/shared_handler.hpp @@ -70,11 +70,10 @@ struct shared_handler : public libremidi::shared_context switch (ev.type) { case in_callback_added: - midiin_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + midiin_callbacks.push_back(std::move(*get_if(&ev.payload))); break; case in_callback_removed: { - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();) { if (it->token == idx) @@ -90,11 +89,10 @@ struct shared_handler : public libremidi::shared_context break; } case out_callback_added: - midiout_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + midiout_callbacks.push_back(std::move(*get_if(&ev.payload))); break; case out_callback_removed: - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();) { if (it->token == idx) @@ -138,7 +136,7 @@ struct shared_handler : public libremidi::shared_context struct event { event_type type; - std::variant payload; + libremidi_variant_alias::variant payload; }; boost::lockfree::spsc_queue events{16}; diff --git a/include/libremidi/backends/pipewire/shared_handler.hpp b/include/libremidi/backends/pipewire/shared_handler.hpp index 36122cbb..a6e531ac 100644 --- a/include/libremidi/backends/pipewire/shared_handler.hpp +++ b/include/libremidi/backends/pipewire/shared_handler.hpp @@ -71,10 +71,10 @@ struct shared_handler : public libremidi::shared_context { case in_callback_added: midiin_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + std::move(*get_if(&ev.payload))); break; case in_callback_removed: { - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiin_callbacks.begin(); it != midiin_callbacks.end();) { if (it->token == idx) @@ -91,10 +91,10 @@ struct shared_handler : public libremidi::shared_context } case out_callback_added: midiout_callbacks.push_back( - std::move(*std::get_if(&ev.payload))); + std::move(*get_if(&ev.payload))); break; case out_callback_removed: - auto idx = *std::get_if(&ev.payload); + auto idx = *get_if(&ev.payload); for (auto it = midiout_callbacks.begin(); it != midiout_callbacks.end();) { if (it->token == idx) @@ -138,7 +138,7 @@ struct shared_handler : public libremidi::shared_context struct event { event_type type; - std::variant payload; + libremidi_variant_alias::variant payload; }; boost::lockfree::spsc_queue events{16}; diff --git a/include/libremidi/backends/winmidi/midi_in.hpp b/include/libremidi/backends/winmidi/midi_in.hpp index c537f029..b8d0fc4e 100644 --- a/include/libremidi/backends/winmidi/midi_in.hpp +++ b/include/libremidi/backends/winmidi/midi_in.hpp @@ -92,7 +92,7 @@ class midi_in_impl final stdx::error open_port(const input_port& port, std::string_view) override { - auto device_id = std::get_if(&port.device); + auto device_id = get_if(&port.device); if (!device_id) return std::errc::invalid_argument; diff --git a/include/libremidi/backends/winmidi/midi_out.hpp b/include/libremidi/backends/winmidi/midi_out.hpp index 6b12f8cb..29b334c3 100644 --- a/include/libremidi/backends/winmidi/midi_out.hpp +++ b/include/libremidi/backends/winmidi/midi_out.hpp @@ -38,7 +38,7 @@ class midi_out_impl final stdx::error open_port(const output_port& port, std::string_view) override { - auto device_id = std::get_if(&port.device); + auto device_id = get_if(&port.device); if (!device_id) return std::errc::invalid_argument; diff --git a/include/libremidi/cmidi2.hpp b/include/libremidi/cmidi2.hpp index 87a40dbc..07b40ae2 100644 --- a/include/libremidi/cmidi2.hpp +++ b/include/libremidi/cmidi2.hpp @@ -6,6 +6,8 @@ #include #include +#include + #if !defined(_MSC_VER) #pragma GCC system_header #pragma clang system_header diff --git a/include/libremidi/config.hpp b/include/libremidi/config.hpp index b8ddbe6f..0193ffa5 100644 --- a/include/libremidi/config.hpp +++ b/include/libremidi/config.hpp @@ -1,6 +1,6 @@ #pragma once -#ifndef LIBREMIDI_CONFIG_HPP - #define LIBREMIDI_CONFIG_HPP + +#define LIBREMIDI_VERSION "5.4.1" // clang-format off #if !defined(LIBREMIDI_BASE_NAMESPACE) @@ -44,6 +44,7 @@ #define WIN32_LEAN_AND_MEAN #endif +// Dynamic exports #if defined(LIBREMIDI_EXPORTS) || defined(LIBREMIDI_MODULE_BUILD) #if defined(_MSC_VER) #define LIBREMIDI_EXPORT __declspec(dllexport) @@ -54,8 +55,7 @@ #define LIBREMIDI_EXPORT #endif -#define LIBREMIDI_VERSION "5.4.1" - +// Boost check to prevent ABI issues #if defined(LIBREMIDI_USE_BOOST) #if !__has_include() #error \ @@ -67,8 +67,8 @@ #endif #endif +// Use boost::small_vector if available #if __has_include() && !defined(LIBREMIDI_NO_BOOST) - #if LIBREMIDI_SLIM_MESSAGE > 0 #include NAMESPACE_LIBREMIDI @@ -92,6 +92,50 @@ using midi_bytes = std::vector; } #endif +// Use boost::variant2 if available +#if __has_include() && !defined(LIBREMIDI_NO_BOOST_VARIANT2) + #if __has_include() + #include +namespace libremidi_variant_alias = boost::variant2; + #else + #include +namespace libremidi_variant_alias = std; + #endif + +namespace libremidi +{ +using monostate = libremidi_variant_alias::monostate; +template +concept nothrow_move_constructible = std::is_nothrow_move_constructible_v; + +template +using variant = libremidi_variant_alias::variant; + +template +using slow_variant = libremidi_variant_alias::variant; + +template +using variant_element = libremidi_variant_alias::variant_alternative; +template +using variant_element_t = libremidi_variant_alias::variant_alternative_t; + +using libremidi_variant_alias::operator==; +using libremidi_variant_alias::operator!=; +using libremidi_variant_alias::operator<; +using libremidi_variant_alias::operator>; +using libremidi_variant_alias::operator<=; +using libremidi_variant_alias::operator>=; + +// using boost::variant2::in_place; +// using libremidi_variant_alias::in_place; +using libremidi_variant_alias::get; +using libremidi_variant_alias::get_if; +using libremidi_variant_alias::in_place_index; +using libremidi_variant_alias::in_place_type; +using libremidi_variant_alias::visit; +} +#endif + #if __has_include() && defined(LIBREMIDI_USE_NI_MIDI2) #define LIBREMIDI_NI_MIDI2_COMPAT 1 #endif @@ -107,4 +151,3 @@ using midi_bytes = std::vector; #else #define LIBREMIDI_PRECONDITION(...) #endif -#endif diff --git a/include/libremidi/configurations.hpp b/include/libremidi/configurations.hpp index 438052b7..9af9fba7 100644 --- a/include/libremidi/configurations.hpp +++ b/include/libremidi/configurations.hpp @@ -31,18 +31,18 @@ struct dummy_configuration { }; -using input_api_configuration = std::variant< +using input_api_configuration = libremidi_variant_alias::variant< unspecified_configuration, dummy_configuration, alsa_raw_input_configuration, alsa_raw_ump::input_configuration, alsa_seq::input_configuration, alsa_seq_ump::input_configuration, coremidi_input_configuration, coremidi_ump::input_configuration, emscripten_input_configuration, jack_input_configuration, - kbd_input_configuration, kdmapi::input_configuration, libremidi::net::dgram_input_configuration, - libremidi::net_ump::dgram_input_configuration, pipewire_input_configuration, - winmidi::input_configuration, winmm_input_configuration, winuwp_input_configuration, - jack_ump::input_configuration, pipewire_ump::input_configuration, android::input_configuration, - libremidi::API>; + kbd_input_configuration, kdmapi::input_configuration, + libremidi::net::dgram_input_configuration, libremidi::net_ump::dgram_input_configuration, + pipewire_input_configuration, winmidi::input_configuration, winmm_input_configuration, + winuwp_input_configuration, jack_ump::input_configuration, pipewire_ump::input_configuration, + android::input_configuration, libremidi::API>; -using output_api_configuration = std::variant< +using output_api_configuration = libremidi_variant_alias::variant< unspecified_configuration, dummy_configuration, alsa_raw_output_configuration, alsa_raw_ump::output_configuration, alsa_seq::output_configuration, alsa_seq_ump::output_configuration, coremidi_output_configuration, @@ -53,7 +53,7 @@ using output_api_configuration = std::variant< jack_ump::output_configuration, pipewire_ump::output_configuration, android::output_configuration, libremidi::API>; -using observer_api_configuration = std::variant< +using observer_api_configuration = libremidi_variant_alias::variant< unspecified_configuration, dummy_configuration, alsa_raw_observer_configuration, alsa_raw_ump::observer_configuration, alsa_seq::observer_configuration, alsa_seq_ump::observer_configuration, coremidi_observer_configuration, diff --git a/include/libremidi/libremidi.cpp b/include/libremidi/libremidi.cpp index dcd8f949..fd1427ef 100644 --- a/include/libremidi/libremidi.cpp +++ b/include/libremidi/libremidi.cpp @@ -59,7 +59,7 @@ libremidi::API midi_api(const input_api_configuration& conf) { libremidi::API ret = libremidi::API::UNSPECIFIED; midi_any::for_all_backends([&](T) { - if (std::get_if(&conf)) + if (get_if(&conf)) { ret = T::API; } @@ -71,7 +71,7 @@ libremidi::API midi_api(const output_api_configuration& conf) { libremidi::API ret = libremidi::API::UNSPECIFIED; midi_any::for_all_backends([&](T) { - if (std::get_if(&conf)) + if (get_if(&conf)) { ret = T::API; } @@ -83,7 +83,7 @@ libremidi::API midi_api(const observer_api_configuration& conf) { libremidi::API ret = libremidi::API::UNSPECIFIED; midi_any::for_all_backends([&](T) { - if (std::get_if(&conf)) + if (get_if(&conf)) { ret = T::API; } diff --git a/include/libremidi/midi_in.cpp b/include/libremidi/midi_in.cpp index ecd4673e..1d4d9a34 100644 --- a/include/libremidi/midi_in.cpp +++ b/include/libremidi/midi_in.cpp @@ -67,7 +67,7 @@ make_midi_in(auto base_conf, input_api_configuration api_conf, auto backends) assert(base_conf.on_message || base_conf.on_raw_data); auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -135,11 +135,11 @@ LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi1_i LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi1_in(const input_configuration& base_conf, const input_api_configuration& api_conf) { - if (std::get_if(&api_conf)) + if (get_if(&api_conf)) { return make_midi1_in(base_conf); } - else if (auto api_p = std::get_if(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { @@ -233,11 +233,11 @@ LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi2_i LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi2_in(const ump_input_configuration& base_conf, const input_api_configuration& api_conf) { - if (std::get_if(&api_conf)) + if (get_if(&api_conf)) { return make_midi2_in(base_conf); } - else if (auto api_p = std::get_if(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { diff --git a/include/libremidi/midi_out.cpp b/include/libremidi/midi_out.cpp index e7123539..205f369e 100644 --- a/include/libremidi/midi_out.cpp +++ b/include/libremidi/midi_out.cpp @@ -15,7 +15,7 @@ make_midi_out_impl(auto base_conf, output_api_configuration api_conf) { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -61,11 +61,11 @@ make_midi_out(const output_configuration& base_conf) LIBREMIDI_STATIC_INLINE_IMPLEMENTATION std::unique_ptr make_midi_out(const output_configuration& base_conf, const output_api_configuration& api_conf) { - if (std::get_if(&api_conf)) + if (get_if(&api_conf)) { return make_midi_out(base_conf); } - else if (auto api_p = std::get_if(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { diff --git a/include/libremidi/observer.cpp b/include/libremidi/observer.cpp index 0f5972fe..156cba97 100644 --- a/include/libremidi/observer.cpp +++ b/include/libremidi/observer.cpp @@ -43,7 +43,7 @@ LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, observer_api_configurat { std::unique_ptr ptr; auto from_api = [&](T& /*backend*/) mutable { - if (auto conf = std::get_if(&api_conf)) + if (auto conf = get_if(&api_conf)) { ptr = libremidi::make(std::move(base_conf), std::move(*conf)); return true; @@ -59,11 +59,11 @@ LIBREMIDI_INLINE auto make_observer_impl(auto base_conf, observer_api_configurat LIBREMIDI_INLINE std::unique_ptr make_observer(const auto& base_conf, observer_api_configuration api_conf) { - if (std::get_if(&api_conf)) + if (get_if(&api_conf)) { return make_observer(base_conf); } - else if (auto api_p = std::get_if(&api_conf)) + else if (auto api_p = get_if(&api_conf)) { if (*api_p == libremidi::API::UNSPECIFIED) { diff --git a/include/libremidi/port_comparison.hpp b/include/libremidi/port_comparison.hpp index cac39a2a..b5b0e0f8 100644 --- a/include/libremidi/port_comparison.hpp +++ b/include/libremidi/port_comparison.hpp @@ -206,7 +206,7 @@ struct port_heuristic_matcher template void score_variant(int& score, const T& target_v, const T& cand_v, int reward, int penalty) const { - if (std::holds_alternative(target_v)) + if (holds_alternative(target_v)) return; // For those we want an exact search @@ -214,7 +214,7 @@ struct port_heuristic_matcher { score += reward; } - else if (!std::holds_alternative(cand_v)) + else if (!holds_alternative(cand_v)) { // Candidate has a specific ID, and it differs from Target's specific ID. score += penalty; diff --git a/include/libremidi/port_information.hpp b/include/libremidi/port_information.hpp index 5dba0e82..9edeffbe 100644 --- a/include/libremidi/port_information.hpp +++ b/include/libremidi/port_information.hpp @@ -43,7 +43,7 @@ struct LIBREMIDI_EXPORT port_information // this is not the string but the binary representation). // WinMM: unavailable // WinUWP: unavailable - container_identifier container = std::monostate{}; + container_identifier container = libremidi_variant_alias::monostate{}; /// Device identifier if the API provides one // Android: unavailable @@ -54,7 +54,7 @@ struct LIBREMIDI_EXPORT port_information // WinMIDI: EndpointDeviceId (std::string), e.g. "\\?\swd#midisrv#midiu_ksa..." // WinMM: MIDI{IN,OUT}CAPS mId / pId { uint16_t manufacturer_id, uint16_t product_id; } // WinUWP: unavailable - device_identifier device = std::monostate{}; + device_identifier device = libremidi_variant_alias::monostate{}; /// Handle to the port identifier if the API provides one // Android: index of the MIDI device in the list provided by the OS. diff --git a/include/libremidi/types.hpp b/include/libremidi/types.hpp index 4e679402..06c2e843 100644 --- a/include/libremidi/types.hpp +++ b/include/libremidi/types.hpp @@ -1,4 +1,6 @@ #pragma once +#include + #include #include #include @@ -60,8 +62,10 @@ struct usb_device_identifier std::strong_ordering operator<=>(const usb_device_identifier& other) const noexcept = default; }; -using container_identifier = std::variant; -using device_identifier - = std::variant; -using endpoint_identifier = std::variant; +using container_identifier = libremidi_variant_alias::variant< + libremidi_variant_alias::monostate, uuid, std::string, std::uint64_t>; +using device_identifier = libremidi_variant_alias::variant< + libremidi_variant_alias::monostate, std::string, std::uint64_t, usb_device_identifier>; +using endpoint_identifier = libremidi_variant_alias::variant< + libremidi_variant_alias::monostate, std::string, std::uint64_t>; } From 99cbae884a06a65a5bacad76f5e88c5bbe47bf02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Mon, 5 Jan 2026 22:55:36 -0500 Subject: [PATCH 2/3] ports: add another heuristic to compare ports --- include/libremidi/detail/memory.hpp | 2 + include/libremidi/port_comparison.hpp | 335 +++++++++++++++++++++++--- 2 files changed, 306 insertions(+), 31 deletions(-) diff --git a/include/libremidi/detail/memory.hpp b/include/libremidi/detail/memory.hpp index fdb12229..02ddc27a 100644 --- a/include/libremidi/detail/memory.hpp +++ b/include/libremidi/detail/memory.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include diff --git a/include/libremidi/port_comparison.hpp b/include/libremidi/port_comparison.hpp index b5b0e0f8..4d7ef255 100644 --- a/include/libremidi/port_comparison.hpp +++ b/include/libremidi/port_comparison.hpp @@ -1,9 +1,23 @@ #pragma once #include - -#include -#include +#if __has_include() + #include +namespace libremidi +{ +template +using temp_map_type = boost::container::flat_map; +} +#else + #include +namespace libremidi +{ +template +using temp_map_type = std::map; +} +#endif +#include #include +#include #include namespace libremidi @@ -47,6 +61,29 @@ struct port_identity_less } }; +struct port_exactly_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.container == rhs.container && lhs.device == rhs.device + && lhs.port == rhs.port && lhs.manufacturer == rhs.manufacturer + && lhs.product == rhs.product && lhs.serial == rhs.serial + && lhs.device_name == rhs.device_name && lhs.port_name == rhs.port_name + && lhs.display_name == rhs.display_name; + } +}; + +struct port_mostly_equal +{ + bool operator()(const port_information& lhs, const port_information& rhs) + { + return lhs.api == rhs.api && lhs.container == rhs.container && lhs.device == rhs.device + && lhs.port == rhs.port && lhs.manufacturer == rhs.manufacturer + && lhs.product == rhs.product && lhs.serial == rhs.serial + && lhs.device_name == rhs.device_name && lhs.port_name == rhs.port_name; + } +}; + struct port_heuristic_matcher { // Configuration for weights @@ -222,20 +259,20 @@ struct port_heuristic_matcher } }; -struct input_port_search_result +template +struct port_search_result { - const input_port* port = nullptr; + const T* port = nullptr; int score = 0; bool found = false; }; -inline input_port_search_result find_closest_port( - const input_port& target, - std::span candidates) +template +inline port_search_result find_closest_port(const T& target, std::span candidates) { port_heuristic_matcher matcher{}; - const input_port* best_match = nullptr; + const T* best_match = nullptr; port_heuristic_matcher::match_score best_score; best_score.score = -1; @@ -256,37 +293,273 @@ inline input_port_search_result find_closest_port( return { nullptr, 0, false }; } -struct output_port_search_result +template +inline std::vector +optimistic_serialized_port_lookup(const T& target, std::span ports) { - const output_port* port = nullptr; - int score = 0; - bool found = false; -}; + if (ports.empty()) + return {}; -inline output_port_search_result find_closest_port( - const output_port& target, - std::span candidates) -{ - port_heuristic_matcher matcher{}; + // 1. Look for an exact match on all fields + std::vector candidates; + for (auto& candidate : ports) + { + if (port_mostly_equal{}(target, candidate)) + candidates.push_back(&candidate); + } + switch (candidates.size()) + { + case 0: { + break; + } + case 1: { + return candidates; + } + default: { + // If we have an exact match return it + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + + // Else return the entire bunch as we have no way to differentiate + return candidates; + } + } - const output_port* best_match = nullptr; - port_heuristic_matcher::match_score best_score{}; - best_score.score = -1; + // 2. Heuristics - for (const auto& candidate : candidates) + // Look for candidates in the same API + candidates.clear(); + for (auto& candidate : ports) { - port_heuristic_matcher::match_score current = matcher.calculate(target, candidate); - - if (current.is_match() && current > best_score) + if (target.api != libremidi::API::UNSPECIFIED) { - best_score = current; - best_match = &candidate; + if (target.api == candidate.api) + { + // Port was set, let's give a high trust to this + if (target.port != static_cast(-1)) + { + if (target.port == candidate.port) + { + if (target.port_name == candidate.port_name + && target.device_name == candidate.device_name) + { + // We can be 99% confident it's the right one + candidates.push_back(&candidate); + } + else if ( + port_heuristic_matcher::fuzzy_match_name(target.port_name, candidate.port_name) + >= 0.8 + && port_heuristic_matcher::fuzzy_match_name( + target.device_name, candidate.device_name) + >= 0.8) + { + candidates.push_back(&candidate); + } + else + { + // Same API & same port, different port_name & device_name: + // very likely it's the wrong one + continue; + } + } + } + else + { +#define do_compare(MEMBER) \ + { \ + ok &= target.MEMBER == candidate.MEMBER || target.MEMBER.empty(); \ + if (!ok) \ + continue; \ + } + bool ok = true; + + // These three are compared later + // do_compare(display_name); + // do_compare(container); + // do_compare(device); + do_compare(manufacturer); + do_compare(product); + do_compare(serial); + do_compare(device_name); + do_compare(port_name); + +#undef do_compare + // If we got there it's a very good candidate + candidates.push_back(&candidate); + } + } } } - if (best_match) - return {best_match, best_score.score, true}; + switch (candidates.size()) + { + case 0: { + break; + } + case 1: { + // One candidate in the same API + return {candidates[0]}; + } + default: { + // Let's look if we have one that has the same container + if (!get_if(&target.container) + && !get_if(&target.device) + && !target.display_name.empty()) + { + for (auto* candidate : candidates) + if (target.container == candidate->container && target.device == candidate->device + && target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.container == candidate->container && target.device == candidate->device) + return {candidate}; + for (auto* candidate : candidates) + if (target.container == candidate->container) + return {candidate}; + for (auto* candidate : candidates) + if (target.device == candidate->device) + return {candidate}; + } + else if ( + !get_if(&target.container) + && !target.display_name.empty()) + { + for (auto* candidate : candidates) + if (target.container == candidate->container + && target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.container == candidate->container) + return {candidate}; + } + else if ( + !get_if(&target.device) + && !target.display_name.empty()) + { + for (auto* candidate : candidates) + if (target.device == candidate->device && target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.display_name == candidate->display_name) + return {candidate}; + for (auto* candidate : candidates) + if (target.device == candidate->device) + return {candidate}; + } + // Else return them all as we have no way to differentiate + return candidates; + } + } + + // Look for candidates in different APIs. + // Here most informations are different, so we only do a fuzzy match + candidates.clear(); + libremidi::temp_map_type ranked_candidates; + for (auto& candidate : ports) + { + int score = 0; + if (!target.port_name.empty() && !candidate.port_name.empty()) + { + float res = port_heuristic_matcher::fuzzy_match_name(target.port_name, candidate.port_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.device_name.empty() && !candidate.device_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.device_name, candidate.device_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.display_name.empty() && !candidate.display_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.display_name, candidate.display_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.display_name.empty() && !candidate.port_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.display_name, candidate.port_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.port_name.empty() && !candidate.display_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.port_name, candidate.display_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.display_name.empty() && !candidate.device_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.display_name, candidate.device_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.device_name.empty() && !candidate.display_name.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.device_name, candidate.display_name); + if (res > 0.7) + { + score += res; + } + } + if (!target.manufacturer.empty() && !candidate.manufacturer.empty()) + { + float res + = port_heuristic_matcher::fuzzy_match_name(target.manufacturer, candidate.manufacturer); + if (res > 0.7) + { + score += 3 * res; + } + } + if (!target.product.empty() && !candidate.product.empty()) + { + float res = port_heuristic_matcher::fuzzy_match_name(target.product, candidate.product); + if (res > 0.7) + { + score += 5 * res; + } + } + if (!target.serial.empty() && !candidate.serial.empty()) + { + float res = port_heuristic_matcher::fuzzy_match_name(target.serial, candidate.serial); + if (res > 0.7) + { + score += 10 * res; + } + } + if (score > 0) + ranked_candidates[score] = &candidate; + } - return {nullptr, 0, false}; + candidates.clear(); + candidates.reserve(ranked_candidates.size()); + for (auto [score, candidate] : ranked_candidates) + candidates.insert(candidates.begin(), candidate); + return candidates; } } From 07fee02ca16378176b5bc3acc48f4b6fa6b90d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Sat, 10 Jan 2026 17:17:27 -0500 Subject: [PATCH 3/3] ci: build fixes --- examples/lookup.cpp | 3 ++- examples/utils.hpp | 8 ++++---- include/libremidi/config.hpp | 12 ++++++++---- include/libremidi/types.hpp | 1 - 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/lookup.cpp b/examples/lookup.cpp index 19392f91..9ff32f04 100644 --- a/examples/lookup.cpp +++ b/examples/lookup.cpp @@ -34,7 +34,8 @@ void lookup_api(libremidi::API api, const libremidi::input_port& searched) { // Check inputs. auto ports = midi.get_input_ports(); - auto res = libremidi::find_closest_port(searched, ports); + auto res + = libremidi::find_closest_port(searched, std::span(ports)); if (res.found) { std::cout << "Found: " << *res.port << "\n"; } diff --git a/examples/utils.hpp b/examples/utils.hpp index 2476933c..9c785697 100644 --- a/examples/utils.hpp +++ b/examples/utils.hpp @@ -49,9 +49,9 @@ inline std::ostream& operator<<(std::ostream& s, const libremidi::container_iden void operator()(libremidi::uuid u) { s << "uuid"; } void operator()(std::string u) { s << u; } void operator()(uint64_t u) { s << u; } - void operator()(std::monostate) { } + void operator()(libremidi::monostate) { } } vis{s}; - std::visit(vis, id); + visit(vis, id); return s; } @@ -73,9 +73,9 @@ inline std::ostream& operator<<(std::ostream& s, const libremidi::device_identif << std::setfill('0') << std::setw(4) << (res & 0x0000FFFF); s.flags(f); } - void operator()(std::monostate) { } + void operator()(libremidi::monostate) { } } vis{s}; - std::visit(vis, id); + visit(vis, id); return s; } diff --git a/include/libremidi/config.hpp b/include/libremidi/config.hpp index 0193ffa5..612308b9 100644 --- a/include/libremidi/config.hpp +++ b/include/libremidi/config.hpp @@ -93,14 +93,19 @@ using midi_bytes = std::vector; #endif // Use boost::variant2 if available +#define LIBREMIDI_NO_BOOST_VARIANT2 1 #if __has_include() && !defined(LIBREMIDI_NO_BOOST_VARIANT2) #if __has_include() #include + #define LIBREMIDI_VARIANT_IS_BOOST_VARIANT2 namespace libremidi_variant_alias = boost::variant2; - #else - #include -namespace libremidi_variant_alias = std; #endif +#endif + +#if !defined(LIBREMIDI_VARIANT_IS_BOOST_VARIANT2) + #include +namespace libremidi_variant_alias = std; +#endif namespace libremidi { @@ -134,7 +139,6 @@ using libremidi_variant_alias::in_place_index; using libremidi_variant_alias::in_place_type; using libremidi_variant_alias::visit; } -#endif #if __has_include() && defined(LIBREMIDI_USE_NI_MIDI2) #define LIBREMIDI_NI_MIDI2_COMPAT 1 diff --git a/include/libremidi/types.hpp b/include/libremidi/types.hpp index 06c2e843..dfd0e2ed 100644 --- a/include/libremidi/types.hpp +++ b/include/libremidi/types.hpp @@ -5,7 +5,6 @@ #include #include #include -#include NAMESPACE_LIBREMIDI {