11#pragma once
22#include < libremidi/observer_configuration.hpp>
3-
4- #include < span>
5- #include < variant>
3+ #if __has_include(<boost/container/flat_map.hpp>)
4+ #include < boost/container/flat_map.hpp>
5+ namespace libremidi
6+ {
7+ template <typename K, typename V>
8+ using temp_map_type = boost::container::flat_map<K, V>;
9+ }
10+ #else
11+ #include < map>
12+ namespace libremidi
13+ {
14+ template <typename K, typename V>
15+ using temp_map_type = std::map<K, V>;
16+ }
17+ #endif
18+ #include < algorithm>
619#include < limits>
20+ #include < span>
721#include < tuple>
822
923namespace libremidi
@@ -47,6 +61,29 @@ struct port_identity_less
4761 }
4862};
4963
64+ struct port_exactly_equal
65+ {
66+ bool operator ()(const port_information& lhs, const port_information& rhs)
67+ {
68+ return lhs.api == rhs.api && lhs.container == rhs.container && lhs.device == rhs.device
69+ && lhs.port == rhs.port && lhs.manufacturer == rhs.manufacturer
70+ && lhs.product == rhs.product && lhs.serial == rhs.serial
71+ && lhs.device_name == rhs.device_name && lhs.port_name == rhs.port_name
72+ && lhs.display_name == rhs.display_name ;
73+ }
74+ };
75+
76+ struct port_mostly_equal
77+ {
78+ bool operator ()(const port_information& lhs, const port_information& rhs)
79+ {
80+ return lhs.api == rhs.api && lhs.container == rhs.container && lhs.device == rhs.device
81+ && lhs.port == rhs.port && lhs.manufacturer == rhs.manufacturer
82+ && lhs.product == rhs.product && lhs.serial == rhs.serial
83+ && lhs.device_name == rhs.device_name && lhs.port_name == rhs.port_name ;
84+ }
85+ };
86+
5087struct port_heuristic_matcher
5188{
5289 // Configuration for weights
@@ -222,20 +259,20 @@ struct port_heuristic_matcher
222259 }
223260};
224261
225- struct input_port_search_result
262+ template <typename T>
263+ struct port_search_result
226264{
227- const input_port * port = nullptr ;
265+ const T * port = nullptr ;
228266 int score = 0 ;
229267 bool found = false ;
230268};
231269
232- inline input_port_search_result find_closest_port (
233- const input_port& target,
234- std::span<input_port> candidates)
270+ template <typename T>
271+ inline port_search_result<T> find_closest_port (const T& target, std::span<const T> candidates)
235272{
236273 port_heuristic_matcher matcher{};
237274
238- const input_port * best_match = nullptr ;
275+ const T * best_match = nullptr ;
239276 port_heuristic_matcher::match_score best_score;
240277 best_score.score = -1 ;
241278
@@ -256,37 +293,273 @@ inline input_port_search_result find_closest_port(
256293 return { nullptr , 0 , false };
257294}
258295
259- struct output_port_search_result
296+ template <typename T>
297+ inline std::vector<const T*>
298+ optimistic_serialized_port_lookup (const T& target, std::span<const T> ports)
260299{
261- const output_port* port = nullptr ;
262- int score = 0 ;
263- bool found = false ;
264- };
300+ if (ports.empty ())
301+ return {};
265302
266- inline output_port_search_result find_closest_port (
267- const output_port& target,
268- std::span<output_port> candidates)
269- {
270- port_heuristic_matcher matcher{};
303+ // 1. Look for an exact match on all fields
304+ std::vector<const T*> candidates;
305+ for (auto & candidate : ports)
306+ {
307+ if (port_mostly_equal{}(target, candidate))
308+ candidates.push_back (&candidate);
309+ }
310+ switch (candidates.size ())
311+ {
312+ case 0 : {
313+ break ;
314+ }
315+ case 1 : {
316+ return candidates;
317+ }
318+ default : {
319+ // If we have an exact match return it
320+ for (auto * candidate : candidates)
321+ if (target.display_name == candidate->display_name )
322+ return {candidate};
323+
324+ // Else return the entire bunch as we have no way to differentiate
325+ return candidates;
326+ }
327+ }
271328
272- const output_port* best_match = nullptr ;
273- port_heuristic_matcher::match_score best_score{};
274- best_score.score = -1 ;
329+ // 2. Heuristics
275330
276- for (const auto & candidate : candidates)
331+ // Look for candidates in the same API
332+ candidates.clear ();
333+ for (auto & candidate : ports)
277334 {
278- port_heuristic_matcher::match_score current = matcher.calculate (target, candidate);
279-
280- if (current.is_match () && current > best_score)
335+ if (target.api != libremidi::API::UNSPECIFIED)
281336 {
282- best_score = current;
283- best_match = &candidate;
337+ if (target.api == candidate.api )
338+ {
339+ // Port was set, let's give a high trust to this
340+ if (target.port != static_cast <port_handle>(-1 ))
341+ {
342+ if (target.port == candidate.port )
343+ {
344+ if (target.port_name == candidate.port_name
345+ && target.device_name == candidate.device_name )
346+ {
347+ // We can be 99% confident it's the right one
348+ candidates.push_back (&candidate);
349+ }
350+ else if (
351+ port_heuristic_matcher::fuzzy_match_name (target.port_name , candidate.port_name )
352+ >= 0.8
353+ && port_heuristic_matcher::fuzzy_match_name (
354+ target.device_name , candidate.device_name )
355+ >= 0.8 )
356+ {
357+ candidates.push_back (&candidate);
358+ }
359+ else
360+ {
361+ // Same API & same port, different port_name & device_name:
362+ // very likely it's the wrong one
363+ continue ;
364+ }
365+ }
366+ }
367+ else
368+ {
369+ #define do_compare (MEMBER ) \
370+ { \
371+ ok &= target.MEMBER == candidate.MEMBER || target.MEMBER .empty (); \
372+ if (!ok) \
373+ continue ; \
374+ }
375+ bool ok = true ;
376+
377+ // These three are compared later
378+ // do_compare(display_name);
379+ // do_compare(container);
380+ // do_compare(device);
381+ do_compare (manufacturer);
382+ do_compare (product);
383+ do_compare (serial);
384+ do_compare (device_name);
385+ do_compare (port_name);
386+
387+ #undef do_compare
388+ // If we got there it's a very good candidate
389+ candidates.push_back (&candidate);
390+ }
391+ }
284392 }
285393 }
286394
287- if (best_match)
288- return {best_match, best_score.score , true };
395+ switch (candidates.size ())
396+ {
397+ case 0 : {
398+ break ;
399+ }
400+ case 1 : {
401+ // One candidate in the same API
402+ return {candidates[0 ]};
403+ }
404+ default : {
405+ // Let's look if we have one that has the same container
406+ if (!get_if<libremidi_variant_alias::monostate>(&target.container )
407+ && !get_if<libremidi_variant_alias::monostate>(&target.device )
408+ && !target.display_name .empty ())
409+ {
410+ for (auto * candidate : candidates)
411+ if (target.container == candidate->container && target.device == candidate->device
412+ && target.display_name == candidate->display_name )
413+ return {candidate};
414+ for (auto * candidate : candidates)
415+ if (target.display_name == candidate->display_name )
416+ return {candidate};
417+ for (auto * candidate : candidates)
418+ if (target.container == candidate->container && target.device == candidate->device )
419+ return {candidate};
420+ for (auto * candidate : candidates)
421+ if (target.container == candidate->container )
422+ return {candidate};
423+ for (auto * candidate : candidates)
424+ if (target.device == candidate->device )
425+ return {candidate};
426+ }
427+ else if (
428+ !get_if<libremidi_variant_alias::monostate>(&target.container )
429+ && !target.display_name .empty ())
430+ {
431+ for (auto * candidate : candidates)
432+ if (target.container == candidate->container
433+ && target.display_name == candidate->display_name )
434+ return {candidate};
435+ for (auto * candidate : candidates)
436+ if (target.display_name == candidate->display_name )
437+ return {candidate};
438+ for (auto * candidate : candidates)
439+ if (target.container == candidate->container )
440+ return {candidate};
441+ }
442+ else if (
443+ !get_if<libremidi_variant_alias::monostate>(&target.device )
444+ && !target.display_name .empty ())
445+ {
446+ for (auto * candidate : candidates)
447+ if (target.device == candidate->device && target.display_name == candidate->display_name )
448+ return {candidate};
449+ for (auto * candidate : candidates)
450+ if (target.display_name == candidate->display_name )
451+ return {candidate};
452+ for (auto * candidate : candidates)
453+ if (target.device == candidate->device )
454+ return {candidate};
455+ }
456+ // Else return them all as we have no way to differentiate
457+ return candidates;
458+ }
459+ }
460+
461+ // Look for candidates in different APIs.
462+ // Here most informations are different, so we only do a fuzzy match
463+ candidates.clear ();
464+ libremidi::temp_map_type<int , const T*> ranked_candidates;
465+ for (auto & candidate : ports)
466+ {
467+ int score = 0 ;
468+ if (!target.port_name .empty () && !candidate.port_name .empty ())
469+ {
470+ float res = port_heuristic_matcher::fuzzy_match_name (target.port_name , candidate.port_name );
471+ if (res > 0.7 )
472+ {
473+ score += res;
474+ }
475+ }
476+ if (!target.device_name .empty () && !candidate.device_name .empty ())
477+ {
478+ float res
479+ = port_heuristic_matcher::fuzzy_match_name (target.device_name , candidate.device_name );
480+ if (res > 0.7 )
481+ {
482+ score += res;
483+ }
484+ }
485+ if (!target.display_name .empty () && !candidate.display_name .empty ())
486+ {
487+ float res
488+ = port_heuristic_matcher::fuzzy_match_name (target.display_name , candidate.display_name );
489+ if (res > 0.7 )
490+ {
491+ score += res;
492+ }
493+ }
494+ if (!target.display_name .empty () && !candidate.port_name .empty ())
495+ {
496+ float res
497+ = port_heuristic_matcher::fuzzy_match_name (target.display_name , candidate.port_name );
498+ if (res > 0.7 )
499+ {
500+ score += res;
501+ }
502+ }
503+ if (!target.port_name .empty () && !candidate.display_name .empty ())
504+ {
505+ float res
506+ = port_heuristic_matcher::fuzzy_match_name (target.port_name , candidate.display_name );
507+ if (res > 0.7 )
508+ {
509+ score += res;
510+ }
511+ }
512+ if (!target.display_name .empty () && !candidate.device_name .empty ())
513+ {
514+ float res
515+ = port_heuristic_matcher::fuzzy_match_name (target.display_name , candidate.device_name );
516+ if (res > 0.7 )
517+ {
518+ score += res;
519+ }
520+ }
521+ if (!target.device_name .empty () && !candidate.display_name .empty ())
522+ {
523+ float res
524+ = port_heuristic_matcher::fuzzy_match_name (target.device_name , candidate.display_name );
525+ if (res > 0.7 )
526+ {
527+ score += res;
528+ }
529+ }
530+ if (!target.manufacturer .empty () && !candidate.manufacturer .empty ())
531+ {
532+ float res
533+ = port_heuristic_matcher::fuzzy_match_name (target.manufacturer , candidate.manufacturer );
534+ if (res > 0.7 )
535+ {
536+ score += 3 * res;
537+ }
538+ }
539+ if (!target.product .empty () && !candidate.product .empty ())
540+ {
541+ float res = port_heuristic_matcher::fuzzy_match_name (target.product , candidate.product );
542+ if (res > 0.7 )
543+ {
544+ score += 5 * res;
545+ }
546+ }
547+ if (!target.serial .empty () && !candidate.serial .empty ())
548+ {
549+ float res = port_heuristic_matcher::fuzzy_match_name (target.serial , candidate.serial );
550+ if (res > 0.7 )
551+ {
552+ score += 10 * res;
553+ }
554+ }
555+ if (score > 0 )
556+ ranked_candidates[score] = &candidate;
557+ }
289558
290- return {nullptr , 0 , false };
559+ candidates.clear ();
560+ candidates.reserve (ranked_candidates.size ());
561+ for (auto [score, candidate] : ranked_candidates)
562+ candidates.insert (candidates.begin (), candidate);
563+ return candidates;
291564}
292565}
0 commit comments